zoukankan      html  css  js  c++  java
  • 自定义圆形头像CircleImageView的使用和源码分析

    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0806/3268.html

    1.     tools:context="com.example.circleimageviewdemo_1.MainActivity" >
    2.  
    3.     <RelativeLayout
    4.         android:layout_width="match_parent"
    5.         android:layout_height="0dp"
    6.         android:layout_weight="1"
    7.         android:padding="@dimen/base_padding"
    8.         android:background="@color/light">
    9.  
    10.         <com.example.library.CircleImageView
    11.             android:layout_width="160dp"
    12.             android:layout_height="160dp"
    13.             android:layout_centerInParent="true"
    14.             android:src="@drawable/hugh"
    15.             app:border_width="2dp"
    16.             app:border_color="@color/dark" />
    17.  
    18.     </RelativeLayout>
    19.  
    20.     <RelativeLayout
    21.         android:layout_width="match_parent"
    22.         android:layout_height="0dp"
    23.         android:layout_weight="1"
    24.         android:padding="@dimen/base_padding"
    25.         android:background="@color/dark">
    26.  
    27.         <com.example.library.CircleImageView
    28.             android:layout_width="160dp"
    29.             android:layout_height="160dp"
    30.             android:layout_centerInParent="true"
    31.             android:src="@drawable/hugh"
    32.             app:border_width="2dp"
    33.             app:border_color="@color/light" />
    34.  
    35.     </RelativeLayout>
    36.  
    37. </LinearLayout>

    注意两点:

    • 在xml布局文件中,声明我们的自定义View,路径要正确。 
      com.example.library.CircleImageView

    • 一定要引入命名空间,xmlns:app=”http://schemas.android.com/apk/res-auto”,不然无法使用自定义属性。

    MainActivity里不用修改,因为我直接在XML里面用src设置了图片,你也可以在MainActivity中用setImageXXX设置。直接运行看效果。 

    blob.png

    总结下CircleImageView的使用:

    1. 将CircleImageView.java拷贝到项目工程中

    2. attrs.xml拷贝到res/values/目录下

    3. 在布局文件中声明自定义View,一定要引入命名空间xmlns:app=”http://schemas.android.com/apk/res-auto”。

    Demo下载: 
    http://download.csdn.net/detail/zhoubin1992/8956647


    CircleImageView源码分析

    一直很好奇这个开源库是怎么自定义圆形ImageView,结果最近花了很多功夫进行分析,现在也非常迫切想整理出来,希望能帮到一些人。我也看了网络上的一些源码分析,有些分析是错误的会误导大家,有些说的不详细还是无法理解。 
    首先我先总结下CircleImageView的主要流程,让大家有个整体把握。然后再一步步进入源码分析。

    CircleImageView的主要流程:

    1. 首先通过setImageXxx()方法设置图片Bitmap; 
    2. 进入构造函数CircleImageView()获取自定义参数,以及调用setup()函数; 
    3. 进入setup()函数(非常关键),进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式和内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数; 
    4. 进入updateShaderMatrix()函数,计算缩放比例和平移,设置BitmapShader的Matrix参数等; 
    5. 触发ondraw()函数完成最终的绘制。使用配置好的Paint先画出绘制内圆形来以后再画边界圆形。


    下面来详细分析一下源码: 
    1. 首先从项目里调用CircleImageView开始分析,我们要么在XML里面用src,要么调用CircleImageView的setImageXXX()方法设置图片。那我们的源码的运行入口在哪里呢,是从构造函数CircleImageView()开始呢还是从setImageXXX()开始?一开始就卡壳了。一开始我以为是从构造函数CircleImageView()开始跑,结果分析下来发现并不会进入setup();所以这是行不通的,那接下来就要论证是不是从setImageXXX()开始呢?我的方法是分别在两者进行System.out.println测试,看看谁先执行。测试结果会发现是从setImageXXX()开始。 
    blob.png

    那我们看下源码:

    1. /**
    2.      * 以下四个函数都是
    3.      * 复写ImageView的setImageXxx()方法 
    4.      * 注意这个函数先于构造函数调用之前调用
    5.      * @param bm
    6.      */
    7.     @Override
    8.     public void setImageBitmap(Bitmap bm) {
    9.         super.setImageBitmap(bm);
    10.         mBitmap = bm;
    11.         setup();
    12.     }
    13.  
    14.     @Override
    15.     public void setImageDrawable(Drawable drawable) {
    16.         super.setImageDrawable(drawable);
    17.         mBitmap = getBitmapFromDrawable(drawable);
    18.         System.out.println("setImageDrawable -- setup");  
    19.         setup();
    20.     }
    21.  
    22.     @Override
    23.     public void setImageResource(@DrawableRes int resId) {
    24.         super.setImageResource(resId);
    25.         mBitmap = getBitmapFromDrawable(getDrawable());
    26.         setup();
    27.     }
    28.  
    29.     @Override
    30.     public void setImageURI(Uri uri) {
    31.         super.setImageURI(uri);
    32.         mBitmap = getBitmapFromDrawable(getDrawable());
    33.         setup();
    34.     }

    看上面代码会发现,四个函数都是获取图片Bitmap,调用setup()。 
    接下来那我们查看setup()源码:

    1. //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
    2. //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
    3. if (!mReady) {
    4.     mSetupPending = true;
    5.     return;
    6. }

    会发现第一次进入时,第一个if语句为真,进入括号体内设置mSetupPending为true然后直接返回,后面的代码并没有执行。为什么要这样设置mReady和mSetupPending呢不执行下面的代码呢,不要急,后面再解释。


    2. 接下来我们会进入构造函数CircleImageView()。查看源码:

    1.     /**
    2.      * 构造函数
    3.      */
    4.     public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    5.         super(context, attrs, defStyle);
    6.         //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。
    7.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
    8.         //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
    9.         // 获取边界的宽度
    10.         mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
    11.         // 获取边界的颜色
    12.         mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
    13.         mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
    14.         //调用 recycle() 回收TypedArray,以便后面重用
    15.         a.recycle();
    16.         System.out.println("CircleImageView -- 构造函数");  
    17.         init();
    18.     }

    分析上面代码会发现很简单,就是首先通过TypedArray获取自定义参数,再调用init()函数:

    1.     /**
    2.      * 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用 
    3.      */
    4.     private void init() {
    5.         //在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。
    6.         super.setScaleType(SCALE_TYPE);
    7.         mReady = true;
    8.  
    9.         if (mSetupPending) {
    10.             setup();
    11.             mSetupPending = false;
    12.         }
    13.     }

    因为在前面mSetupPending为真,则执行setup(),而且这里mReady 也为true,所以会执行setup()下面的代码。为什么要这样设置mSetupPending和mReady呢? 
    这是因为第一次进入setImageXXX()时候,我们无法获取自定义参数值,所以setup()下面的代码无法绘制我们想要的样式而是默认样式。而获取自定义参数只能在构造函数里,这样我们就能明白大神作者的意图了,通过设置mSetupPending和mReady控制第一次执行setup函数里下面代码要在构造函数执行完毕时。再者如果用户再进行setImageXXX()设置图片的话,就直接会执行setup()下面的代码,因为这之后mReady一直为true。


    3 说了这么多,我们来看看setup()下面的代码到底是啥?没错,它就是这么酷炫,很关键:

    1.     /**
    2.      * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:
    3.      * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,
    4.      * 以及调用updateShaderMatrix()函数和 invalidate()函数;
    5.      */
    6.     private void setup() {
    7.         //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
    8.         //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
    9.         if (!mReady) {
    10.             mSetupPending = true;
    11.             return;
    12.         }
    13.         //防止空指针异常
    14.         if (mBitmap == null) {
    15.             return;
    16.         }
    17.         // 构建渲染器,用mBitmap来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
    18.         mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    19.         // 设置图片画笔反锯齿
    20.         mBitmapPaint.setAntiAlias(true);
    21.         // 设置图片画笔渲染器
    22.         mBitmapPaint.setShader(mBitmapShader);
    23.         // 设置边界画笔样式
    24.         mBorderPaint.setStyle(Paint.Style.STROKE);
    25.         mBorderPaint.setAntiAlias(true);
    26.         mBorderPaint.setColor(mBorderColor);    //画笔颜色
    27.         mBorderPaint.setStrokeWidth(mBorderWidth);//画笔边界宽度
    28.         //这个地方是取的原图片的宽高
    29.         mBitmapHeight = mBitmap.getHeight();
    30.         mBitmapWidth = mBitmap.getWidth();
    31.         // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)  getWidth得到是某个view的实际尺寸
    32.         mBorderRect.set(0, 0, getWidth(), getHeight());
    33.         //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)
    34.         mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
    35.         // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)
    36.         mDrawableRect.set(mBorderRect);
    37.         if (!mBorderOverlay) {
    38.             //demo里始终执行
    39.             //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)
    40.             mDrawableRect.inset(mBorderWidth, mBorderWidth);
    41.         }
    42.         //这里计算的是内圆的最小半径,也即去除边界宽度的半径
    43.         mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
    44.         //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充
    45.         updateShaderMatrix();
    46.         //手动触发ondraw()函数 完成最终的绘制
    47.         invalidate();
    48.     }

    上面代码注释我写的很详细不再一步步解释了,进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数。 
    这里关于半径的计算,我画图举个例子:CircleImageView的布局宽高度均为160,边界的宽度为10如图所示:

    blob.png

    那么去除边界宽度的内圆半径为70,带边界部分的外圆半径为75。


    4 根据上面,接下来进入updateShaderMatrix()函数,查看源码:

    1.     /**
    2.     * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。
    3.     * 作用:保证图片损失度最小和始终绘制图片正中央的那部分
    4.     */
    5.     private void updateShaderMatrix() {
    6.         float scale;
    7.         float dx = 0;
    8.         float dy = 0;
    9.  
    10.         mShaderMatrix.set(null);
    11.         // 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
    12.         //取最小的缩放比例
    13.         if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
    14.             //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
    15.             scale = mDrawableRect.height() / (float) mBitmapHeight;
    16.             dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
    17.         } else {
    18.             //x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
    19.             scale = mDrawableRect.width() / (float) mBitmapWidth;
    20.             dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
    21.         }
    22.         // shaeder的变换矩阵,我们这里主要用于放大或者缩小。
    23.         mShaderMatrix.setScale(scale, scale);
    24.         // 平移
    25.         mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
    26.         // 设置变换矩阵
    27.         mBitmapShader.setLocalMatrix(mShaderMatrix);
    28.     }

    通过updateShaderMatrix函数设置BitmapShader的Matrix参数,对图片mBitmap位置用何种缩放平移形式填充。

    1. if判断语句里
    2. mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight
    3. 写成
    4. (mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
    5. 更好理解。

    目的是用最小的缩放比例,使得图片的某个方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样。做到了图片损失度最小。同时scale保证Bitmap的宽或高和目标区域一致,那么高或宽就需要进行位移,使得Bitmap居中。这里写得很nice。


    5 现在万事俱备,只欠ondraw()了。接着上面再setup()最后会调用invalidate()函数触发ondraw()函数完成最终的绘制。查看源码:

    1.     @Override
    2.     protected void onDraw(Canvas canvas) {
    3.         //如果图片不存在就不画
    4.         if (getDrawable() == null) {
    5.             return;
    6.         }
    7.         //绘制内圆形,参数内圆半径,图片画笔为mBitmapPaint
    8.         canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
    9.       //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 参数外圆半径,边界画笔为mBorderPaint
    10.         if (mBorderWidth != 0) {
    11.             canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
    12.         }
    13.     }

    使用配置好的mBitmapPaint和mBorderPaint先画出绘制内圆形来以后再画边界圆形。源码还有一些自定义设置样式函数,很简单。 
    大功告成,是不是觉得思路比较简单,精致干练。

    我总结下源码的精致之处:

    • 流程控制的比较严谨,比如setup函数的使用

    • updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分

    • 作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。


    附上完整源码:

    1. package com.example.library;
    2.  
    3. import com.example.circleimageviewdemo_1.R;
    4.  
    5. import android.content.Context;
    6. import android.content.res.TypedArray;
    7. import android.graphics.Bitmap;
    8. import android.graphics.BitmapShader;
    9. import android.graphics.Canvas;
    10. import android.graphics.Color;
    11. import android.graphics.ColorFilter;
    12. import android.graphics.Matrix;
    13. import android.graphics.Paint;
    14. import android.graphics.RectF;
    15. import android.graphics.Shader;
    16. import android.graphics.drawable.BitmapDrawable;
    17. import android.graphics.drawable.ColorDrawable;
    18. import android.graphics.drawable.Drawable;
    19. import android.net.Uri;
    20. import android.support.annotation.ColorRes;
    21. import android.support.annotation.DrawableRes;
    22. import android.util.AttributeSet;
    23. import android.widget.ImageView;
    24. /**
    25.   * 流程控制的比较严谨,比如setup函数的使用
    26.   * updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分
    27.   * 作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。
    28.   */
    29. public class CircleImageView extends ImageView {
    30.     //缩放类型
    31.     private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
    32.     private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    33.     private static final int COLORDRAWABLE_DIMENSION = 2;
    34.     // 默认边界宽度
    35.     private static final int DEFAULT_BORDER_WIDTH = 0;
    36.     // 默认边界颜色
    37.     private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
    38.     private static final boolean DEFAULT_BORDER_OVERLAY = false;
    39.  
    40.     private final RectF mDrawableRect = new RectF();
    41.     private final RectF mBorderRect = new RectF();
    42.  
    43.     private final Matrix mShaderMatrix = new Matrix();
    44.     //这个画笔最重要的是关联了mBitmapShader 使canvas在执行的时候可以切割原图片(mBitmapShader是关联了原图的bitmap的)
    45.     private final Paint mBitmapPaint = new Paint();
    46.     //这个描边,则与本身的原图bitmap没有任何关联,
    47.     private final Paint mBorderPaint = new Paint();
    48.     //这里定义了 圆形边缘的默认宽度和颜色
    49.     private int mBorderColor = DEFAULT_BORDER_COLOR;
    50.     private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    51.  
    52.     private Bitmap mBitmap;
    53.     private BitmapShader mBitmapShader; // 位图渲染
    54.     private int mBitmapWidth;   // 位图宽度
    55.     private int mBitmapHeight;  // 位图高度
    56.  
    57.     private float mDrawableRadius;// 图片半径
    58.     private float mBorderRadius;// 带边框的的图片半径
    59.  
    60.     private ColorFilter mColorFilter;
    61.     //初始false
    62.     private boolean mReady;
    63.     private boolean mSetupPending;
    64.     private boolean mBorderOverlay;
    65.     //构造函数
    66.     public CircleImageView(Context context) {
    67.         super(context);
    68.         init();
    69.     }
    70.     //构造函数
    71.     public CircleImageView(Context context, AttributeSet attrs) {
    72.         this(context, attrs, 0);
    73.     }
    74.     /**
    75.      * 构造函数
    76.      */
    77.     public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    78.         super(context, attrs, defStyle);
    79.         //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。
    80.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
    81.         //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
    82.         // 获取边界的宽度
    83.         mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
    84.         // 获取边界的颜色
    85.         mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
    86.         mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
    87.         //调用 recycle() 回收TypedArray,以便后面重用
    88.         a.recycle();
    89.         System.out.println("CircleImageView -- 构造函数");  
    90.         init();
    91.     }
    92.     /**
    93.      * 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用 
    94.      */
    95.     private void init() {
    96.         //在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。
    97.         super.setScaleType(SCALE_TYPE);
    98.         mReady = true;
    99.  
    100.         if (mSetupPending) {
    101.             setup();
    102.             mSetupPending = false;
    103.         }
    104.     }
    105.  
    106.     @Override
    107.     public ScaleType getScaleType() {
    108.         return SCALE_TYPE;
    109.     }
    110.     /**
    111.           * 这里明确指出 此种imageview 只支持CENTER_CROP 这一种属性
    112.           *
    113.           * @param scaleType
    114.           */
    115.     @Override
    116.     public void setScaleType(ScaleType scaleType) {
    117.         if (scaleType != SCALE_TYPE) {
    118.             throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
    119.         }
    120.     }
    121.  
    122.     @Override
    123.     public void setAdjustViewBounds(boolean adjustViewBounds) {
    124.         if (adjustViewBounds) {
    125.             throw new IllegalArgumentException("adjustViewBounds not supported.");
    126.         }
    127.     }
    128.  
    129.     @Override
    130.     protected void onDraw(Canvas canvas) {
    131.         //如果图片不存在就不画
    132.         if (getDrawable() == null) {
    133.             return;
    134.         }
    135.         //绘制内圆形 图片 画笔为mBitmapPaint
    136.         canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
    137.       //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 边界画笔为mBorderPaint
    138.         if (mBorderWidth != 0) {
    139.             canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
    140.         }
    141.     }
    142.  
    143.     @Override
    144.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    145.         super.onSizeChanged(w, h, oldw, oldh);
    146.         setup();
    147.     }
    148.  
    149.     public int getBorderColor() {
    150.         return mBorderColor;
    151.     }
    152.  
    153.     public void setBorderColor(int borderColor) {
    154.         if (borderColor == mBorderColor) {
    155.             return;
    156.         }
    157.  
    158.         mBorderColor = borderColor;
    159.         mBorderPaint.setColor(mBorderColor);
    160.         invalidate();
    161.     }
    162.  
    163.     public void setBorderColorResource(@ColorRes int borderColorRes) {
    164.         setBorderColor(getContext().getResources().getColor(borderColorRes));
    165.     }
    166.  
    167.     public int getBorderWidth() {
    168.         return mBorderWidth;
    169.     }
    170.  
    171.     public void setBorderWidth(int borderWidth) {
    172.         if (borderWidth == mBorderWidth) {
    173.             return;
    174.         }
    175.  
    176.         mBorderWidth = borderWidth;
    177.         setup();
    178.     }
    179.  
    180.     public boolean isBorderOverlay() {
    181.         return mBorderOverlay;
    182.     }
    183.  
    184.     public void setBorderOverlay(boolean borderOverlay) {
    185.         if (borderOverlay == mBorderOverlay) {
    186.             return;
    187.         }
    188.  
    189.         mBorderOverlay = borderOverlay;
    190.         setup();
    191.     }
    192.  
    193.     /**
    194.      * 以下四个函数都是
    195.      * 复写ImageView的setImageXxx()方法 
    196.      * 注意这个函数先于构造函数调用之前调用
    197.      * @param bm
    198.      */
    199.     @Override
    200.     public void setImageBitmap(Bitmap bm) {
    201.         super.setImageBitmap(bm);
    202.         mBitmap = bm;
    203.         setup();
    204.     }
    205.  
    206.     @Override
    207.     public void setImageDrawable(Drawable drawable) {
    208.         super.setImageDrawable(drawable);
    209.         mBitmap = getBitmapFromDrawable(drawable);
    210.         System.out.println("setImageDrawable -- setup");  
    211.         setup();
    212.     }
    213.  
    214.     @Override
    215.     public void setImageResource(@DrawableRes int resId) {
    216.         super.setImageResource(resId);
    217.         mBitmap = getBitmapFromDrawable(getDrawable());
    218.         setup();
    219.     }
    220.  
    221.     @Override
    222.     public void setImageURI(Uri uri) {
    223.         super.setImageURI(uri);
    224.         mBitmap = getBitmapFromDrawable(getDrawable());
    225.         setup();
    226.     }
    227.  
    228.     @Override
    229.     public void setColorFilter(ColorFilter cf) {
    230.         if (cf == mColorFilter) {
    231.             return;
    232.         }
    233.  
    234.         mColorFilter = cf;
    235.         mBitmapPaint.setColorFilter(mColorFilter);
    236.         invalidate();
    237.     }
    238.     /**
    239.      * Drawable转Bitmap
    240.      * @param drawable
    241.      * @return
    242.      */
    243.     private Bitmap getBitmapFromDrawable(Drawable drawable) {
    244.         if (drawable == null) {
    245.             return null;
    246.         }
    247.  
    248.         if (drawable instanceof BitmapDrawable) {
    249.              //通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap
    250.             return ((BitmapDrawable) drawable).getBitmap();
    251.         }
    252.  
    253.         try {
    254.             Bitmap bitmap;
    255.  
    256.             if (drawable instanceof ColorDrawable) {
    257.                 bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
    258.             } else {
    259.                 bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
    260.             }
    261.  
    262.             Canvas canvas = new Canvas(bitmap);
    263.             drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    264.             drawable.draw(canvas);
    265.             return bitmap;
    266.         } catch (OutOfMemoryError e) {
    267.             return null;
    268.         }
    269.     }
    270.     /**
    271.      * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:
    272.      * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,
    273.      * 以及调用updateShaderMatrix()函数和 invalidate()函数;
    274.      */
    275.     private void setup() {
    276.         //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
    277.         //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
    278.         if (!mReady) {
    279.             mSetupPending = true;
    280.             return;
    281.         }
    282.         //防止空指针异常
    283.         if (mBitmap == null) {
    284.             return;
    285.         }
    286.         // 构建渲染器,用mBitmap位图来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
    287.         mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    288.         // 设置图片画笔反锯齿
    289.         mBitmapPaint.setAntiAlias(true);
    290.         // 设置图片画笔渲染器
    291.         mBitmapPaint.setShader(mBitmapShader);
    292.         // 设置边界画笔样式
    293.         mBorderPaint.setStyle(Paint.Style.STROKE);//设画笔为空心
    294.         mBorderPaint.setAntiAlias(true);
    295.         mBorderPaint.setColor(mBorderColor);    //画笔颜色
    296.         mBorderPaint.setStrokeWidth(mBorderWidth);//画笔边界宽度
    297.         //这个地方是取的原图片的宽高
    298.         mBitmapHeight = mBitmap.getHeight();
    299.         mBitmapWidth = mBitmap.getWidth();
    300.         // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)  getWidth得到是某个view的实际尺寸
    301.         mBorderRect.set(0, 0, getWidth(), getHeight());
    302.         //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)
    303.         mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
    304.         // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)
    305.         mDrawableRect.set(mBorderRect);
    306.         if (!mBorderOverlay) {
    307.             //demo里始终执行
    308.             //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)
    309.             mDrawableRect.inset(mBorderWidth, mBorderWidth);
    310.         }
    311.         //这里计算的是内圆的最小半径,也即去除边界宽度的半径
    312.         mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
    313.         //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充
    314.         updateShaderMatrix();
    315.         //手动触发ondraw()函数 完成最终的绘制
    316.         invalidate();
    317.     }
    318.     /**
    319.     * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。
    320.     * 作用:保证图片损失度最小和始终绘制图片正中央的那部分
    321.     */
    322.     private void updateShaderMatrix() {
    323.         float scale;
    324.         float dx = 0;
    325.         float dy = 0;
    326.  
    327.         mShaderMatrix.set(null);
    328.         // 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
    329.         //取最小的缩放比例
    330.         if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
    331.             //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
    332.             scale = mDrawableRect.height() / (float) mBitmapHeight;
    333.             dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
    334.         } else {
    335.             //x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
    336.             scale = mDrawableRect.width() / (float) mBitmapWidth;
    337.             dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
    338.         }
    339.         // shaeder的变换矩阵,我们这里主要用于放大或者缩小。
    340.         mShaderMatrix.setScale(scale, scale);
    341.         // 平移
    342.         mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
    343.         // 设置变换矩阵
    344.         mBitmapShader.setLocalMatrix(mShaderMatrix);
    345.     }
    346.  
    347. }

    Android自定义View总结

    步骤 
    1. 自定义View的属性 
    2. 继承View,重写构造函数,获取我们自定义的属性值 
    3. 重写onMesure()方法 
    4. 重写onDraw()方法

  • 相关阅读:
    bcftools 为 vcf 文件建索引及合并 vcf 文件
    Linux 替换^M字符方法
    shell 字符串分割方法简介
    shell 数组介绍及相关操作
    Annovar 信息注释
    C++ string与数值的转换
    C/C++ 删除文件 remove函数
    关于内核转储(core dump)的设置方法
    mac下nginx安装
    linux独有的sendfile系统调用--“零拷贝,高效”
  • 原文地址:https://www.cnblogs.com/jukan/p/7217841.html
Copyright © 2011-2022 走看看