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 }