本文主要讲述了如何实现Android 自定义view之环形等待控件,现在把实现思路和代码整理出来分享给Android程序员兄弟们,希望给他们的开发工作带来帮助。
效果图如下:
以上就是今天我们要实现的效果,乍一看是不是觉得高端大气上档次,完全没有什么头绪怎么去实现这么“高端”的东西。还会不定时的反问自己可以吗?对,你可以的。让我们一起来学习如何写这样的控件吧。
【前言】自定义view 的几个步骤
自定义view的属性在view 的构造方法中获取我们自定义的属性的值重写onMeasure方法(有时不需要重写这个方法)重写onDraw方法
【正文】
项目结构图奉上:
我们先来分析一下这个控件。这个控件主要有两种颜色,加载的速度,圆环的宽度,好像也没有其他属性值了。
1.自定义属性:attrs.xml
1 | <span style= "font-size: medium;" ><!--?xml version= 1.0 encoding=utf- 8 ?--></span> |
这里的format主要常见的有以下几种属性:reference 、color、boolean、dimension、float、integer、string、fraction、enum、flag。更多用法以及如何在初始化时获取相应的值,可以百度,这里不是我们的重点。
2.在构造方法中获取我们自定义的属性:CustomProgressbar.java
1 | <span style= "font-size: medium;" > // 设置第一圈颜色 |
2 | private int mFirstColor=Color.GREEN; |
3 | // 设置第二圈颜色 |
4 | private int mSecondColor=Color.RED; |
5 | // 设置圈的宽度 |
6 | private int mCircleWidth= 20 ; |
7 | // 设置颜色填充画笔 |
8 | private Paint mPaint; |
9 | // 设置当前进度 |
10 | private int mProgress; |
11 | // 设置当前进度加载速度 |
12 | private int speed= 20 ; |
13 | // 是否开始下一个 |
14 | private boolean isNext = false ; |
15 |
16 | public CustomProgressbar(Context context, AttributeSet attrs) { |
17 | this (context, attrs, 0 ); |
18 | } |
19 |
20 | public CustomProgressbar(Context context) { |
21 | this (context, null ); |
22 | } |
23 |
24 | /** |
25 | * 必要的初始化,获取一些自定义的值 |
26 | * |
27 | * @param context |
28 | * @param attrs |
29 | * @param defStyle |
30 | */ |
31 | public CustomProgressbar(Context context, AttributeSet attrs, int defStyle) { |
32 | super (context, attrs, defStyle); |
33 | // 获取自定义的属性集合 |
34 | TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressbar, defStyle, 0 ); |
35 | // 获取自定义属性的个数 |
36 | int n = array.getIndexCount(); |
37 | Log.i(test, 自定义属性的个数: + n); |
38 | // 遍历属性值 |
39 | for ( int i = 0 ; i < n; i++) { |
40 | int attr = array.getIndex(i); |
41 | Log.i(test, 自定义的属性为:+attr); |
42 | switch (attr) { |
43 | case R.styleable.CustomProgressbar_firstColor: |
44 | // 获取第一圈颜色值 |
45 | mFirstColor = array.getColor(attr, Color.GREEN); |
46 | break ; |
47 | case R.styleable.CustomProgressbar_secondColor: |
48 | // 获取第一圈颜色值 |
49 | mSecondColor = array.getColor(attr, Color.RED); |
50 | break ; |
51 | case R.styleable.CustomProgressbar_circleWidth: |
52 | // 设置默认圈的宽度为20px |
53 | mCircleWidth = array.getDimensionPixelSize(attr, ( int ) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 20 , getResources().getDisplayMetrics())); |
54 | break ; |
55 | case R.styleable.CustomProgressbar_speed: |
56 | // 获取默认加载速度 |
57 | speed = array.getInt(attr, 20 ); |
58 | break ; |
59 | } |
60 | } |
61 | // 回收 |
62 | array.recycle(); |
63 | mPaint = new Paint(); |
64 | // 绘图线程 此线程为耗时线程,放在子线程中执行,防止主线程的卡顿 |
65 | new Thread() { |
66 | public void run() { |
67 | while ( true ) { |
68 | mProgress++; |
69 | if (mProgress == 360 ) { |
70 | mProgress = 0 ; |
71 | // 如果没有开始下一个,则设置isNext为true |
72 | if (!isNext) { |
73 | isNext = true ; |
74 | } else { |
75 | isNext = false ; |
76 | } |
77 | } |
78 | // 刷新UI |
79 | // postInvalidate()此方法可以直接在UI线程调用,invalidate()则需要在handler中进行调用 |
80 | postInvalidate(); |
81 | try { |
82 | Thread.sleep(speed); |
83 | } catch (InterruptedException e) { |
84 | e.printStackTrace(); |
85 | } |
86 | } |
87 | } |
88 | }.start(); |
89 | }</span> |
很多童鞋肯定会问,为什么这个操作要放在thread里进行操作?重写view是一个耗时操作,所以我们这里就直接放在子线程里进行重绘了,防止程序卡死。还有一个需要注意的就是postInvalidate()方法。postInvalidate()此方法可以直接在UI线程调用,invalidate()则需要在handler中进行调用,想要了解更多的童鞋也可以自己百度两者之间的区别。
3.重写onMeasure方法:本例中没有用到重写onMeasure方法,此处就不贴代码了。
4.重写onDraw方法,进行view 的绘制:CustomProgressbar.java
1 | <span style= "font-size: medium;" > @Override |
2 | protected void onDraw(Canvas canvas) { |
3 | // 获取圆心的x坐标 |
4 | int center = getWidth() / 2 ; |
5 | // 获取圆的半径 |
6 | int radius = center - mCircleWidth / 2 ; |
7 | // 设置填充的宽度 |
8 | mPaint.setStrokeWidth(mCircleWidth); |
9 | mPaint.setAntiAlias( true ); |
10 | // 设置填充的style |
11 | mPaint.setStyle(Paint.Style.STROKE); |
12 | // new RectF(left, top, right, bottom) 为距离x轴,y轴之间的距离 |
13 | // 定义rect的形状 |
14 | RectF f = new RectF(center - radius, center - radius, center + radius, center + radius); |
15 | if (!isNext) { |
16 | // 第一圈颜色完整,第二圈颜色跑 |
17 | mPaint.setColor(mFirstColor); // 设置画笔颜色 |
18 | // 画出圆环 |
19 | canvas.drawCircle(center, center, radius, mPaint); |
20 | // 设置圆环颜色 |
21 | mPaint.setColor(mSecondColor); |
22 | /* |
23 | * public void drawArc(RectF oval, float startAngle, float sweepAngle, |
24 | * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle: |
25 | * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter: |
26 | * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。 |
27 | */ |
28 | canvas.drawArc(f, -90, mProgress, false, mPaint); |
29 | } else { |
30 | // 第一圈颜色完整,第二圈颜色跑 |
31 | mPaint.setColor(mSecondColor);// 设置画笔颜色 |
32 | // 画出圆环 |
33 | canvas.drawCircle(center, center, radius, mPaint); |
34 | // 设置圆环颜色 |
35 | mPaint.setColor(mFirstColor); |
36 | /* |
37 | * public void drawArc(RectF oval, float startAngle, float sweepAngle, |
38 | * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle: |
39 | * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter: |
40 | * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。 |
41 | */ |
42 | canvas.drawArc(f, - 90 , mProgress, false , mPaint); |
43 | } |
44 | }</span> |
这里关于drawArc的方法,我已经加了详细的注释,不明白的同学可以自己百度一下。
由于上面贴的是片段代码,这里给出CustomProgressbar.java的全部代码:
1 | <span style= "font-size: medium;" > package com.beyole.view; |
2 |
3 | import android.content.Context; |
4 | import android.content.res.TypedArray; |
5 | import android.graphics.Canvas; |
6 | import android.graphics.Color; |
7 | import android.graphics.Paint; |
8 | import android.graphics.RectF; |
9 | import android.util.AttributeSet; |
10 | import android.util.Log; |
11 | import android.util.TypedValue; |
12 | import android.view.View; |
13 |
14 | import com.beyole.circlewaitting.R; |
15 |
16 | public class CustomProgressbar extends View { |
17 |
18 | // 设置第一圈颜色 |
19 | private int mFirstColor=Color.GREEN; |
20 | // 设置第二圈颜色 |
21 | private int mSecondColor=Color.RED; |
22 | // 设置圈的宽度 |
23 | private int mCircleWidth= 20 ; |
24 | // 设置颜色填充画笔 |
25 | private Paint mPaint; |
26 | // 设置当前进度 |
27 | private int mProgress; |
28 | // 设置当前进度加载速度 |
29 | private int speed= 20 ; |
30 | // 是否开始下一个 |
31 | private boolean isNext = false ; |
32 |
33 | public CustomProgressbar(Context context, AttributeSet attrs) { |
34 | this (context, attrs, 0 ); |
35 | } |
36 |
37 | public CustomProgressbar(Context context) { |
38 | this (context, null ); |
39 | } |
40 |
41 | /** |
42 | * 必要的初始化,获取一些自定义的值 |
43 | * |
44 | * @param context |
45 | * @param attrs |
46 | * @param defStyle |
47 | */ |
48 | public CustomProgressbar(Context context, AttributeSet attrs, int defStyle) { |
49 | super (context, attrs, defStyle); |
50 | // 获取自定义的属性集合 |
51 | TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressbar, defStyle, 0 ); |
52 | // 获取自定义属性的个数 |
53 | int n = array.getIndexCount(); |
54 | Log.i(test, 自定义属性的个数: + n); |
55 | // 遍历属性值 |
56 | for ( int i = 0 ; i < n; i++) { |
57 | int attr = array.getIndex(i); |
58 | Log.i(test, 自定义的属性为:+attr); |
59 | switch (attr) { |
60 | case R.styleable.CustomProgressbar_firstColor: |
61 | // 获取第一圈颜色值 |
62 | mFirstColor = array.getColor(attr, Color.GREEN); |
63 | break ; |
64 | case R.styleable.CustomProgressbar_secondColor: |
65 | // 获取第一圈颜色值 |
66 | mSecondColor = array.getColor(attr, Color.RED); |
67 | break ; |
68 | case R.styleable.CustomProgressbar_circleWidth: |
69 | // 设置默认圈的宽度为20px |
70 | mCircleWidth = array.getDimensionPixelSize(attr, ( int ) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 20 , getResources().getDisplayMetrics())); |
71 | break ; |
72 | case R.styleable.CustomProgressbar_speed: |
73 | // 获取默认加载速度 |
74 | speed = array.getInt(attr, 20 ); |
75 | break ; |
76 | } |
77 | } |
78 | // 回收 |
79 | array.recycle(); |
80 | mPaint = new Paint(); |
81 | // 绘图线程 此线程为耗时线程,放在子线程中执行,防止主线程的卡顿 |
82 | new Thread() { |
83 | public void run() { |
84 | while ( true ) { |
85 | mProgress++; |
86 | if (mProgress == 360 ) { |
87 | mProgress = 0 ; |
88 | // 如果没有开始下一个,则设置isNext为true |
89 | if (!isNext) { |
90 | isNext = true ; |
91 | } else { |
92 | isNext = false ; |
93 | } |
94 | } |
95 | // 刷新UI |
96 | // postInvalidate()此方法可以直接在UI线程调用,invalidate()则需要在handler中进行调用 |
97 | postInvalidate(); |
98 | try { |
99 | Thread.sleep(speed); |
100 | } catch (InterruptedException e) { |
101 | e.printStackTrace(); |
102 | } |
103 | } |
104 | } |
105 | }.start(); |
106 | } |
107 |
108 | @Override |
109 | protected void onDraw(Canvas canvas) { |
110 | // 获取圆心的x坐标 |
111 | int center = getWidth() / 2 ; |
112 | // 获取圆的半径 |
113 | int radius = center - mCircleWidth / 2 ; |
114 | // 设置填充的宽度 |
115 | mPaint.setStrokeWidth(mCircleWidth); |
116 | mPaint.setAntiAlias( true ); |
117 | // 设置填充的style |
118 | mPaint.setStyle(Paint.Style.STROKE); |
119 | // new RectF(left, top, right, bottom) 为距离x轴,y轴之间的距离 |
120 | // 定义rect的形状 |
121 | RectF f = new RectF(center - radius, center - radius, center + radius, center + radius); |
122 | if (!isNext) { |
123 | // 第一圈颜色完整,第二圈颜色跑 |
124 | mPaint.setColor(mFirstColor); // 设置画笔颜色 |
125 | // 画出圆环 |
126 | canvas.drawCircle(center, center, radius, mPaint); |
127 | // 设置圆环颜色 |
128 | mPaint.setColor(mSecondColor); |
129 | /* |
130 | * public void drawArc(RectF oval, float startAngle, float sweepAngle, |
131 | * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle: |
132 | * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter: |
133 | * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。 |
134 | */ |
135 | canvas.drawArc(f, -90, mProgress, false, mPaint); |
136 | } else { |
137 | // 第一圈颜色完整,第二圈颜色跑 |
138 | mPaint.setColor(mSecondColor);// 设置画笔颜色 |
139 | // 画出圆环 |
140 | canvas.drawCircle(center, center, radius, mPaint); |
141 | // 设置圆环颜色 |
142 | mPaint.setColor(mFirstColor); |
143 | /* |
144 | * public void drawArc(RectF oval, float startAngle, float sweepAngle, |
145 | * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle: |
146 | * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter: |
147 | * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。 |
148 | */ |
149 | canvas.drawArc(f, - 90 , mProgress, false , mPaint); |
150 | } |
151 | } |
152 | }</span> |
自定义的控件已经定义结束,接下来就是如何调用我们写的view了,首先,在我们的主布局文件:activity_main.xml中进行引用:
1 |
这里注意,我们定义了自己的命名控件,也就是
1 | <span style= "font-size: medium;" > xmlns:beyole=http: //schemas.android.com/apk/res/com.beyole.circlewaitting</span> |
这里的com.beyole.circlewaitting就是我们应用程序的包名。如何知道我们应用程序的包名?直接在AndroidManifest.xml文件中
主布局文件里面没有更改内容:MainActivity.java
1 | <span style= "font-size: medium;" > package com.beyole.circlewaitting; |
2 |
3 | import android.app.Activity; |
4 | import android.os.Bundle; |
5 |
6 | public class MainActivity extends Activity { |
7 |
8 | @Override |
9 | protected void onCreate(Bundle savedInstanceState) { |
10 | super .onCreate(savedInstanceState); |
11 | setContentView(R.layout.activity_main); |
12 | } |
13 | }</span> |
本文到此结束,需要的朋友可以参考下。