Android原始碼分析--CircleImageView 原始碼詳解

希爾瓦娜斯女神發表於2015-07-06

原始碼地址為 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 }

 

相關文章