一、引言
想實現一個空白的畫板,上面可以畫出手滑動的軌跡,就這么一個小需求。一般就來講就兩種實現方式,view或者surfaceview。下面看看兩種是如何實現的。
二、實現原理
先簡單說一下實現原理:
(1)用一張白色的bitmap作為畫板
(2)用canvas在bitmap上畫線
(3)為了畫出平滑的曲線,要用canvas的drawpath(path,paint)方法。
(4)同時使用貝塞爾曲線來使曲線更加平滑
三、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
184
185
186
187
188
189
190
191
192
193
194
195
196
|
package picturegame.view; import android.annotation.suppresslint; import android.content.context; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.canvas; import android.graphics.color; import android.graphics.paint; import android.graphics.paint.style; import android.graphics.path; import android.util.attributeset; import android.view.motionevent; import android.view.view; import com.winton.picturegame.r; /** * @classname: gameview * @description: todo(這里用一句話描述這個類的作用) * @author winton [email protected] * @date 2015年9月26日 上午8:54:37 * */ public class gameview extends view{ private paint paint = null ; // private bitmap originalbitmap = null ; //原始圖 private bitmap new1bitmap = null ; private bitmap new2bitmap = null ; private float clickx = 0 ; private float clicky= 0 ; private float startx= 0 ; private float starty= 0 ; private boolean ismove = true ; private boolean isclear = false ; private int color =color.red; //默認畫筆顏色 private float strokewidth =20f; //默認畫筆寬度 path mpath; public gameview(context context) { this (context, null ); // todo auto-generated constructor stub } public gameview(context context,attributeset atts) { this (context,atts, 0 ); // todo auto-generated constructor stub } @suppresswarnings ( "static-access" ) public gameview(context context,attributeset atts, int defstyle) { super (context,atts,defstyle); // todo auto-generated constructor stub originalbitmap = bitmapfactory.decoderesource(getresources(), r.drawable.default_pic).copy(bitmap.config.argb_8888, true ); //白色的畫板 new1bitmap=originalbitmap.createbitmap(originalbitmap); mpath= new path(); } //清楚 @suppresswarnings ( "static-access" ) public void clear(){ isclear = true ; new2bitmap=originalbitmap.createbitmap(originalbitmap); invalidate(); //重置 } public void setstrokewidth( float width){ this .strokewidth=width; initpaint(); } @override protected void ondraw(canvas canvas) { // todo auto-generated method stub super .ondraw(canvas); canvas.drawbitmap(writer(new1bitmap), 0 , 0 , null ); } @suppresslint ( "clickableviewaccessibility" ) @override public boolean ontouchevent(motionevent event) { // todo auto-generated method stub clickx =event.getx(); clicky=event.gety(); if (event.getaction()==motionevent.action_down){ //手指點下屏幕時觸發 startx=clickx; starty=clicky; mpath.reset(); mpath.moveto(clickx, clicky); // ismove =false; // invalidate(); // return true; } else if (event.getaction()==motionevent.action_move){ //手指移動時觸發 float dx=math.abs(clickx-startx); float dy=math.abs(clicky-starty); // if(dx>=3||dy>=3){ //設置貝塞爾曲線的操作點為起點和終點的一半 float cx = (clickx + startx) / 2 ; float cy = (clicky + starty) / 2 ; mpath.quadto(startx,starty, cx, cy); startx=clickx; starty=clicky; // } // ismove =true; // invalidate(); // return true; } invalidate(); return true ; } /** * @title: writer * @description: todo(這里用一句話描述這個方法的作用) * @param @param pic * @param @return 設定文件 * @return bitmap 返回類型 * @throws */ public bitmap writer(bitmap pic){ initpaint(); canvas canvas = null ; if (isclear){ canvas= new canvas(new2bitmap); } else { canvas= new canvas(pic); } //canvas.drawline(startx, starty, clickx, clicky, paint);//畫線 canvas.drawpath(mpath, paint); if (isclear){ return new2bitmap; } return pic; } private void initpaint(){ paint = new paint(); //初始化畫筆 paint.setstyle(style.stroke); //設置為畫線 paint.setantialias( true ); //設置畫筆抗鋸齒 paint.setcolor(color); //設置畫筆顏色 paint.setstrokewidth(strokewidth); //設置畫筆寬度 } /** * @title: setcolor * @description: todo(設置畫筆顏色) * @param @param color 設定文件 * @return void 返回類型 * @throws */ public void setcolor( int color){ this .color=color; initpaint(); } public bitmap getpaint(){ return new1bitmap; } } |
看一下效果:
基本滿足需求
三、surfaceview實現
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
|
package picturegame.view; import android.content.context; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.canvas; import android.graphics.paint; import android.graphics.paint.style; import android.graphics.path; import android.util.attributeset; import android.view.motionevent; import android.view.surfaceholder; import android.view.surfaceholder.callback; import android.view.surfaceview; import com.winton.picturegame.r; public class gameviewsurface extends surfaceview implements callback,runnable{ /** 控制游戲更新循環 **/ boolean mrunning = false ; /**控制游戲循環**/ boolean misrunning = false ; /**每50幀刷新一次屏幕**/ public static final int time_in_frame = 50 ; private int paintcolor=android.graphics.color.white; //默認畫筆顏色為黑色 private float paintwidth=2f; //默認畫筆寬度 private style paintstyle=style.stroke; //默認畫筆風格 private int paintalph= 255 ; //默認不透明 private path mpath; //軌跡 private paint mpaint; //畫筆 private float startx= 0 .0f; //初始x private float starty= 0 .0f; //初始y private surfaceholder surfaceholder; public canvas mcanvas; public boolean first= true ; bitmap bg; public gameviewsurface(context context){ this (context, null ); } public gameviewsurface(context context,attributeset attrs){ this (context,attrs, 0 ); } public gameviewsurface(context context, attributeset attrs, int defstyle) { super (context, attrs, defstyle); // todo auto-generated constructor stub this .setfocusable( true ); //設置當前view擁有觸摸事件 surfaceholder=getholder(); surfaceholder.addcallback( this ); mpath= new path(); initpaint(); bg = bitmapfactory.decoderesource(getresources(), r.drawable.default_pic).copy(bitmap.config.argb_8888, true ); //白色的畫板 } /** * @title: initpaint * @description: todo(初始化畫筆) * @param 設定文件 * @return void 返回類型 * @throws */ private void initpaint(){ mpaint= new paint(); mpaint.setantialias( true ); //消除鋸齒 mpaint.setcolor(paintcolor); //畫筆顏色 mpaint.setalpha(paintalph); //畫筆透明度 mpaint.setstyle(paintstyle); //設置畫筆風格 mpaint.setstrokewidth(paintwidth); //設置畫筆寬度 } public void dodraw(){ mcanvas=surfaceholder.lockcanvas(); mcanvas.drawpath(mpath, mpaint); //繪制 surfaceholder.unlockcanvasandpost(mcanvas); } @override public boolean ontouchevent(motionevent event) { // todo auto-generated method stub switch (event.getaction()) { case motionevent.action_down: //手接觸屏幕時觸發 dotouchdown(event); break ; case motionevent.action_move: //手滑動時觸發 dotouchmove(event); break ; case motionevent.action_up: //手抬起時觸發 break ; default : break ; } return true ; } /** * @title: dotouchdown * @description: todo(手觸摸到屏幕時需要做的事情) * @param @param event 設定文件 * @return void 返回類型 * @throws */ private void dotouchdown(motionevent event){ float touchx=event.getx(); float touchy=event.gety(); startx=touchx; starty=touchy; mpath.reset(); mpath.moveto(touchx, touchy); } /** * @title: dotouchmove * @description: todo(手在屏幕上滑動時要做的事情) * @param @param event 設定文件 * @return void 返回類型 * @throws */ private void dotouchmove(motionevent event){ float touchx=event.getx(); float touchy=event.gety(); float dx=math.abs(touchx-startx); //移動的距離 float dy =math.abs(touchy-startx); //移動的距離 if (dx> 3 ||dy> 3 ){ float cx=(touchx+startx)/ 2 ; float cy=(touchy+starty)/ 2 ; mpath.quadto(startx, starty, cx, cy); startx=touchx; starty=touchy; } } public void setpaintcolor( int paintcolor) { this .paintcolor = paintcolor; initpaint(); } public void setpaintwidth( float paintwidth) { this .paintwidth = paintwidth; initpaint(); } public void setpaintstyle(style paintstyle) { this .paintstyle = paintstyle; initpaint(); } public void setpaintalph( int paintalph) { this .paintalph = paintalph; initpaint(); } @override public void run() { // todo auto-generated method stub while (misrunning) { /** 取得更新游戲之前的時間 **/ long starttime = system.currenttimemillis(); /** 在這里加上線程安全鎖 **/ synchronized (surfaceholder){ dodraw(); } /** 取得更新游戲結束的時間 **/ long endtime = system.currenttimemillis(); /** 計算出游戲一次更新的毫秒數 **/ int difftime = ( int ) (endtime - starttime); /** 確保每次更新時間為50幀 **/ while (difftime <= time_in_frame) { difftime = ( int ) (system.currenttimemillis() - starttime); /** 線程等待 **/ thread.yield(); } } } @override public void surfacecreated(surfaceholder holder) { // todo auto-generated method stub mcanvas =surfaceholder.lockcanvas(); mcanvas.drawbitmap(bg, 0 , 0 , null ); surfaceholder.unlockcanvasandpost(mcanvas); misrunning= true ; new thread( this ).start(); } @override public void surfacechanged(surfaceholder holder, int format, int width, int height) { // todo auto-generated method stub } @override public void surfacedestroyed(surfaceholder holder) { // todo auto-generated method stub misrunning = false ; } } |
看看運行效果:
當我不設置背景時是沒問題的,但使用了背景就不停的閃爍了,不知道有沒同學知道的,可以說一下。
大家可以閱讀本文《解決android surfaceview繪制觸摸軌跡閃爍問題的方法》,或許對大家的學習有所幫助。
五、總結
兩種方式都是可以實現的,而且仔細對比發現surfaceview響應的速度比view快很多,view想必與surfaceview更容易實現。
view用于顯示被動更新的動畫,即需要操作才會更新的動畫,而surfaceview則用于主動更新的動畫,如在界面上顯示一個奔跑的小狗。
view更新界面是在ui主線程。surfaceview是自己起一個線程更新界面。
以上就是本文的全部內容,希望大家喜歡。