zoukankan      html  css  js  c++  java
  • 自定义控件之 圆形 / 圆角 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     @Override
    12     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     @Override
    15     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 }
    View Code

    看代码中,加边框实际上就是用实心纯色的 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配置自定义属性的话,首先需要在  esvalues 里去定义属性: 

     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>  
    View attrs_imageviewplus.xml

     然后在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     }
    View Code

     在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.ImageViewPlus
    11         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>
    View Code

    [转载请保留本文地址: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 }
    View ImageViewPlus.java
     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.ImageViewPlus
    11         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>
    View layout
     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>  
    View attrs_imageviewplus.xml

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

    七、Demo源码

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

     

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

     

  • 相关阅读:
    python实例
    date命令
    unbuntu禁用ipv6
    Oracle学习(一)
    深入浅出区块链笔记
    sqlserver索引
    Go学习(16):网络编程
    Go学习(15):并发与包
    Go学习(14):defer
    Go学习(13):异常
  • 原文地址:https://www.cnblogs.com/snser/p/5159126.html
Copyright © 2011-2022 走看看