Android自定义View总结

Android自定义View总结

步骤

  1. 自定义View的属性
  2. 在View的构造方法获取我们自定义的属性值
  3. 重写onMesure()方法
  4. 重写onDraw()方法

自定义View的属性

在res/values/下建立attrs.xml,定义控件的属性和样式,如下所示:

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

format为取值类型,有以下类型:

  • string 字符串
  • color 颜色值,如#ffffff
  • dimension 尺寸,xml中设置为dp/dip, 字体为sp
  • integer 数值 1
  • enum 枚举类型
  • reference 引用,如@drawable/ic_launcher
  • float 浮点类型 如1.0
  • boolean 布尔类型 true或false
  • fraction 百分数 如100%
  • flag 位或运算

在xml布局文件中,声明我们的自定义View,示例代码:

<com.infzm.o2o.view.CircleImageView
       xmlns:app="http://schemas.android.com/apk/res-auto"
       android:id="@+id/iv_head_icon"
       android:layout_width="wrap_content"
       android:layout_height="59dp"
       android:layout_marginBottom="15dp"
       android:layout_marginLeft="15dp"
       android:src="@drawable/ic_profile_user_default"
       app:border_color="@color/white"
       app:border_width="2dp" >

注意需要引入命名空间,xmlns:app=”http://schemas.android.com/apk/res-auto",不然无法使用我们的属性

在View的构造方法获取我们自定义的属性值

重写3个构造方法,示例代码如下:

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
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);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

// 获取边界的宽度
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
// 获取边界的颜色
mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);

// 回收TypedArray,以便后面重用
a.recycle();

init();
}

重写onMesure()方法

这个方法用来计算View的大小,如果需要计算View的大小,则需要重写此方法。
示例代码:

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);  

    int width;  
    int height;  

    ...   

    if (widthMode == MeasureSpec.EXACTLY) {  
        // Parent has told us how big to be. So be it.  
        width = widthSize;  
    } else {  
        if (mLayout != null && mEllipsize == null) {  
            des = desired(mLayout);  
        }  

    ...  

    setMeasuredDimension(width, height);   
    }

这里有几个点需要提一下是specMode,我们视图的规格模式,有三种类型:

  1. MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的,一般是设置了明确的值或者说是MATCH_PARENT。
  2. MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,不建议子视图大小超过specSize中给定的值,一般为WRAP_CONTENT。
  3. MeasureSpec.UNSPECIFIED:可以随意指定视图的大小。

最终我们会调用setMesuredDimension(width, height);来设置最终视图的大小,width和height就是我们计算得来的值。

重写onDraw()方法

我们通过这个方法来绘制我们的视图,示例代码:

   @Override
protected void onDraw(Canvas canvas) {
    if (getDrawable() == null) {
        return;
    }

    canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
    if (mBorderWidth != 0) {
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
    }
}

比如示例代码中,我们通过画布来绘制一个圆形。

最后附上开源控件,自定义圆形头像的解析:

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
package com.infzm.o2o.view;

import com.infzm.o2o.R;

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.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
* 自定义圆形头像
* @author wwj_748
*
*/
public class CircleImageView extends ImageView {

/**
* 缩放类型
*/
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

/**
* 位图配置
* ARGB_8888代表
* A:透明度:8
* R:红色:8
* G:绿:8
* B:蓝:8
* 表示图片每个像素点占8+8+8+8 = 32位
*/
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 1;

private static final int DEFAULT_BORDER_WIDTH = 0; // 默认边界宽度
private static final int DEFAULT_BORDER_COLOR = Color.BLACK; // 默认边界颜色

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

private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
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 boolean mReady;
private boolean mSetupPending;

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);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

// 获取边界的宽度
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
// 获取边界的颜色
mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);

// 回收TypedArray,以便后面重用
a.recycle();

init();
}

private void init() {
super.setScaleType(SCALE_TYPE);
mReady = true;

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

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

@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
}
}


@Override
protected void onDraw(Canvas canvas) {
if (getDrawable() == null) {
return;
}

// 绘制圆形图片
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
if (mBorderWidth != 0) { // 如果边界不为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 int getBorderWidth() {
return mBorderWidth;
}

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

mBorderWidth = borderWidth;
setup();
}

@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(int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}

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

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

if (drawable instanceof BitmapDrawable) {
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;
}
}

private void setup() {
if (!mReady) {
mSetupPending = true;
return;
}

if (mBitmap == null) {
return;
}

// 构建渲染器
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();

// 设置含边框显示区域
mBorderRect.set(0, 0, getWidth(), getHeight());
// 计算半径
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);

// 设置图片显示区域:即View的大小区域减去边界的大小
mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
// 计算图片区域的半径
mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);

updateShaderMatrix();
invalidate();
}

private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;

mShaderMatrix.set(null);

// 如果位图的高度*显示图片区域的高度 大于 位图高度 * 图片区域宽度
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
// 按高计算缩放比例
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
// 按宽计算缩放比例
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}

// shaeder的变换矩阵,我们这里主要用于放大或者缩小。
mShaderMatrix.setScale(scale, scale);
// 平移
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);

// 设置变换矩阵
mBitmapShader.setLocalMatrix(mShaderMatrix);
}

}
posted @ 2017-03-13 15:50  天涯海角路  阅读(134)  评论(0)    收藏  举报