Android自定义圆角圆形图片
2018-03-07 11:44
417 查看
转载请注明出处: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就讲解完了,老规矩,有疑问的同学可以在下方留言,最后放出源码:
源码下载
参考:https://www.jianshu.com/p/94093d8b168a
http://blog.csdn.net/programchangesworld/article/details/49078387
相关文章推荐
- Android自定义ImageView实现图片圆形 ,椭圆和矩形圆角显示
- android 自定义圆形图片与图片圆角
- Android自定义圆形和圆角图片
- Android自定义控件之自定义圆形图片和圆角图片
- Android自定义view实现圆形、圆角和椭圆图片(BitmapShader图形渲染)
- Android自定义View学习之圆角图片(圆形图片)
- Android 图片圆角,自定义圆角的弧度,或者直接设置为圆形图片
- Android 使用自定义Drawable 设置圆角矩形或者圆形图片
- Android 自定义 RoundImageView 实现圆角矩形或圆形图片
- Android自定义ImageView实现图片圆形 ,椭圆和矩形圆角显示
- [AndroidUI]自定义view(四):实现圆形圆角图片
- Android Drawable 自定义(圆角图片和圆形图片)
- Android imageView 自定义单个或多个角为圆角及圆形图片工具
- Android 自定义View修炼-Android实现圆形、圆角和椭圆自定义图片View(使用BitmapShader图形渲染方法)
- Android自定义圆形圆角图片示例
- Android 自定义Drawable 实现圆角圆形图片
- Android自定义ImageView实现图片圆形 ,椭圆和矩形圆角显示
- Android自定义ImageView实现图片缩放滑动,双击放大缩小,多点触控旋转,圆角矩形,圆形和仿刮刮卡效果
- Android使用BitmapShader图形渲染实现圆形、圆角和椭圆自定义图片View
- Android 自定义Drawable 实现圆角矩形和圆形图片