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 }