Android源码分析--CircleImageView 源码详解

源码地址为 https://github.com/hdodenhof/CircleImageView

实际上就是一个圆形的imageview 的自定义控件。代码写的很优雅,实现效果也很好,

特此分析。源码其实不难 主要就是一个类,可以把我的这个加了注释的源码放到你自己的工程里直接替换

然后run,这样效果更佳。

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