希望各位朋友能多提意见,共同进步,祝各位同学,前程似锦,万事如意。

github 地址

功能

  1. 描边功能
  2. 图片和描边间距功能
  3. 图片四个角,每个角均可单独设置 X Y 轴方向的圆弧半径

效果图

可用属性

    /**
     * 图片展示方式
     * 0 -- 图片顶部开始展示,铺满,如果Y轴铺满时,X轴大,则图片水平居中
     * 1 -- 图片中心点与指定区域中心重合
     * 2 -- 图片底部开始展示,铺满,如果Y轴铺满时,X轴大,则图片水平居中
     * 3 -- 图片完全展示
     */
    public static final int TOP = 0;
    public static final int CENTER = 1;
    public static final int BOTTOM = 2;
    public static final int FITXY = 3;
复制代码

主要逻辑

  1. 使用 Path 创建图形路径
  2. 根据绘制所需占据的矩形 RectF,在 BitMap 中找到相似矩形 Src,然后将 BitMap 指定区域内容绘制到 RectF 中,配合 PorterDuffXfermode 进行挖洞

代码解读

  1. 构造器里面获取自定义属性的值,并提供默认值
  2. initRadius,设置半径,看注释
 private void initRadius() {
        //  该处便于代码编写 如XML设置 radius = 20,topLeftRadius = 10,最终结果是  10 20 20 20
        if (radius != 0) {
            topLeftRadius = topLeftRadius == 0 ? radius : topLeftRadius;
            ...
        }
        //  如果设置了 radius = 20,topLeftRadius = 10,topLeftRadius_x = 30,
        //  最终结果,topLeftRadius_x = 30,topLeftRadius_y = 10,其余 20 
        topLeftRadius_x = topLeftRadius_x == 0 ? topLeftRadius : topLeftRadius_x;
        topLeftRadius_y = topLeftRadius_y == 0 ? topLeftRadius : topLeftRadius_y;
        ...  
}
复制代码
  1. 判断是否需要绘制多边形,也就是说,如果使用者不设置如下的属性,那么将等同于普通的 imageView
        //  判断是否需要调用绘制函数
        circle = borderWidth != 0 || borderSpace != 0 ||
                topLeftRadius_x != 0 || topLeftRadius_y != 0 ||
                topRightRadius_x != 0 || topRightRadius_y != 0 ||
                bottomLeftRadius_x != 0 || bottomLeftRadius_y != 0 ||
                bottomRightRadius_x != 0 || bottomRightRadius_y != 0;
复制代码
  1. 针对 Glide 设置
        if (circle) {
            //  为什么设置这一条,因为Glide中,在into 源码内
            //  不同的 ScaleType 会对drawable进行压缩,一旦压缩了,我们在onDraw里面获取图片的大小就没有意义了
            setScaleType(ScaleType.MATRIX);
        }
复制代码

onDraw

知识点

  1. 绘制边框

在绘制边框时候,线宽是以线为中心,两边扩大,所以会有一半的线宽绘制不出来 所以,我们在绘制描边时候,调用 RectF.inset,调整矩形大小

  1. 绘制图片(2.3 与 2.5 是最重要的部分,在下面有详细解释)

    2.1、在 Canvans 中为图片指定绘制区域 RectF,RectF 需要进行 inset() 调整,调整值为 描边宽度 + 内间距宽度

    2.2、调用 canvas.saveLayer,得到 layerID

    2.3、绘制圆角矩形,看函数

     drawPath(canvas, rectF, borderPaint, i);
    复制代码

    2.4、根据 rectF,在 BitMap 中找到 与 rectF 的相似矩形 src,然后返回

     Rect src = getSrc(bitmap, (int) rectF.width(), (int) rectF.height());
    复制代码

    2.5、设置挖洞模式

     paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    复制代码

    2.6、将 BitMap 指定大小的 src 区域的像素,绘制到 rectF,使用画笔 paint,而画笔已经设置了挖洞模式

     canvas.drawBitmap(bitmap, src, rectF, paint);
    复制代码

    2.7、paint 还原,将 layerID 的图层绘制到 Canvas

 @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        Drawable drawable = getDrawable();
    //  使用局部变量,降低函数调用次数
    int vw = getMeasuredWidth();
    int vh = getMeasuredHeight();

    int paddingLeft = getPaddingLeft();
    int paddingRight = getPaddingRight();
    int paddingTop = getPaddingTop();
    int paddingBottom = getPaddingBottom();

    //  绘制描边
    <span class="hljs-keyword">if</span> (borderWidth != 0) {
        RectF rectF = new RectF(paddingLeft, paddingTop, vw - paddingRight, vh - paddingBottom);
        //  描边会有一半处于框体之外
        <span class="hljs-built_in">float</span> i = borderWidth / 2;
        //  移动矩形,以便于描边都处于view内
        rectF.inset(i, i);
        //  绘制描边,半径需要进行偏移 i
        drawPath(canvas, rectF, borderPaint, i);

    }

    <span class="hljs-keyword">if</span> ((null != drawable &amp;&amp; circle)) {
        RectF rectF = new RectF(paddingLeft, paddingTop, vw - paddingRight, vh - paddingBottom);
        //  矩形需要缩小的值
        <span class="hljs-built_in">float</span> i = borderWidth + borderSpace;
        //  这里解释一下,为什么要减去一个像素,因为像素融合时,由于锯齿的存在和图片像素不高,会导致图片和边框出现1像素的间隙
        //  大家可以试一下,去掉这一句,然后用高清图就不会出问题,用非高清图就会出现
        i = i &gt; 1 ? i - 1 : 0;
        //  矩形偏移
        rectF.inset(i, i);
        int layerId = canvas.saveLayer(rectF, null, Canvas.ALL_SAVE_FLAG);
        //  多边形
        drawPath(canvas, rectF, paint, i);
        //  设置像素融合模式
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //  drawable转为 bitmap
        Bitmap bitmap = drawableToBitmap(drawable);
        //  根据图片的大小,控件的大小,图片的展示形式,然后来计算图片的src取值范围
        Rect src = getSrc(bitmap, (int) rectF.width(), (int) rectF.height());
        //  dst取整个控件,也就是表示,我们的图片要占满整个控件
        canvas.drawBitmap(bitmap, src, rectF, paint);
        paint.setXfermode(null);
        canvas.restoreToCount(layerId);
    } <span class="hljs-keyword">else</span> {
        super.onDraw(canvas);
    }
}
复制代码

2.3 绘制圆角矩形

使用 path绘制圆角矩形,而且提供了 四个角,每个角都可以单独设置 X Y的半径,CW是顺时针的意思
复制代码
    /**
     * 绘制多边形
     *
     * @param canvas 画布
     * @param rectF  矩形
     * @param paint  画笔
     * @param offset 半径偏移量
     */
    private void drawPath(Canvas canvas, RectF rectF, Paint paint, float offset) {
        Path path = new Path();
        path.addRoundRect(rectF,
                new float[]{
                        topLeftRadius_x - offset, topLeftRadius_y - offset,
                        topRightRadius_x - offset, topRightRadius_y - offset,
                        bottomRightRadius_x - offset, bottomRightRadius_y - offset,
                        bottomLeftRadius_x - offset, bottomLeftRadius_y - offset}, Path.Direction.CW);
        path.close();
        canvas.drawPath(path, paint);
    }
复制代码

2.5、根据 rectF,在 BitMap 中找到 与 rectF 的相似矩形 src

这部分代码较长,好好看下,先看下示例图
复制代码

最后一步

根据展示类型,修改图片截取区域。这部分就不解释了,很简单。
复制代码
   /**
     * 这里详细说一下,我们的目标就是在 bitmap 中找到一个 和 view 宽高比例相等的 一块矩形 
     * tempRect,然后截取出来 放到整个view中
     * tempRect 总是会存在
     *
     * @param bitmap bitmap
     * @param rw     绘制区域的宽度
     * @param rh     绘制区域的高度
     * @return 矩形
     */
    private Rect getSrc(@NonNull Bitmap bitmap, int rw, int rh) {
        //  bw bh,bitmap 的宽高
        //  vw vh,view 的宽高
        int bw = bitmap.getWidth();
        int bh = bitmap.getHeight();
    int left = 0, top = 0, right = 0, bottom = 0;

    //  判断 bw/bh 与 vw/vh
    int temp1 = bw * rh;
    int temp2 = rw * bh;

    //  相似矩形的宽高
    int[] tempRect = {bw, bh};

    <span class="hljs-keyword">if</span> (temp1 == temp2) {
        <span class="hljs-built_in">return</span> new Rect(0, 0, bw, bh);
    }
    //  tempRect 的宽度比 bw 小
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (temp1 &gt; temp2) {
        int tempBw = temp2 / rh;
        tempRect[0] = tempBw;
    }
    //  tempRect 的宽度比 bw 大
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (temp1 &lt; temp2) {
        int tempBh = temp1 / rw;
        tempRect[1] = tempBh;
    }

    //  tempRect 的宽度与 bw 的比值
    Boolean compare = bw &gt; tempRect[0];

    switch (styleType) {
        <span class="hljs-keyword">case</span> TOP:
            //  从上往下展示,我们这里的效果是不止从上往下,compare = <span class="hljs-literal">true</span>,还要居中
            left = compare ? (bw - tempRect[0]) / 2 : 0;
            top = 0;
            right = compare ? (bw + tempRect[0]) / 2 : tempRect[0];
            bottom = tempRect[1];
            <span class="hljs-built_in">break</span>;
        <span class="hljs-keyword">case</span> CENTER:
            //  居中
            left = compare ? (bw - tempRect[0]) / 2 : 0;
            top = compare ? 0 : (bh - tempRect[1]) / 2;
            right = compare ? (bw + tempRect[0]) / 2 : tempRect[0];
            bottom = compare ? tempRect[1] : (bh + tempRect[1]) / 2;
            <span class="hljs-built_in">break</span>;
        <span class="hljs-keyword">case</span> BOTTOM:
            left = compare ? (bw - tempRect[0]) / 2 : 0;
            top = compare ? 0 : bh - tempRect[1];
            right = compare ? (bw + tempRect[0]) / 2 : tempRect[0];
            bottom = compare ? tempRect[1] : bh;
            <span class="hljs-built_in">break</span>;
        <span class="hljs-keyword">case</span> FITXY:
            left = 0;
            top = 0;
            right = bw;
            bottom = bh;
            <span class="hljs-built_in">break</span>;
        default:
    }

    <span class="hljs-built_in">return</span> new Rect(left, top, right, bottom);
}
复制代码

总结

经过上面的步骤,我们在图片中找到了一块内容,这块内容所在的矩形大小 和 将要绘制到的区域矩形 是相似的。
比如我们找到的区域是 0,0,300,300,目标矩形是 150 X 150,那么就相当于把图片压缩2倍。
这个和 图片大小 300 X 300,ImageView 宽高 150 X 150 ,是一个意思。
复制代码
  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...