Android鬼点子-上手自定义View


Dots preloader
       Android自定义View也算是Android程序猿们基本技能了,所以最近想试着上周做一做。
作为入门,就以一个简单的不响应触摸事件的View开始吧!

       我在https://material.uplabs.com/posts/dots-preloader 上看到的下面的效果,那么动手实现一下吧 Dots preloader

  • 在OnMeasure()方法中,获取属性值,测量自定义控件的大小,使自定义控件能够自适应布局各种各样的需求。

在attr.xml中

1
2
3
<declare-styleable name="DotsPreloader">
<attr name="Circlecolor" format="color"></attr>
</declare-styleable>

在构造方法中获取属性,设置点点的颜色

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
TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.DotsPreloader);
mColor = a.getColor(R.styleable.DotsPreloader_Circlecolor,Color.GRAY);
a.recycle();
```
测量View的大小
```java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
```
其中来自父控件的信息都是封装在widthMeasureSpec和heightMeasureSpec中,其中包含测量模式和具体的大小,由高32位和低16位组成,高32位保存的值叫specMode,可以通过如代码中所示的MeasureSpec.getMode()获取;低16位为specSize,由MeasureSpec.getSize()获取。
specMode一共有三种可能:
MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的。
MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,也就是说不建议子视图的大小超过specSize中给定的值。
MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。比如scollerView,因为是可以滚动的,所以长度是没有限制的。
最后测量的结果由下面这个方法设定
setMeasuredDimension(int measuredWidth, int measuredHeight)
```java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
mLayoutSize = Math.min(widthSpecSize,heightSpecSize);
// 此处需要调用measureChild,子View才会被测量,在onLayout()中,才能取到子View长宽
// int cCount = getChildCount();
// for (int i = 0; i < cCount; i++) {
// View child = getChildAt(i);
// // 测量每一个child的宽和高
// measureChild(child, widthMeasureSpec, heightMeasureSpec);
//
// }
//这里直接设成来自父控件大小的一个正方形
setMeasuredDimension(mLayoutSize, mLayoutSize);
}
  • onLayout()是决定View在ViewGroup的位置。因为我们现在讨论的是View,没有子View需要排列,所以这一步其实我们不需要做额外的工作。插一句,对ViewGroup类,onLayout方法中,我们需要将所有子View的大小宽高设置好,这里大致说以layout的流程。
1
2
3
4
5
6
7
8
9
10
11
getChildCount();//取到自子View的数量。
View childView = getChildAt(i);//取到子View的信息。
cWidth = childView.getMeasuredWidth();
cHeight = childView.getMeasuredHeight();
cParams = (MarginLayoutParams) childView.getLayoutParams();
cl = cParams.leftMargin;
ct = cParams.topMargin;
//最后设置
childView.layout(cl, ct, cr, cb);

View中还有三个比较重要的方法

requestLayout
View重新调用一次layout过程。

invalidate
View重新调用一次draw过程

forceLayout
标识View在下一次重绘,需要重新调用layout过程。
  • 在OnDraw()方法中,来绘制要显示的内容。
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
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//圆点的半径
double radius = (mLayoutSize/6) * 0.3;
//圆点之间的缝隙
double gap = (mLayoutSize/6) * 0.4;
//计算6个圆点的圆心坐标
List<Point> mCenterPoints = getCenterPoints(radius,gap);
for(Point mCenterPoint : mCenterPoints){
canvas.drawCircle(mCenterPoint.x , mCenterPoint.y , (float) radius , mPain);
}
// postInvalidateDelayed(20);
invalidate();
}
private List<Point> getCenterPoints(double r, double g) {
List<Point> mCenterPoints = new ArrayList<Point>();
float mCenterPointGap = new Float(2 * r + g);
double a = (1-(1-time) * (1-time)) * mCenterPointGap;
if(time > 1){
time = 0;
//这里控制第一个圆点是在上面走还是下面走
if(flag == 0){
flag = 1;
}else{
flag = 0;
}
}else{
time = time + 0.025;
}
//后5个点只是平移效果
int y = mLayoutSize/2;
Float x2 = new Float((r + g)/2 + mCenterPointGap * 1 - a);
Float x3 = new Float((r + g)/2 + mCenterPointGap * 2 - a);
Float x4 = new Float((r + g)/2 + mCenterPointGap * 3 - a);
Float x5 = new Float((r + g)/2 + mCenterPointGap * 4 - a);
Float x6 = new Float((r + g)/2 + mCenterPointGap * 5 - a);
Double x1 = 0.5 * g + r + (1-(1-time) * (1-time)) * mCenterPointGap * 5;
//这里使用了x²+y²=r²
Double y1 = Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5)) + mLayoutSize * 0.5;
if(flag == 0){
y1 = mLayoutSize * 0.5 - Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5));
}
mCenterPoints.add(new Point(x1.intValue(),y1.intValue()));
mCenterPoints.add(new Point(x2.intValue(),y));
mCenterPoints.add(new Point(x3.intValue(),y));
mCenterPoints.add(new Point(x4.intValue(),y));
mCenterPoints.add(new Point(x5.intValue(),y));
mCenterPoints.add(new Point(x6.intValue(),y));
return mCenterPoints;
}

下面是完整代码

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
package com.example.dotspreloader;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Created by GreendaMi on 2016/9/17.
*/
public class DotsPreloader extends View{
public int mColor;
public Paint mPain = new Paint();
private int mLayoutSize = 100;
double time = 0;
int flag = 0;
public DotsPreloader(Context context) {
super(context);
}
public DotsPreloader(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.DotsPreloader);
mColor = a.getColor(R.styleable.DotsPreloader_Circlecolor,Color.WHITE);
a.recycle();
init();
}
public DotsPreloader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
mPain.setAntiAlias(false);
if (BuildConfig.DEBUG) Log.d("DotsPreloader", "mColor:" + mColor);
mPain.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
mLayoutSize = Math.min(widthSpecSize,heightSpecSize);
setMeasuredDimension(mLayoutSize, mLayoutSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
double radius = (mLayoutSize/6) * 0.3;
double gap = (mLayoutSize/6) * 0.4;
List<Point> mCenterPoints = getCenterPoints(radius,gap);
for(Point mCenterPoint : mCenterPoints){
canvas.drawCircle(mCenterPoint.x , mCenterPoint.y , (float) radius , mPain);
}
// postInvalidateDelayed(20);
invalidate();
}
private List<Point> getCenterPoints(double r, double g) {
List<Point> mCenterPoints = new ArrayList<Point>();
float mCenterPointGap = new Float(2 * r + g);
double a = (1-(1-time) * (1-time)) * mCenterPointGap;
if(time > 1){
time = 0;
if(flag == 0){
flag = 1;
}else{
flag = 0;
}
}else{
time = time + 0.025;
}
int y = mLayoutSize/2;
Float x2 = new Float((r + g)/2 + mCenterPointGap * 1 - a);
Float x3 = new Float((r + g)/2 + mCenterPointGap * 2 - a);
Float x4 = new Float((r + g)/2 + mCenterPointGap * 3 - a);
Float x5 = new Float((r + g)/2 + mCenterPointGap * 4 - a);
Float x6 = new Float((r + g)/2 + mCenterPointGap * 5 - a);
Double x1 = 0.5 * g + r + (1-(1-time) * (1-time)) * mCenterPointGap * 5;
Double y1 = Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5)) + mLayoutSize * 0.5;
if(flag == 0){
y1 = mLayoutSize * 0.5 - Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5));
}
mCenterPoints.add(new Point(x1.intValue(),y1.intValue()));
mCenterPoints.add(new Point(x2.intValue(),y));
mCenterPoints.add(new Point(x3.intValue(),y));
mCenterPoints.add(new Point(x4.intValue(),y));
mCenterPoints.add(new Point(x5.intValue(),y));
mCenterPoints.add(new Point(x6.intValue(),y));
return mCenterPoints;
}
}

在value/attr.xml中

1
2
3
<declare-styleable name="DotsPreloader">
<attr name="Circlecolor" format="color"></attr>
</declare-styleable>

布局文件中使用

1
2
3
4
5
6
7
8
引用自定义属性要加上下面这句话
xmlns:DotsPreloader="http://schemas.android.com/apk/res-auto"
<com.example.dotspreloader.DotsPreloader
android:layout_width="200dp"
android:layout_height="200dp"
DotsPreloader:Circlecolor="@color/myOrderColor"/>

到此一个最简单的自定义View就完成了,比较有困都难的地方就是坐标的计算,可怜了我小学的体育老师啊~~~

文章目录
|