CircleImageView的代码很简洁,因此先将此工程作为源码解析系列的第一篇文章.
解析说明都在代码里了。
1 /*
2 * Copyright 2014 - 2015 Henning Dodenhof 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0
9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */
16 package de.hdodenhof.circleimageview; 17
18 import android.content.Context; 19 import android.content.res.TypedArray; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapShader; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.ColorFilter; 25 import android.graphics.Matrix; 26 import android.graphics.Paint; 27 import android.graphics.RectF; 28 import android.graphics.Shader; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.graphics.drawable.ColorDrawable; 31 import android.graphics.drawable.Drawable; 32 import android.net.Uri; 33 import android.support.annotation.ColorInt; 34 import android.support.annotation.ColorRes; 35 import android.support.annotation.DrawableRes; 36 import android.util.AttributeSet; 37 import android.widget.ImageView; 38
39 public class CircleImageView extends ImageView { 40
41 private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;//这里限制了只要一种模式:CENTER_CROP
42
43 private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; 44 private static final int COLORDRAWABLE_DIMENSION = 2; 45
46 private static final int DEFAULT_BORDER_WIDTH = 0; 47 private static final int DEFAULT_BORDER_COLOR = Color.BLACK; 48 private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT; 49 private static final boolean DEFAULT_BORDER_OVERLAY = false; 50
51 private final RectF mDrawableRect = new RectF(); 52 private final RectF mBorderRect = new RectF(); 53
54 //用来变换 mBitmapShader,从而影响 mBitmapPaint的显示效果
55 private final Matrix mShaderMatrix = new Matrix(); 56 private final Paint mBitmapPaint = new Paint(); 57 private final Paint mBorderPaint = new Paint(); 58 private final Paint mFillPaint = new Paint(); 59
60 private int mBorderColor = DEFAULT_BORDER_COLOR; 61 private int mBorderWidth = DEFAULT_BORDER_WIDTH; 62 private int mFillColor = DEFAULT_FILL_COLOR; 63
64 private Bitmap mBitmap; 65 private BitmapShader mBitmapShader; 66 private int mBitmapWidth; 67 private int mBitmapHeight; 68
69 private float mDrawableRadius; 70 private float mBorderRadius; 71
72 private ColorFilter mColorFilter;//滤镜效果,用在中间的bitmap上
73
74 private boolean mReady; 75 private boolean mSetupPending; 76 private boolean mBorderOverlay; 77
78 public CircleImageView(Context context) { 79 super(context); 80 init(); 81 } 82
83 public CircleImageView(Context context, AttributeSet attrs) { 84 this(context, attrs, 0); 85 } 86
87 //构造方法,获取属性文件中的定义,不必细说
88 public CircleImageView(Context context, AttributeSet attrs, int defStyle) { 89 super(context, attrs, defStyle); 90
91 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); 92
93 mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH); 94 mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR); 95 mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY); 96 mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR); 97
98 a.recycle(); 99
100 init(); 101 } 102
103 //初始化方法,主要是设置ScaleType,ScaleType被限定为center_crop模式
104 private void init() { 105 super.setScaleType(SCALE_TYPE); 106 mReady = true; 107
108 if (mSetupPending) { 109 setup(); 110 mSetupPending = false; 111 } 112 } 113
114 @Override 115 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 116 super.onSizeChanged(w, h, oldw, oldh); 117 setup();//实现了refresh功能,当属性改变时,调用此方法,完成刷新界面的功能
118 } 119
120 @Override 121 protected void onDraw(Canvas canvas) { 122 if (mBitmap == null) { 123 return; 124 } 125
126 //底色 mFillColor --> 画笔 mFillPaint
127 if (mFillColor != Color.TRANSPARENT) { 128 canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mFillPaint); 129 } 130
131 canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mBitmapPaint); 132
133 //边色 mBorderColor --> 画笔 mBorderPaint
134 if (mBorderWidth != 0) { 135 canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mBorderRadius, mBorderPaint); 136 } 137 } 138
139 private void setup() { 140 if (!mReady) { 141 mSetupPending = true; 142 return; 143 } 144
145 if (getWidth() == 0 && getHeight() == 0) { 146 return; 147 } 148
149 if (mBitmap == null) { 150 invalidate(); 151 return; 152 } 153
154 mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 155
156 mBitmapPaint.setAntiAlias(true); 157 mBitmapPaint.setShader(mBitmapShader); 158
159 mBorderPaint.setStyle(Paint.Style.STROKE); 160 mBorderPaint.setAntiAlias(true); 161 mBorderPaint.setColor(mBorderColor); 162 mBorderPaint.setStrokeWidth(mBorderWidth); 163
164 mFillPaint.setStyle(Paint.Style.FILL); 165 mFillPaint.setAntiAlias(true); 166 mFillPaint.setColor(mFillColor); 167
168 mBitmapHeight = mBitmap.getHeight(); 169 mBitmapWidth = mBitmap.getWidth(); 170
171 mBorderRect.set(0, 0, getWidth(), getHeight()); 172 mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f); 173
174 mDrawableRect.set(mBorderRect); 175 if (!mBorderOverlay) { 176 mDrawableRect.inset(mBorderWidth, mBorderWidth); 177 } 178 mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f); 179
180 updateShaderMatrix(); 181 invalidate(); 182 } 183
184 private void updateShaderMatrix() { 185 float scale; 186 float dx = 0; 187 float dy = 0; 188
189 mShaderMatrix.set(null); 190 //这里判断长宽比例,即图片的实际长宽比例与view的长宽比例 191 //如果图片更"扁",则缩放的尺寸按照两者的高度来定 192 //如果图片更"瘦",则缩放的尺寸按照两者的宽度来定
193 if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { 194 scale = mDrawableRect.height() / (float) mBitmapHeight; 195 dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; 196 } else { 197 scale = mDrawableRect.width() / (float) mBitmapWidth; 198 dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; 199 } 200
201 mShaderMatrix.setScale(scale, scale); 202 mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); 203 //可以简单的将BitmapShader类用来给Paint设置"填充颜色",这种说法其实并不准确,shader"填充"效果针对的范围是整个canvas, 204 //而paint显示的是这个paint实时画出来的部分。 205 //shader可以设置matrix,用来缩放或位移
206 mBitmapShader.setLocalMatrix(mShaderMatrix); 207 } 208
209 @Override 210 public ScaleType getScaleType() { 211 return SCALE_TYPE; 212 } 213
214 @Override 215 public void setScaleType(ScaleType scaleType) { 216 if (scaleType != SCALE_TYPE) { 217 throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)); 218 } 219 } 220
221 @Override 222 public void setAdjustViewBounds(boolean adjustViewBounds) { 223 if (adjustViewBounds) { 224 throw new IllegalArgumentException("adjustViewBounds not supported."); 225 } 226 } 227
228 public int getBorderColor() { 229 return mBorderColor; 230 } 231
232 public void setBorderColor(@ColorInt int borderColor) { 233 if (borderColor == mBorderColor) { 234 return; 235 } 236
237 mBorderColor = borderColor; 238 mBorderPaint.setColor(mBorderColor); 239 invalidate(); 240 } 241
242 public void setBorderColorResource(@ColorRes int borderColorRes) { 243 setBorderColor(getContext().getResources().getColor(borderColorRes)); 244 } 245
246 public int getFillColor() { 247 return mFillColor; 248 } 249
250 public void setFillColor(@ColorInt int fillColor) { 251 if (fillColor == mFillColor) { 252 return; 253 } 254
255 mFillColor = fillColor; 256 mFillPaint.setColor(fillColor); 257 invalidate(); 258 } 259
260 public void setFillColorResource(@ColorRes int fillColorRes) { 261 setFillColor(getContext().getResources().getColor(fillColorRes)); 262 } 263
264 public int getBorderWidth() { 265 return mBorderWidth; 266 } 267
268 public void setBorderWidth(int borderWidth) { 269 if (borderWidth == mBorderWidth) { 270 return; 271 } 272
273 mBorderWidth = borderWidth; 274 setup(); 275 } 276
277 public boolean isBorderOverlay() { 278 return mBorderOverlay; 279 } 280
281 public void setBorderOverlay(boolean borderOverlay) { 282 if (borderOverlay == mBorderOverlay) { 283 return; 284 } 285
286 mBorderOverlay = borderOverlay; 287 setup(); 288 } 289
290 @Override 291 public void setImageBitmap(Bitmap bm) { 292 super.setImageBitmap(bm); 293 mBitmap = bm; 294 setup(); 295 } 296
297 @Override 298 public void setImageDrawable(Drawable drawable) { 299 super.setImageDrawable(drawable); 300 mBitmap = getBitmapFromDrawable(drawable); 301 setup(); 302 } 303
304 @Override 305 public void setImageResource(@DrawableRes int resId) { 306 super.setImageResource(resId); 307 mBitmap = getBitmapFromDrawable(getDrawable()); 308 setup(); 309 } 310
311 @Override 312 public void setImageURI(Uri uri) { 313 super.setImageURI(uri); 314 mBitmap = uri != null ? getBitmapFromDrawable(getDrawable()) : null; 315 setup(); 316 } 317
318 @Override 319 public void setColorFilter(ColorFilter cf) { 320 if (cf == mColorFilter) { 321 return; 322 } 323
324 mColorFilter = cf; 325 mBitmapPaint.setColorFilter(mColorFilter); 326 invalidate(); 327 } 328
329 private Bitmap getBitmapFromDrawable(Drawable drawable) { 330 if (drawable == null) { 331 return null; 332 } 333
334 if (drawable instanceof BitmapDrawable) { 335 return ((BitmapDrawable) drawable).getBitmap(); 336 } 337
338 try { 339 Bitmap bitmap; 340
341 if (drawable instanceof ColorDrawable) { 342 //COLORDRAWABLE_DIMENSION == 2,如果是ColorDrawable类型的,就取2x2大小
343 bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); 344 } else { 345 bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG); 346 } 347
348 Canvas canvas = new Canvas(bitmap); 349 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 350 drawable.draw(canvas); 351 return bitmap; 352 } catch (Exception e) { 353 e.printStackTrace(); 354 return null; 355 } 356 } 357
358
359
360 }