自定义控件之 圆形 / 圆角 ImageView

来源:互联网 发布:淘宝上回收电脑靠谱吗 编辑:程序博客网 时间:2024/06/11 02:48

自定义控件之 圆形 / 圆角 ImageView

一、问题在哪里?

问题来源于app开发中一个很常见的场景——用户头像要展示成圆的:

   

 

 二、怎么

机智的我,第一想法就是,切一张中间圆形透明、四周与底色相同、尺寸与头像相同的蒙板图片,盖在头像上不就完事了嘛,哈哈哈!

在背景纯色的前提下,这的确能简单解决问题,但是如果背景没有这么简单呢?

在这种不规则背景下,有两个问题:

1)  背景图常常是适应手机宽度缩放,而头像的尺寸又是固定宽高DP的,所以固定的蒙板图片是没法保证在不同机型上都和背景图案吻合的。

2)  在这种非纯色背景下,哪天想调整一下头像位置就得重新换图片蒙板,实在是太难维护了……

所以呢,既然头像图片肯定是方的,那就就让ImageView圆起来吧。

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5159126.html] 

三、开始干活

基本思路是,自定义一个ImageView,通过重写onDraw方法画出一个圆形的图片来:

复制代码
 1 public class ImageViewPlus extends ImageView{ 2     private Paint mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG); 3     private Bitmap mRawBitmap; 4     private BitmapShader mShader; 5     private Matrix mMatrix = new Matrix(); 6      7     public ImageViewPlus(Context context, AttributeSet attrs) { 8         super(context, attrs); 9     }10     11     @Override12     protected void onDraw(Canvas canvas) {13         Bitmap rawBitmap = getBitmap(getDrawable());14         if (rawBitmap != null){15             int viewWidth = getWidth();16             int viewHeight = getHeight();17             int viewMinSize = Math.min(viewWidth, viewHeight);18             float dstWidth = viewMinSize;19             float dstHeight = viewMinSize;20             if (mShader == null || !rawBitmap.equals(mRawBitmap)){21                 mRawBitmap = rawBitmap;22                 mShader = new BitmapShader(mRawBitmap, TileMode.CLAMP, TileMode.CLAMP);23             }24             if (mShader != null){25                 mMatrix.setScale(dstWidth / rawBitmap.getWidth(), dstHeight / rawBitmap.getHeight());26                 mShader.setLocalMatrix(mMatrix);27             }28             mPaintBitmap.setShader(mShader);29             float radius = viewMinSize / 2.0f;30             canvas.drawCircle(radius, radius, radius, mPaintBitmap);31         } else {32             super.onDraw(canvas);33         }34     }35 36     private Bitmap getBitmap(Drawable drawable){37         if (drawable instanceof BitmapDrawable){38             return ((BitmapDrawable)drawable).getBitmap();39         } else if (drawable instanceof ColorDrawable){40             Rect rect = drawable.getBounds();41             int width = rect.right - rect.left;42             int height = rect.bottom - rect.top;43             int color = ((ColorDrawable)drawable).getColor();44             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);45             Canvas canvas = new Canvas(bitmap);46             canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));47             return bitmap;48         } else {49             return null;50         }51     }52 }
复制代码

分析一下代码:

 canvas.drawCircle 决定了画出来的形状是圆形,而圆形的内容则是通过 mPaintBitmap.setShader 搞定的。

其中,BitmapShader需要设置Bitmap填充ImageView的方式(CLAMP:拉伸边缘, MIRROR:镜像, REPEAT:整图重复)。

这里其实设成什么不重要,因为我们实际需要的是将Bitmap按比例缩放成跟ImageView一样大,而不是预置的三种效果。

所以,别忘了 mMatrix.setScale  mShader.setLocalMatrix 一起用,将图片缩放一下。

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5159126.html] 

四、更多玩法 —— 支持边框

看下面的效果图,如果想给圆形的头像上加一个边框,该怎么搞呢?

      

复制代码
 1 public class ImageViewPlus extends ImageView{ 2     private Paint mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG); 3     private Paint mPaintBorder = new Paint(Paint.ANTI_ALIAS_FLAG); 4     private Bitmap mRawBitmap; 5     private BitmapShader mShader; 6     private Matrix mMatrix = new Matrix(); 7     private float mBorderWidth = dip2px(15); 8     private int mBorderColor = 0xFF0080FF; 9     10     public ImageViewPlus(Context context, AttributeSet attrs) {11         super(context, attrs);12     }13     14     @Override15     protected void onDraw(Canvas canvas) {16         Bitmap rawBitmap = getBitmap(getDrawable());17         if (rawBitmap != null){18             int viewWidth = getWidth();19             int viewHeight = getHeight();20             int viewMinSize = Math.min(viewWidth, viewHeight);21             float dstWidth = viewMinSize;22             float dstHeight = viewMinSize;23             if (mShader == null || !rawBitmap.equals(mRawBitmap)){24                 mRawBitmap = rawBitmap;25                 mShader = new BitmapShader(mRawBitmap, TileMode.CLAMP, TileMode.CLAMP);26             }27             if (mShader != null){28                 mMatrix.setScale((dstWidth - mBorderWidth * 2) / rawBitmap.getWidth(), (dstHeight - mBorderWidth * 2) / rawBitmap.getHeight());29                 mShader.setLocalMatrix(mMatrix);30             }31             mPaintBitmap.setShader(mShader);32             mPaintBorder.setStyle(Paint.Style.STROKE);33             mPaintBorder.setStrokeWidth(mBorderWidth);34             mPaintBorder.setColor(mBorderColor);35             float radius = viewMinSize / 2.0f;36             canvas.drawCircle(radius, radius, radius - mBorderWidth / 2.0f, mPaintBorder);37             canvas.translate(mBorderWidth, mBorderWidth);38             canvas.drawCircle(radius - mBorderWidth, radius - mBorderWidth, radius - mBorderWidth, mPaintBitmap);39         } else {40             super.onDraw(canvas);41         }42     }43 44     private Bitmap getBitmap(Drawable drawable){45         if (drawable instanceof BitmapDrawable){46             return ((BitmapDrawable)drawable).getBitmap();47         } else if (drawable instanceof ColorDrawable){48             Rect rect = drawable.getBounds();49             int width = rect.right - rect.left;50             int height = rect.bottom - rect.top;51             int color = ((ColorDrawable)drawable).getColor();52             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);53             Canvas canvas = new Canvas(bitmap);54             canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));55             return bitmap;56         } else {57             return null;58         }59     }60     61     private int dip2px(int dipVal)62     {63         float scale = getResources().getDisplayMetrics().density;64         return (int)(dipVal * scale + 0.5f);65     }66 }
复制代码

看代码中,加边框实际上就是用实心纯色的 Paint 画了一个圆边,在此基础上画上原来的头像即可。

需要的注意的地方有三个:

1)  圆框的半径不是 radius ,而应该是 radius - mBorderWidth / 2.0f 。想象着拿着笔去画线,线其实是画在右图中白色圈的位置,只不过它很粗。

2)  在ImageView大小不变的基础上,头像的实际大小要比没有边框的时候小了,所以 mMatrix.setScale 的时候要把边框的宽度去掉。

3)  画头像Bitmap的时候不能直接 canvas.drawCircle(radius, radius, radius - mBorderWidth, mPaintBitmap) ,这样你会发现头像的右侧和下方边缘被拉伸了(右图)

     为什么呢?因为 Paint 默认是以左上角为基准开始绘制的,此时头像的实际区域是右图中的红框,而超过红框的部分(圆形的右侧和下方),自然被 TileMode.CLAMP效果沿边缘拉伸了。

     所以,需要通过挪动坐标系的位置和调整圆心,才能把头像画在正确的区域(右图绿框)中。

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5159126.html] 

五、更多玩法 —— 支持xml配置

既然有了边框,那如果想配置边框的宽度和颜色该如何是好呢?

基本上两个思路:

1)  给ImageViewPlus加上set接口,设置完成之后通过 invalidate(); 重绘一下即可;

2)  在xml里就支持配置一些自定义属性,这样用起来会方便很多。

这里重点说一下支持xml配置自定义属性。

自定义控件要支持xml配置自定义属性的话,首先需要在 \res\values 里去定义属性: 

复制代码
 1 <?xml version="1.0" encoding="utf-8"?>   2 <resources>   3     <attr name="borderColor" format="color" /> 4     <attr name="borderWidth" format="dimension" /> 5  6     <declare-styleable name="ImageViewPlus">   7         <attr name="borderColor" /> 8         <attr name="borderWidth" /> 9     </declare-styleable>  10 </resources>  
复制代码

 然后在ImageViewPlus的构造函数中去读取这些自定义属性:

复制代码
 1     private static final int DEFAULT_BORDER_COLOR = Color.TRANSPARENT; 2     private static final int DEFAULT_BORDER_WIDTH = 0; 3      4     public ImageViewPlus(Context context, AttributeSet attrs) { 5         super(context, attrs); 6         //取xml文件中设定的参数 7         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ImageViewPlus); 8         mBorderColor = ta.getColor(R.styleable.ImageViewPlus_borderColor, DEFAULT_BORDER_COLOR); 9         mBorderWidth = ta.getDimensionPixelSize(R.styleable.ImageViewPlus_borderWidth, dip2px(DEFAULT_BORDER_WIDTH));10         ta.recycle();11     }
复制代码

 在xml布局中使用自定义属性:

复制代码
 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2     xmlns:tools="http://schemas.android.com/tools" 3     xmlns:snser="http://schemas.android.com/apk/res/cc.snser.imageviewplus" 4     android:layout_width="match_parent" 5     android:layout_height="match_parent" 6     android:background="@drawable/wallpaper" 7     android:orientation="vertical" 8     tools:context="${relativePackage}.${activityClass}" > 9     10     <cc.snser.imageviewplus.ImageViewPlus11         android:id="@+id/imgplus"12         android:layout_width="200dp"13         android:layout_height="300dp"14         android:layout_marginBottom="50dp"15         android:layout_centerHorizontal="true"16         android:layout_alignParentBottom="true"17         android:src="@drawable/img_square"18         snser:borderColor="#FF0080FF"19         snser:borderWidth="15dp" />20     21 </RelativeLayout>
复制代码

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5159126.html] 

六、更多玩法 —— 圆角ImageView

搞定了圆形ImageView以及对应的边框,那如何实现下面这种圆角的ImageView呢?

 

其实原理上一样,把 canvas.drawCircle 对应改成 canvas.drawRoundRect 就OK了,直接贴代码吧:

复制代码
  1 public class ImageViewPlus extends ImageView{  2     /**  3      * android.widget.ImageView  4      */  5     public static final int TYPE_NONE = 0;  6     /**  7      * 圆形  8      */  9     public static final int TYPE_CIRCLE = 1; 10     /** 11      * 圆角矩形 12      */ 13     public static final int TYPE_ROUNDED_RECT = 2;     14      15     private static final int DEFAULT_TYPE = TYPE_NONE; 16     private static final int DEFAULT_BORDER_COLOR = Color.TRANSPARENT; 17     private static final int DEFAULT_BORDER_WIDTH = 0; 18     private static final int DEFAULT_RECT_ROUND_RADIUS = 0; 19      20     private int mType; 21     private int mBorderColor; 22     private int mBorderWidth; 23     private int mRectRoundRadius; 24      25     private Paint mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG); 26     private Paint mPaintBorder = new Paint(Paint.ANTI_ALIAS_FLAG); 27      28     private RectF mRectBorder = new RectF(); 29     private RectF mRectBitmap = new RectF(); 30      31     private Bitmap mRawBitmap; 32     private BitmapShader mShader; 33     private Matrix mMatrix = new Matrix(); 34      35     public ImageViewPlus(Context context, AttributeSet attrs) { 36         super(context, attrs); 37         //取xml文件中设定的参数 38         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ImageViewPlus); 39         mType = ta.getInt(R.styleable.ImageViewPlus_type, DEFAULT_TYPE); 40         mBorderColor = ta.getColor(R.styleable.ImageViewPlus_borderColor, DEFAULT_BORDER_COLOR); 41         mBorderWidth = ta.getDimensionPixelSize(R.styleable.ImageViewPlus_borderWidth, dip2px(DEFAULT_BORDER_WIDTH)); 42         mRectRoundRadius = ta.getDimensionPixelSize(R.styleable.ImageViewPlus_rectRoundRadius, dip2px(DEFAULT_RECT_ROUND_RADIUS)); 43         ta.recycle(); 44     } 45      46     @Override 47     protected void onDraw(Canvas canvas) { 48         Bitmap rawBitmap = getBitmap(getDrawable()); 49          50         if (rawBitmap != null && mType != TYPE_NONE){ 51             int viewWidth = getWidth(); 52             int viewHeight = getHeight(); 53             int viewMinSize = Math.min(viewWidth, viewHeight); 54             float dstWidth = mType == TYPE_CIRCLE ? viewMinSize : viewWidth; 55             float dstHeight = mType == TYPE_CIRCLE ? viewMinSize : viewHeight; 56             float halfBorderWidth = mBorderWidth / 2.0f; 57             float doubleBorderWidth = mBorderWidth * 2; 58              59             if (mShader == null || !rawBitmap.equals(mRawBitmap)){ 60                 mRawBitmap = rawBitmap; 61                 mShader = new BitmapShader(mRawBitmap, TileMode.CLAMP, TileMode.CLAMP); 62             } 63             if (mShader != null){ 64                 mMatrix.setScale((dstWidth - doubleBorderWidth) / rawBitmap.getWidth(), (dstHeight - doubleBorderWidth) / rawBitmap.getHeight()); 65                 mShader.setLocalMatrix(mMatrix); 66             } 67              68             mPaintBitmap.setShader(mShader); 69             mPaintBorder.setStyle(Paint.Style.STROKE); 70             mPaintBorder.setStrokeWidth(mBorderWidth); 71             mPaintBorder.setColor(mBorderWidth > 0 ? mBorderColor : Color.TRANSPARENT); 72              73             if (mType == TYPE_CIRCLE){ 74                 float radius = viewMinSize / 2.0f; 75                 canvas.drawCircle(radius, radius, radius - halfBorderWidth, mPaintBorder); 76                 canvas.translate(mBorderWidth, mBorderWidth); 77                 canvas.drawCircle(radius - mBorderWidth, radius - mBorderWidth, radius - mBorderWidth, mPaintBitmap); 78             } else if (mType == TYPE_ROUNDED_RECT){ 79                 mRectBorder.set(halfBorderWidth, halfBorderWidth, dstWidth - halfBorderWidth, dstHeight - halfBorderWidth); 80                 mRectBitmap.set(0.0f, 0.0f, dstWidth - doubleBorderWidth, dstHeight - doubleBorderWidth); 81                 float borderRadius = mRectRoundRadius - halfBorderWidth > 0.0f ? mRectRoundRadius - halfBorderWidth : 0.0f; 82                 float bitmapRadius = mRectRoundRadius - mBorderWidth > 0.0f ? mRectRoundRadius - mBorderWidth : 0.0f; 83                 canvas.drawRoundRect(mRectBorder, borderRadius, borderRadius, mPaintBorder); 84                 canvas.translate(mBorderWidth, mBorderWidth); 85                 canvas.drawRoundRect(mRectBitmap, bitmapRadius, bitmapRadius, mPaintBitmap); 86             } 87         } else { 88             super.onDraw(canvas); 89         } 90     } 91  92     private int dip2px(int dipVal) 93     { 94         float scale = getResources().getDisplayMetrics().density; 95         return (int)(dipVal * scale + 0.5f); 96     } 97      98     private Bitmap getBitmap(Drawable drawable){ 99         if (drawable instanceof BitmapDrawable){100             return ((BitmapDrawable)drawable).getBitmap();101         } else if (drawable instanceof ColorDrawable){102             Rect rect = drawable.getBounds();103             int width = rect.right - rect.left;104             int height = rect.bottom - rect.top;105             int color = ((ColorDrawable)drawable).getColor();106             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);107             Canvas canvas = new Canvas(bitmap);108             canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));109             return bitmap;110         } else {111             return null;112         }113     }114 }
复制代码
复制代码
 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2     xmlns:tools="http://schemas.android.com/tools" 3     xmlns:snser="http://schemas.android.com/apk/res/cc.snser.imageviewplus" 4     android:layout_width="match_parent" 5     android:layout_height="match_parent" 6     android:background="@drawable/wallpaper" 7     android:orientation="vertical" 8     tools:context="${relativePackage}.${activityClass}" > 9     10     <cc.snser.imageviewplus.ImageViewPlus11         android:id="@+id/imgplus"12         android:layout_width="200dp"13         android:layout_height="300dp"14         android:layout_marginBottom="50dp"15         android:layout_centerHorizontal="true"16         android:layout_alignParentBottom="true"17         android:src="@drawable/img_rectangle"18         snser:type="rounded_rect"19         snser:borderColor="#FF0080FF"20         snser:borderWidth="10dp"21         snser:rectRoundRadius="30dp" />22     23 </RelativeLayout>
复制代码
复制代码
 1 <?xml version="1.0" encoding="utf-8"?>   2 <resources>   3     <attr name="type">   4         <enum name="none" value="0" />   5         <enum name="circle" value="1" />   6         <enum name="rounded_rect" value="2" /> 7     </attr> 8     <attr name="borderColor" format="color" /> 9     <attr name="borderWidth" format="dimension" />10     <attr name="rectRoundRadius" format="dimension" />11 12     <declare-styleable name="ImageViewPlus">  13         <attr name="type" />14         <attr name="borderColor" />15         <attr name="borderWidth" />16         <attr name="rectRoundRadius" />17     </declare-styleable>18 </resources>  
复制代码

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5159126.html] 

七、Demo源码

保存下面的图片,扩展名改成zip后解压即可。

 

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5159126.html] 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 牙侧面有小小洞怎么办 喝酒喝的手抖怎么办 孩子在幼儿园不爱说话怎么办 孕妇吃了方头鱼怎么办 油炸的东西软了怎么办 跑步小腿疼怎么办 知乎 健身后小腿粗了怎么办 背心式内衣大了怎么办 运动文胸买大了怎么办 喝碳酸饮料胖了怎么办 奥鹏学费迟交了怎么办 侧乌鸦做不起来怎么办 发烧很难受怎么办 知乎 上班后奶水越来越少怎么办 孕晚期胖的厉害怎么办 怀孕后猛长胖怎么办啊 怀孕坐了按摩椅怎么办 怀孕8个月胎位不正怎么办 缓刑期间被打了怎么办 非法集资人跑了怎么办 玩游戏恶心想吐怎么办 中奖了填了信息怎么办 穿开衫老是溜肩怎么办 开车开久了腰疼怎么办 西药吃了胃难受怎么办 铁海棠叶子变黄怎么办 四季海棠根烂了怎么办 水银弄到皮肤上怎么办 头条指数下降了怎么办 下面长泡泡破了怎么办 期望工资说低了怎么办 机票买了后降价怎么办 刚谈对象没话说怎么办 微信好友太少怎么办 qq头像不显示了怎么办 找借口不出去玩怎么办 老婆不花我的钱怎么办 家里养兔子很臭怎么办 养兔子家里好臭怎么办 昌硕辞职不批怎么办 昌硕怎么办自离手续