一、引言
想实现一个空白的画板,上面可以画出手滑动的轨迹,就这么一个小需求。一般就来讲就两种实现方式,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 winton_by@126.com * @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是自己起一个线程更新界面。
以上就是本文的全部内容,希望大家喜欢。