新年到新年到,红包抢不停。在我抢红包的时候意外的发现了百度的福袋界面挺不错的,于是抽时间专门写篇文章来完成百度红包界面吧。
当然啦,这其实就是解锁界面的进化版本。不过其包含的知识点还是挺多的,写篇博文记录一下看看具体有哪些技术点啦。看看百度的效果图:
1.编程思路
看看界面,不难发现,其就是一个放入九张图片的容器,绘制其实可以在其上面另创建一个透明View负责绘制线与圆圈。下面我们将介绍一下实现过程。
㈠自定义ViewGroup
我们知道,自定义ViewGroup一定需要实现其onLayout()方法。该方法是设置子View位置与尺寸的时候调用。还有一个onMeasure()方法,该方法是测量view及其内容来确定view的宽度和高度。
㈡存储其点与圆的位置及绘制参数
当重回界面的时候,是不会保存上一次绘制界面的内容,必须存储以备重绘时候绘制到界面
㈢简单的缩放动画
㈣自定义View实现绘制界面
㈤绘制完成时,清除界面绘制内容,并且保证不连接重复图片
下面我们将完成这些步骤。
2.自定义ViewGroup
开始的任务就是将九张图片平均分布到图片的位置,显示在手机界面中。其代码如下:
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
|
public class LYJViewGroup extends ViewGroup implements LYJGestureDrawline.OnAnimationCallback{ /** * 每个点区域的宽度 */ private int childWidth; /*** * 上下文 */ private Context context; /*** * 保存图片点的位置 */ private List<LYJGesturePoint> list; /*** * 创建view使其在ViewGroup之上。 */ private LYJGestureView gestureDrawline; private int baseNum = 5 ; public LYJViewGroup(Context context) { super (context); this .context = context; this .list = new ArrayList<>(); DisplayMetrics metric = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric); childWidth = metric.widthPixels / 3 ; // 屏幕宽度(像素) addChild(); // 初始化一个可以画线的view gestureDrawline = new LYJGestureView(context, list); gestureDrawline.setAnimationCallback( this ); } public void setParentView(ViewGroup parent){ // 得到屏幕的宽度 DisplayMetrics metric = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric); int width = metric.widthPixels; LayoutParams layoutParams = new LayoutParams(width, width); this .setLayoutParams(layoutParams); gestureDrawline.setLayoutParams(layoutParams); parent.addView( this ); parent.addView(gestureDrawline); } @Override protected void onLayout( boolean changed, int l, int t, int r, int b) { for ( int i = 0 ; i < getChildCount(); i++) { //第几行 int rowspan = i / 3 ; //第几列 int column = i % 3 ; android.view.View v = getChildAt(i); v.layout(column * childWidth + childWidth / baseNum, rowspan * childWidth + childWidth / baseNum, column * childWidth + childWidth - childWidth / baseNum, rowspan * childWidth + childWidth - childWidth / baseNum); } } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); // 遍历设置每个子view的大小 for ( int i = 0 ; i < getChildCount(); i++) { View v = getChildAt(i); v.measure(widthMeasureSpec, heightMeasureSpec); } } private void addChild() { for ( int i = 0 ; i < 9 ; i++) { ImageView image = new ImageView(context); image.setBackgroundResource(R.drawable.marker); this .addView(image); invalidate(); // 第几行 int rowspan = i / 3 ; // 第几列 int column = i % 3 ; // 定义点的左上角与右下角的坐标 int leftX = column * childWidth + childWidth / baseNum; int topY = rowspan * childWidth + childWidth / baseNum; int rightX = column * childWidth + childWidth - childWidth / baseNum; int bottomY = rowspan * childWidth + childWidth - childWidth / baseNum; LYJGesturePoint p = new LYJGesturePoint(leftX, topY, rightX,bottomY,i); this .list.add(p); } } @Override public void startAnimationImage( int i) { Animation animation= AnimationUtils.loadAnimation(getContext(), R.anim.gridlayout_child_scale_anim); getChildAt(i).startAnimation(animation); } } |
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
26
27
28
29
30
31
32
33
34
35
36
|
public class LYJGesturePoint { private Point pointLeftTop; //左上角坐标 private Point pointRightBottom; //右下角坐标 private int centerX; //图片中心点X坐标 private int centerY; //图片中心点Y坐标 private int pointState; //是否点击了该图片 private int num; public int getNum() { return num; } public int getPointState() { return pointState; } public void setPointState( int pointState) { this .pointState = pointState; } public Point getPointLeftTop() { return pointLeftTop; } public Point getPointRightBottom() { return pointRightBottom; } public LYJGesturePoint( int left, int top, int right, int bottom, int i){ this .pointLeftTop= new Point(left,top); this .pointRightBottom= new Point(right,bottom); this .num=i; } public int getCenterX() { this .centerX=( this .pointLeftTop.x+ this .pointRightBottom.x)/ 2 ; return centerX; } public int getCenterY() { this .centerY=( this .pointLeftTop.y+ this .pointRightBottom.y)/ 2 ; return centerY; } } |
4.自定义圆类
这个类较简单就三个属性而已(圆中心点坐标及半径),代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class LYJCirclePoint { private int roundX; //圆中心点X坐标 private int roundY; //圆中心点Y坐标 private int radiu; //圆半径 public int getRadiu() { return radiu; } public int getRoundX() { return roundX; } public int getRoundY() { return roundY; } public LYJCirclePoint( int roundX, int roundY, int radiu){ this .roundX=roundX; this .roundY=roundY; this .radiu=radiu; } } |
5.实现自定义绘制类View
代码如下:
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
|
public class LYJGestureView extends android.view.View { /*** * 声明直线画笔 */ private Paint paint; /*** * 声明圆圈画笔 */ private Paint circlePaint; /*** * 画布 */ private Canvas canvas; /*** * 位图 */ private Bitmap bitmap; /*** * 装有各个view坐标的集合,用于判断点是否在其中 */ private List<LYJGesturePoint> list; /*** * 记录画过的线 */ private List<Pair<LYJGesturePoint, LYJGesturePoint>> lineList; /*** * 记录画过的圆 */ private List<LYJCirclePoint> circlePoints; /** * 手指当前在哪个Point内 */ private LYJGesturePoint currentPoint; /*** * 手指按下动画 */ private OnAnimationCallback animationCallback; public interface OnAnimationCallback{ public void startAnimationImage( int i); } public void setAnimationCallback(OnAnimationCallback animationCallback) { this .animationCallback = animationCallback; } public LYJGestureView(Context context, List<LYJGesturePoint> list){ super (context); Log.i(getClass().getName(), "GestureDrawline" ); paint = new Paint(Paint.DITHER_FLAG); // 创建一个画笔 circlePaint= new Paint(Paint.DITHER_FLAG); DisplayMetrics metric = new DisplayMetrics(); ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(metric); Log.i(getClass().getName(), "widthPixels" + metric.widthPixels); Log.i(getClass().getName(), "heightPixels" + metric.heightPixels); bitmap = Bitmap.createBitmap(metric.widthPixels, metric.heightPixels, Bitmap.Config.ARGB_8888); // 设置位图的宽高 canvas = new Canvas(); canvas.setBitmap(bitmap); paint.setStyle(Paint.Style.STROKE); // 设置非填充 paint.setStrokeWidth( 20 ); // 笔宽20像素 paint.setColor(Color.rgb( 245 , 142 , 33 )); // 设置默认连线颜色 paint.setAntiAlias( true ); // 不显示锯齿 circlePaint.setStyle(Paint.Style.FILL); circlePaint.setStrokeWidth( 1 ); circlePaint.setAntiAlias( true ); circlePaint.setColor(Color.rgb( 245 , 142 , 33 )); this .list = list; this .lineList = new ArrayList<>(); this .circlePoints= new ArrayList<>(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: // 判断当前点击的位置是处于哪个点之内 currentPoint = getPointAt(( int ) event.getX(), ( int ) event.getY()); if (currentPoint != null ) { currentPoint.setPointState(Constants.POINT_STATE_SELECTED); this .animationCallback.startAnimationImage(currentPoint.getNum()); canvas.drawCircle(currentPoint.getCenterX(), currentPoint.getCenterY(), 20 , circlePaint); circlePoints.add( new LYJCirclePoint(currentPoint.getCenterX(),currentPoint.getCenterY(), 20 )); } invalidate(); break ; case MotionEvent.ACTION_MOVE: clearScreenAndDrawList(); // 得到当前移动位置是处于哪个点内 LYJGesturePoint pointAt = getPointAt(( int ) event.getX(), ( int ) event.getY()); if (currentPoint == null && pointAt == null ) { //你把手指按在屏幕滑动,如果终点与起点都不图片那么返回 return true ; } else { // 代表用户的手指移动到了点上 if (currentPoint == null ) { // 先判断当前的point是不是为null // 如果为空,那么把手指移动到的点赋值给currentPoint currentPoint = pointAt; // 把currentPoint这个点设置选中状态; currentPoint.setPointState(Constants.POINT_STATE_SELECTED); } } //如果移动到的点不为图片区域或者移动到自己的地方,或者该图片已经为选中状态,直接画直线就可以了 if (pointAt == null || currentPoint.equals(pointAt) || Constants.POINT_STATE_SELECTED == pointAt.getPointState()){ canvas.drawCircle(currentPoint.getCenterX(), currentPoint.getCenterY(), 20 , circlePaint); circlePoints.add( new LYJCirclePoint(currentPoint.getCenterX(), currentPoint.getCenterY(), 20 )); canvas.drawLine(currentPoint.getCenterX(), currentPoint.getCenterY(), event.getX(), event.getY(), paint); } else { //其他情况画两点相连直线,并且保存绘制圆与直线,并调用按下图片的缩放动画 canvas.drawCircle(pointAt.getCenterX(),pointAt.getCenterY(), 20 ,circlePaint); circlePoints.add( new LYJCirclePoint(pointAt.getCenterX(), pointAt.getCenterY(), 20 )); this .animationCallback.startAnimationImage(pointAt.getNum()); pointAt.setPointState(Constants.POINT_STATE_SELECTED); canvas.drawLine(currentPoint.getCenterX(), currentPoint.getCenterY(), pointAt.getCenterX(), pointAt.getCenterY(), paint); Pair<LYJGesturePoint, LYJGesturePoint> pair = new Pair<>(currentPoint, pointAt); lineList.add(pair); currentPoint=pointAt; //设置选中点为当前点。 } invalidate(); //重绘 break ; case MotionEvent.ACTION_UP: clearScreenAndDrawList(); //防止多出一条没有终点的直线 new Handler().postDelayed( new clearLineRunnable(), 1000 ); //1秒后清空绘制界面 invalidate(); //重绘 break ; default : break ; } return true ; } class clearLineRunnable implements Runnable { public void run() { // 清空保存点与圆的集合 lineList.clear(); circlePoints.clear(); // 重新绘制界面 clearScreenAndDrawList(); for (LYJGesturePoint p : list) { //设置其为初始化不选中状态 p.setPointState(Constants.POINT_STATE_NORMAL); } invalidate(); } } /** * 通过点的位置去集合里面查找这个点是包含在哪个Point里面的 * * @param x * @param y * @return 如果没有找到,则返回null,代表用户当前移动的地方属于点与点之间 */ private LYJGesturePoint getPointAt( int x, int y) { for (LYJGesturePoint point : list) { // 先判断点是否在图片的X坐标内 int leftX = point.getPointLeftTop().x; int rightX = point.getPointRightBottom().x; if (!(x >= leftX && x < rightX)) { // 如果为假,则跳到下一个对比 continue ; } //在判断点是否在图片的Y坐标内 int topY = point.getPointLeftTop().y; int bottomY = point.getPointRightBottom().y; if (!(y >= topY && y < bottomY)) { // 如果为假,则跳到下一个对比 continue ; } // 如果执行到这,那么说明当前点击的点的位置在遍历到点的位置这个地方 return point; } return null ; } /** * 清掉屏幕上所有的线,然后画出集合里面的线 */ private void clearScreenAndDrawList() { canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); for (Pair<LYJGesturePoint, LYJGesturePoint> pair : lineList) { canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(), pair.second.getCenterX(), pair.second.getCenterY(), paint); // 画线 } for (LYJCirclePoint lyjCirclePoint : circlePoints){ canvas.drawCircle(lyjCirclePoint.getRoundX(),lyjCirclePoint.getRoundY(), lyjCirclePoint.getRadiu(),circlePaint); } } //绘制用bitmap创建出来的画布 @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(bitmap, 0 , 0 , null ); } } |
这样就可以得到如下界面效果(当然反编译百度钱包,并没有百度钱包中的图片,只好随便找了一张图片):