新年到新年到,紅包搶不停。在我搶紅包的時候意外的發(fā)現(xiàn)了百度的福袋界面挺不錯的,于是抽時間專門寫篇文章來完成百度紅包界面吧。
當(dāng)然啦,這其實就是解鎖界面的進(jìn)化版本。不過其包含的知識點(diǎn)還是挺多的,寫篇博文記錄一下看看具體有哪些技術(shù)點(diǎn)啦。看看百度的效果圖:
1.編程思路
看看界面,不難發(fā)現(xiàn),其就是一個放入九張圖片的容器,繪制其實可以在其上面另創(chuàng)建一個透明View負(fù)責(zé)繪制線與圓圈。下面我們將介紹一下實現(xiàn)過程。
㈠自定義ViewGroup
我們知道,自定義ViewGroup一定需要實現(xiàn)其onLayout()方法。該方法是設(shè)置子View位置與尺寸的時候調(diào)用。還有一個onMeasure()方法,該方法是測量view及其內(nèi)容來確定view的寬度和高度。
㈡存儲其點(diǎn)與圓的位置及繪制參數(shù)
當(dāng)重回界面的時候,是不會保存上一次繪制界面的內(nèi)容,必須存儲以備重繪時候繪制到界面
㈢簡單的縮放動畫
㈣自定義View實現(xiàn)繪制界面
㈤繪制完成時,清除界面繪制內(nèi)容,并且保證不連接重復(fù)圖片
下面我們將完成這些步驟。
2.自定義ViewGroup
開始的任務(wù)就是將九張圖片平均分布到圖片的位置,顯示在手機(jī)界面中。其代碼如下:
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{ /** * 每個點(diǎn)區(qū)域的寬度 */ private int childWidth; /*** * 上下文 */ private Context context; /*** * 保存圖片點(diǎn)的位置 */ private List<LYJGesturePoint> list; /*** * 創(chuàng)建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); // 遍歷設(shè)置每個子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 ; // 定義點(diǎn)的左上角與右下角的坐標(biāo) 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.自定義點(diǎn)類
顧名思義,就是為了獲取點(diǎn)的相關(guān)的屬性,其中基礎(chǔ)屬性圖片左上角坐標(biāo)與右下角坐標(biāo),計算圖片中心位置以便獲取圖片中心點(diǎn)。狀態(tài)標(biāo)記,表示該點(diǎn)是否繪制到圖片。下面是其實體類:
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; //左上角坐標(biāo) private Point pointRightBottom; //右下角坐標(biāo) private int centerX; //圖片中心點(diǎn)X坐標(biāo) private int centerY; //圖片中心點(diǎn)Y坐標(biāo) private int pointState; //是否點(diǎn)擊了該圖片 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.自定義圓類
這個類較簡單就三個屬性而已(圓中心點(diǎn)坐標(biāo)及半徑),代碼如下:
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; //圓中心點(diǎn)X坐標(biāo) private int roundY; //圓中心點(diǎn)Y坐標(biāo) 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.實現(xiàn)自定義繪制類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坐標(biāo)的集合,用于判斷點(diǎn)是否在其中 */ private List<LYJGesturePoint> list; /*** * 記錄畫過的線 */ private List<Pair<LYJGesturePoint, LYJGesturePoint>> lineList; /*** * 記錄畫過的圓 */ private List<LYJCirclePoint> circlePoints; /** * 手指當(dāng)前在哪個Point內(nèi) */ 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); // 創(chuàng)建一個畫筆 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); // 設(shè)置位圖的寬高 canvas = new Canvas(); canvas.setBitmap(bitmap); paint.setStyle(Paint.Style.STROKE); // 設(shè)置非填充 paint.setStrokeWidth( 20 ); // 筆寬20像素 paint.setColor(Color.rgb( 245 , 142 , 33 )); // 設(shè)置默認(rèn)連線顏色 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: // 判斷當(dāng)前點(diǎn)擊的位置是處于哪個點(diǎn)之內(nèi) 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(); // 得到當(dāng)前移動位置是處于哪個點(diǎn)內(nèi) LYJGesturePoint pointAt = getPointAt(( int ) event.getX(), ( int ) event.getY()); if (currentPoint == null && pointAt == null ) { //你把手指按在屏幕滑動,如果終點(diǎn)與起點(diǎn)都不圖片那么返回 return true ; } else { // 代表用戶的手指移動到了點(diǎn)上 if (currentPoint == null ) { // 先判斷當(dāng)前的point是不是為null // 如果為空,那么把手指移動到的點(diǎn)賦值給currentPoint currentPoint = pointAt; // 把currentPoint這個點(diǎn)設(shè)置選中狀態(tài); currentPoint.setPointState(Constants.POINT_STATE_SELECTED); } } //如果移動到的點(diǎn)不為圖片區(qū)域或者移動到自己的地方,或者該圖片已經(jīng)為選中狀態(tài),直接畫直線就可以了 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 { //其他情況畫兩點(diǎn)相連直線,并且保存繪制圓與直線,并調(diào)用按下圖片的縮放動畫 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; //設(shè)置選中點(diǎn)為當(dāng)前點(diǎn)。 } invalidate(); //重繪 break ; case MotionEvent.ACTION_UP: clearScreenAndDrawList(); //防止多出一條沒有終點(diǎn)的直線 new Handler().postDelayed( new clearLineRunnable(), 1000 ); //1秒后清空繪制界面 invalidate(); //重繪 break ; default : break ; } return true ; } class clearLineRunnable implements Runnable { public void run() { // 清空保存點(diǎn)與圓的集合 lineList.clear(); circlePoints.clear(); // 重新繪制界面 clearScreenAndDrawList(); for (LYJGesturePoint p : list) { //設(shè)置其為初始化不選中狀態(tài) p.setPointState(Constants.POINT_STATE_NORMAL); } invalidate(); } } /** * 通過點(diǎn)的位置去集合里面查找這個點(diǎn)是包含在哪個Point里面的 * * @param x * @param y * @return 如果沒有找到,則返回null,代表用戶當(dāng)前移動的地方屬于點(diǎn)與點(diǎn)之間 */ private LYJGesturePoint getPointAt( int x, int y) { for (LYJGesturePoint point : list) { // 先判斷點(diǎn)是否在圖片的X坐標(biāo)內(nèi) int leftX = point.getPointLeftTop().x; int rightX = point.getPointRightBottom().x; if (!(x >= leftX && x < rightX)) { // 如果為假,則跳到下一個對比 continue ; } //在判斷點(diǎn)是否在圖片的Y坐標(biāo)內(nèi) int topY = point.getPointLeftTop().y; int bottomY = point.getPointRightBottom().y; if (!(y >= topY && y < bottomY)) { // 如果為假,則跳到下一個對比 continue ; } // 如果執(zhí)行到這,那么說明當(dāng)前點(diǎn)擊的點(diǎn)的位置在遍歷到點(diǎn)的位置這個地方 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創(chuàng)建出來的畫布 @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(bitmap, 0 , 0 , null ); } } |
這樣就可以得到如下界面效果(當(dāng)然反編譯百度錢包,并沒有百度錢包中的圖片,只好隨便找了一張圖片):