您的位置:首页 > 其它

通过自定义View实现圆形图片

2017-10-20 14:50 501 查看
转载自:http://blog.csdn.net/csdnzouqi/article/details/52014489

今天给大家讲的是怎么在xml文件找中通过引用自定义的view实现ImageView的圆形图片效果。首先在你的项目中新建一个类,我给它命名为:CircleImageView;然后在res目录下的values资源目录下面新建一个名为:attrs的资源xml文件。当你把这两步都弄完了后就可以在你的xml布局文件中使用这个自定义的圆形view了。

效果图如下所示:
使用android自带的小机器人图片对比图



使用正常的图片对比图



下面是自定义的view,代码如下:
package com.example.circleimageviewtest;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.draw
4000
able.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
* 流程控制的比较严谨,比如setup函数的使用 updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分
* 作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。
*/
public class CircleImageView extends ImageView {
// 缩放类型
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;
// 默认边界宽度
private static final int DEFAULT_BORDER_WIDTH = 0;
// 默认边界颜色
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
private static final boolean DEFAULT_BORDER_OVERLAY = false;

private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();

private final Matrix mShaderMatrix = new Matrix();
// 这个画笔最重要的是关联了mBitmapShader
// 使canvas在执行的时候可以切割原图片(mBitmapShader是关联了原图的bitmap的)
private final Paint mBitmapPaint = new Paint();
// 这个描边,则与本身的原图bitmap没有任何关联,
private final Paint mBorderPaint = new Paint();
// 这里定义了 圆形边缘的默认宽度和颜色
private int mBorderColor = DEFAULT_BORDER_COLOR;
private int mBorderWidth = DEFAULT_BORDER_WIDTH;

private Bitmap mBitmap;
private BitmapShader mBitmapShader; // 位图渲染
private int mBitmapWidth; // 位图宽度
private int mBitmapHeight; // 位图高度

private float mDrawableRadius;// 图片半径
private float mBorderRadius;// 带边框的的图片半径

private ColorFilter mColorFilter;
// 初始false
private boolean mReady;
private boolean mSetupPending;
private boolean mBorderOverlay;

// 构造函数
public CircleImageView(Context context) {
super(context);
init();
}

// 构造函数
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

/**
* 构造函数
*/
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) ,
// 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CircleImageView, defStyle, 0);
// 通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
// 获取边界的宽度
mBorderWidth = a.getDimensionPixelSize(
R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
// 获取边界的颜色
mBorderColor = a.getColor(R.styleable.CircleImageView_border_color,
DEFAULT_BORDER_COLOR);
// mBorderOverlay =
// a.getBoolean(R.styleable.CircleImageView_border_overlay,
// DEFAULT_BORDER_OVERLAY);
// 调用 recycle() 回收TypedArray,以便后面重用
a.recycle();
init();
}

/**
* 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用
*/
private void init() {
// 在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。
super.setScaleType(SCALE_TYPE);
mReady = true;

if (mSetupPending) {
setup();
mSetupPending = false;
}
}

@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}

/**
* 这里明确指出 此种imageview 只支持CENTER_CROP 这一种属性
*
* @param scaleType
*/
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format(
"ScaleType %s not supported.", scaleType));
}
}

@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (adjustViewBounds) {
throw new IllegalArgumentException(
"adjustViewBounds not supported.");
}
}

@Override
protected void onDraw(Canvas canvas) {
// 如果图片不存在就不画
if (getDrawable() == null) {
return;
}
// 绘制内圆形 图片 画笔为mBitmapPaint
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
mBitmapPaint);
// 如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 边界画笔为mBorderPaint
if (mBorderWidth != 0) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius,
mBorderPaint);
}
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setup();
}

public int getBorderColor() {
return mBorderColor;
}

public void setBorderColor(int borderColor) {
if (borderColor == mBorderColor) {
return;
}

mBorderColor = borderColor;
mBorderPaint.setColor(mBorderColor);
invalidate();
}

public void setBorderColorResource(@ColorRes int borderColorRes) {
setBorderColor(getContext().getResources().getColor(borderColorRes));
}

public int getBorderWidth() {
return mBorderWidth;
}

public void setBorderWidth(int borderWidth) {
if (borderWidth == mBorderWidth) {
return;
}

mBorderWidth = borderWidth;
setup();
}

public boolean isBorderOverlay() {
return mBorderOverlay;
}

public void setBorderOverlay(boolean borderOverlay) {
if (borderOverlay == mBorderOverlay) {
return;
}

mBorderOverlay = borderOverlay;
setup();
}

/**
* 以下四个函数都是 复写ImageView的setImageXxx()方法 注意这个函数先于构造函数调用之前调用
*
* @param bm
*/
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mBitmap = bm;
setup();
}

@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
setup();
}

@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}

@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}

@Override
public void setColorFilter(ColorFilter cf) {
if (cf == mColorFilter) {
return;
}

mColorFilter = cf;
mBitmapPaint.setColorFilter(mColorFilter);
invalidate();
}

/**
* Drawable转Bitmap
*
* @param drawable
* @return
*/
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}

if (drawable instanceof BitmapDrawable) {
// 通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap
return ((BitmapDrawable) drawable).getBitmap();
}

try {
Bitmap bitmap;

if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION,
COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}

Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}

/**
* 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:
* 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,
* 以及调用updateShaderMatrix()函数和 invalidate()函数;
*/
private void setup() {
// 因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
// 设置mSetupPending为true然后直接返回,后面的代码并没有执行。
if (!mReady) {
mSetupPending = true;
return;
}
// 防止空指针异常
if (mBitmap == null) {
return;
}
// 构建渲染器,用mBitmap位图来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
// 设置图片画笔反锯齿
mBitmapPaint.setAntiAlias(true);
// 设置图片画笔渲染器
mBitmapPaint.setShader(mBitmapShader);
// 设置边界画笔样式
mBorderPaint.setStyle(Paint.Style.STROKE);// 设画笔为空心
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor); // 画笔颜色
mBorderPaint.setStrokeWidth(mBorderWidth);// 画笔边界宽度
// 这个地方是取的原图片的宽高
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
// 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)
// getWidth得到是某个view的实际尺寸
mBorderRect.set(0, 0, getWidth(), getHeight());
// 计算
// 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2,
(mBorderRect.width() - mBorderWidth) / 2);
// 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay) {
// demo里始终执行
// 通过inset方法
// 使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)
mDrawableRect.inset(mBorderWidth, mBorderWidth);
}
// 这里计算的是内圆的最小半径,也即去除边界宽度的半径
mDrawableRadius = Math.min(mDrawableRect.height() / 2,
mDrawableRect.width() / 2);
// 设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充
updateShaderMatrix();
// 手动触发ondraw()函数 完成最终的绘制
invalidate();
}

/**
* 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。 作用:保证图片损失度最小和始终绘制图片正中央的那部分
*/
private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;

mShaderMatrix.set(null);
// 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) >
// (mBitmapHeight / mDrawableRect.height())
// 取最小的缩放比例
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width()
* mBitmapHeight) {
// y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
// x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}
// shaeder的变换矩阵,我们这里主要用于放大或者缩小。
mShaderMatrix.setScale(scale, scale);
// 平移
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left,
(int) (dy + 0.5f) + mDrawableRect.top);
// 设置变换矩阵
mBitmapShader.setLocalMatrix(mShaderMatrix);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372

该类里的代码你可以直接复制到你的类里,将代码里的包名修改为你现在使用的包名就可以拿来用了。
下面是attrs文件里面的内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="CircleImageView">
<attr name="border_width" format="dimension" />
<attr name="border_color" format="color" />
</declare-styleable>

</resources>
1
2
3
4
5
6
7
8
9

该文件是用来定义组件的属性的,其所在的目录如下图:



上面说的类和attrs文件都装备好了后就可以在你的xml布局文件里面使用了。使用方法就是在布局文件里面引用你的自定义view。

代码如下:
<com.example.circleimageviewtest.CircleImageView
android:id="@+id/cv_1"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/ddt"
app:border_color="#35B7F7"
app:border_width="1dp" />
1
2
3
4
5
6
7

这里面有两个属性很关键,想要显示圆形的效果就需要这两个属性。 

一个是:
app:border_color="#35B7F7"
 

一个是:
app:border_width="1dp"
 

其中border_color属性是用来显示圆形边框的颜色的,border_width属性是用来显示圆形边框的宽度的。大家可以自己在xml布局文件中使用就可以很清楚属性的作用了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐