Android自定义圆角圆形图片

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/binbinqq86/article/details/79463977

转载请注明出处:http://blog.csdn.net/binbinqq86/article/details/79463977

说起Android里面的自定义圆角圆形图片,已经算是老生常谈的话题了,之前一直使用别人的,最近使用的时候发现自己居然没有一个这样属于自己的工具库,实在遗憾,毕竟还是自己的东西用起来最顺手,所以就打造了一个,先来看看效果:
这里写图片描述
怎么样,还不错吧~支持各种图案,边框,各种圆角。其中原理也是很简单的,无非就是canvas知识的应用。接下来我们一个一个形状来看,首先是圆形,这个应该是最简单的了,直接使用canvas的drawCircle来绘制一个圆形就搞定了,看代码:

float r = hasBorder ? width / 2f - borderWidth : width / 2f;
canvas.drawCircle(width / 2f, height / 2f, r, mPaintDrawable);

其中的r就是圆的半径,这里我们用一个变量hasBorder 来区分是否绘制边框(边框后面细说),drawCircle的前两个参数就是圆心坐标,最后一个参数则是画笔,我们的图片呢???确定这样就能画出来圆形图片???其实图片被我们封装在笔刷里面了:BitmapShader。这个类的使用很简单,官方文档是这样解释的:

Shader used to draw a bitmap as a texture.

就是使用特定的图片来作为纹理使用。这里就不再详细解释这个类的使用了,不懂的同学可以参考文章最后的链接。其实除了使用BitmapShader还有另外一种方案,就是PorterDuffXfermode,感兴趣的可以参考我之前的一篇文章:http://blog.csdn.net/binbinqq86/article/details/78329238,里面讲述了另外一种实现圆形图片的方案。

下面来看一下获取Bitmap的方法:

/**
     * 获取imageview设置的图片(针对大图,此时必须已经处理过了,否则会造成内存溢出)
     * <p>
     * 获取bitmap:
     * 1、如果设置了src为图片则返回该图片,
     * 2、如果设置了src为颜色值则返回颜色值,
     * 3、如果没有设置src,则返回默认颜色值(未设置则为透明)
     *
     * @param drawable
     * @return
     */
    private Bitmap getBitmap(Drawable drawable) {
        Bitmap bitmap = null;
        if (drawable instanceof BitmapDrawable) {
            bitmap = ((BitmapDrawable) drawable).getBitmap();
        } else if (drawable instanceof ColorDrawable) {
            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(bitmap);
            int color = ((ColorDrawable) drawable).getColor();
            c.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));
        } else {
            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(bitmap);
            c.drawARGB(Color.alpha(defaultColor), Color.red(defaultColor), Color.green(defaultColor), Color.blue(defaultColor));
        }
        if (isBlur) {
            //高斯模糊
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                try {
                    bitmap = RSBlur.blur(getContext(), bitmap, (int) blurRadius);
                } catch (Exception e) {
                    bitmap = FastBlur.blur(bitmap, (int) blurRadius, true);
                }
            } else {
                bitmap = FastBlur.blur(bitmap, (int) blurRadius, true);
            }
        }
        return bitmap;
    }

这里就是获取imageview设置的图片,然后就可以随意绘制我们想要的效果了,这里我们取图片的中间区域进行绘制,可以防止图片跟视图大小不一样(前提是我们已经根据imageView的宽高去缩放过图片了,这里处理的只是绘制部分,类似imageView的centerCrop),看代码:

/**
     * 处理图片大于或者小于控件的情况
     * (不是针对大图内存溢出的处理,此处的处理只是为了让图片居中绘制——centerCrop:参照imageView的处理)
     *
     * @param bitmap
     */
    private void setUpShader(Bitmap bitmap) {
        mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        int dWidth = bitmap.getWidth();
        int dHeight = bitmap.getHeight();

        int vWidth = width;
        int vHeight = height;
        if (hasBorder) {
            vWidth -= 2 * borderWidth;
            vHeight -= 2 * borderWidth;
        }
        if (dWidth == vWidth && dHeight == vHeight) {

        } else {
            float scale = 1.0f;
            float dx = 0, dy = 0;

            if (dWidth * vHeight > vWidth * dHeight) {
                scale = (float) vHeight / (float) dHeight;
                dx = (vWidth - dWidth * scale) * 0.5f;
            } else {
                scale = (float) vWidth / (float) dWidth;
                dy = (vHeight - dHeight * scale) * 0.5f;
            }

            mMatrix.setScale(scale, scale);
            if (hasBorder) {//有边框的情况,view视图缩小了,所以需要对图片移动一个边框宽度的处理
                dx += borderWidth;
                dy += borderWidth;
            }
            //上一个缩放操作完成之后,进行移动(把图片中心与视图中心对应,这样保证图片居中,而原来图片是左上角对应视图左上角),与pre对应

            mMatrix.postTranslate(dx, dy);

            mBitmapShader.setLocalMatrix(mMatrix);
        }

        mPaintDrawable.setShader(mBitmapShader);
    }

里面的主要逻辑就是用matrix去设置BitmapShader的缩放效果,这里我们参照的是imageView的源码:

    private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }

        final int dwidth = mDrawableWidth;
        final int dheight = mDrawableHeight;

        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);

        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* If the drawable has no intrinsic size, or we're told to
                scaletofit, then we just fill our entire view.
            */
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            mDrawable.setBounds(0, 0, dwidth, dheight);

            if (ScaleType.MATRIX == mScaleType) {
                // Use the specified matrix as-is.
                //省略...
            } else if (fits) {
                // The bitmap fits exactly, no transform needed.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) {
                // Center bitmap in view, no scaling.
                //省略...
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix;

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
            } else if (ScaleType.CENTER_INSIDE == mScaleType) {

                //省略...
            } else {
                // Generate the required transform.

                //省略...scaleTypeToScaleToFit(mScaleType));
            }
        }
    }

矩阵的变换可以参考文章最后的链接,矩阵post,pre,set我在上面的注释也写了,基本上就是set为直接变换,pre和post分别是变换之前和变换之后再进行后续的变换。

至此,bitmap的准备工作就完成了,我们就可以调用canvas的drawCircle来绘制圆形图片了。上面提到了边框的绘制,这里我们就来说一下,其实原理是一样的,无非就是把画笔paint设置为STOKE模式,并且在计算Rect的四个点的坐标的时候边框和内容要注意一下,可以把边框设置为半透明模式,看看图片是否绘制到边框下面了,如果两者边缘正好贴合,则说明半径或者矩形计算的正确,否则就是计算有误。另外一点就是图片的画笔和边框的最好区分开来,这样各司其职,否则就比较混乱。边框的绘制还有一点需要注意的就是,它的半径是圆心到边框宽度的中间,而不是边缘,因为是空心的。

下面就是圆角图片了,这个则是调用canvas的drawRoundRect方法,首先第一个参数就是一个矩形,他是圆角的外接矩形,然后就是圆角的x,y半径了,最后一个参数是画笔。圆角的情况一共可以分为15种,四个角全部是圆的,另外就是单个圆角的组合,这里我才用了枚举类型:

 /**
     * 圆角的类型
     * 顺时针1234四个角,四个角可分为1,2,3,4,12,13,14,23,24,34,123,124,134,234,1234十五种情况
     */
    public enum CornerType {
        ALL,
        TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT,
        TOP_LEFT_TOP_RIGHT, TOP_LEFT_BOTTOM_RIGHT, TOP_LEFT_BOTTOM_LEFT,
        TOP_RIGHT_BOTTOM_RIGHT, TOP_RIGHT_BOTTOM_LEFT, BOTTOM_RIGHT_BOTTOM_LEFT,
        TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT, TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT, TOP_LEFT_BOTTOM_RIGHT_BOTTOM_LEFT, TOP_RIGHT_BOTTOM_RIGHT_BOTTOM_LEFT
    }

而这些参数都可以在xml属性中配置,也可以通过代码去set具体的type。下面是具体绘制代码:

                case ALL:
                    if (hasBorder) {
                        //Math.ceil进位来保证不留白,扩大一点绘制区域
                        RectF rf = new RectF(borderWidth, borderWidth, (float) Math.ceil(width - borderWidth), (float) Math.ceil(height - borderWidth));

                        canvas.drawRoundRect(rf, cr, cr, mPaintDrawable);
                    } else {
                        RectF rf = new RectF(0, 0, width, height);
                        //corner为圆心到边缘的距离
                        canvas.drawRoundRect(rf, cornerRadius, cornerRadius, mPaintDrawable);
                    }
                    break;
                case TOP_LEFT:
                    //分块绘制,也可以采用path来绘制
                    if (hasBorder) {
//                        path.reset();
//                        path.moveTo(borderWidth,borderWidth+cr+borderWidth);
//                        path.addArc(new RectF(borderWidth, borderWidth, cr * 2f + borderWidth, cr * 2f + borderWidth), 180, 90);
//                        path.lineTo(width-borderWidth,borderWidth);
//                        path.lineTo(width-borderWidth,height-borderWidth);
//                        path.lineTo(borderWidth,height-borderWidth);
//                        path.close();
//                        canvas.drawPath(path,mPaintDrawable);

//                        canvas.drawRoundRect(new RectF(borderWidth, borderWidth, cr * 2f+borderWidth, cr * 2f+borderWidth),cr,cr,mPaintDrawable);
                        canvas.drawArc(new RectF(borderWidth, borderWidth, cornerRadius * 2f - borderWidth, cr * 2f + borderWidth), 180, 90, true, mPaintDrawable);
                        canvas.drawRect(new RectF(borderWidth, cornerRadius, cornerRadius, height - borderWidth), mPaintDrawable);
                        canvas.drawRect(new RectF(cornerRadius, borderWidth, width - borderWidth, height - borderWidth), mPaintDrawable);
                    } else {
                        //drawRoundRect也可以,不过多绘制了一部分圆
                        canvas.drawRoundRect(new RectF(0, 0, cornerRadius * 2f, cornerRadius * 2f), cornerRadius, cornerRadius, mPaintDrawable);
                        canvas.drawRect(new RectF(0, cornerRadius, cornerRadius, height), mPaintDrawable);
                        canvas.drawRect(new RectF(cornerRadius, 0, width, height), mPaintDrawable);
                    }
                    break;

这里我只列举了两个情况:全部圆角和左上角圆角,可以看到我们绘制的方案可以多种,单个圆角组合的情况其实就是把图片拆分为不同区块,然后连接起来去绘制,原理就是这么简单!既然可以用path去绘制各种各样的图形,那么我们是不是可以绘制一些特殊形状呢,比如五角星,小熊,三角形,五边形,六边形。。。等等,自己可以随意去扩展了,这里我用了一个OtherType来表示特殊形状:

/**
     * 其他类型,如五角星,小熊,六边形等等不规则的
     */
    public enum OtherType {
        STAR, BEAR, HEXAGON
    }

具体使用的时候可以自己根据具体情况去扩展。这里我只列出了六边形:

path.reset();
path.moveTo(width * 0.25f, 0);
path.lineTo(width * 0.75f, 0);
path.lineTo(width, height * 0.5f);
path.lineTo(width * 0.75f, height);
path.lineTo(width * 0.25f, height);
path.lineTo(0, height * 0.5f);
path.close();
canvas.drawPath(path, mPaintDrawable);

可以看到用到了一些数学知识,尤其是当你画五角星的时候,这些特殊形状基本上都是数学知识的运用和path的api的基本使用。

最后就是状态的保存与恢复了,防止我们的自定义view出现异常,核心思想就是保存我们设置的属性,然后在恢复的时候去重新绘制就可以恢复销毁之前的原状了。

@Nullable
    @Override
    protected Parcelable onSaveInstanceState() {
        super.onSaveInstanceState();
        //状态保存
        Bundle bundle = new Bundle();
        bundle.putBoolean("hasBorder", hasBorder);
        bundle.putBoolean("isCircle", isCircle);
        bundle.putBoolean("isBlur", isBlur);
        bundle.putBoolean("isOval", isOval);

        bundle.putFloat("cornerRadius", cornerRadius);
        bundle.putFloat("borderWidth", borderWidth);
        bundle.putFloat("blurRadius", blurRadius);

        bundle.putInt("borderColor", borderColor);
        bundle.putInt("defaultColor", defaultColor);

        bundle.putSerializable("otherType", otherType);
        bundle.putSerializable("cornerType", cornerType);
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        //状态恢复
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            setHasBorder(bundle.getBoolean("hasBorder"));
            setCircle(bundle.getBoolean("isCircle"));
            setBlur(bundle.getBoolean("isBlur"));
            setOval(bundle.getBoolean("isOval"));

            setCornerRadius(bundle.getFloat("cornerRadius"));
            setBorderWidth(bundle.getFloat("borderWidth"));
            setBlurRadius(bundle.getFloat("blurRadius"));

            setBorderColor(bundle.getInt("borderColor"));
            setDefaultColor(bundle.getInt("defaultColor"));

            setOtherType((OtherType) bundle.getSerializable("otherType"));
            setCornerType((CornerType) bundle.getSerializable("cornerType"));
        }
        reDraw();
    }

最后就是自定义属性了:

<declare-styleable name="baselib_BaseImageView">
        <!--其他一切不规则图案-->
        <attr name="baselib_other_type">
            <enum name="STAR" value="1"/>
            <enum name="BEAR" value="2"/>
            <enum name="HEXAGON" value="3"/>
        </attr>
        <!--顺时针1234四个角,四个角可分为1,2,3,4,12,13,14,23,24,34,123,124,134,234,1234十五种情况-->
        <attr name="baselib_corner_type">
            <enum name="ALL" value="1234"/>
            <enum name="TOP_LEFT" value="1"/>
            <enum name="TOP_RIGHT" value="2"/>
            <enum name="BOTTOM_RIGHT" value="3"/>
            <enum name="BOTTOM_LEFT" value="4"/>
            <enum name="TOP_LEFT_TOP_RIGHT" value="12"/>
            <enum name="TOP_LEFT_BOTTOM_RIGHT" value="13"/>
            <enum name="TOP_LEFT_BOTTOM_LEFT" value="14"/>
            <enum name="TOP_RIGHT_BOTTOM_RIGHT" value="23"/>
            <enum name="TOP_RIGHT_BOTTOM_LEFT" value="24"/>
            <enum name="BOTTOM_RIGHT_BOTTOM_LEFT" value="34"/>
            <enum name="TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT" value="123"/>
            <enum name="TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT" value="124"/>
            <enum name="TOP_LEFT_BOTTOM_RIGHT_BOTTOM_LEFT" value="134"/>
            <enum name="TOP_RIGHT_BOTTOM_RIGHT_BOTTOM_LEFT" value="234"/>
        </attr>
        <!--椭圆-->
        <attr name="baselib_is_oval" format="boolean"/>
        <!--是否是圆形图片-->
        <attr name="baselib_is_circle" format="boolean"/>

        <!--=====================上面是互斥的属性,下面的可以与上面的共存=====================-->
        <!--是否带边框-->
        <attr name="baselib_has_border" format="boolean"/>
        <!--边框颜色-->
        <attr name="baselib_border_color" format="color|reference"/>
        <!--边框宽度-->
        <attr name="baselib_border_width" format="dimension"/>
        <!--圆角的度数(代表所有角,不再单独针对部分圆角的情况提供某一个角的度数)-->
        <attr name="baselib_corner_radius" format="dimension"/>
        <!--是否高斯模糊-->
        <attr name="baselib_is_blur" format="boolean"/>
        <!--高斯模糊半径-->
        <attr name="baselib_blur_radius" format="float"/>
        <!--当图片为空的时候,默认颜色-->
        <attr name="baselib_default_color" format="color|reference"/>
    </declare-styleable>

所有属性都可以通过代码set和get,而且可以自己扩展,相信这个imageview基本上满足日常开发了,最后如果使用glide加载图片的话,在getBitmap方法里面需要加上一种情况:

else if (drawable instanceof GlideBitmapDrawable) {
            bitmap = ((GlideBitmapDrawable) drawable).getBitmap();
        } 

到此整个imageview就讲解完了,老规矩,有疑问的同学可以在下方留言,最后放出源码:

源码下载

参考:

猜你喜欢

转载自blog.csdn.net/binbinqq86/article/details/79463977