前段时间研究了下涂鸦功能的实现,其实单独的涂鸦实现起来还是挺简单的,关键的
技术难点是撤销与重做功能的实现。但是这里暂时只说明下涂鸦功能的实现,高手勿喷哈,而且该功能在
Android SDK提供的APIDemo当中就有的,但是如果能够将该地方的知识点搞懂的话,我认为View画图基本上是难不倒你了,特别是里面为什么要用一个中间的Bitmap。老规矩,还是先看看效果图吧:
代码如下:
1.package cn.ych.tuya;
2.
3.import
java.io.File;
4.import
java.io.FileNotFoundException;
5.import
java.io.FileOutputStream;
6.import
java.io.IOException;
7.import
java.util.ArrayList;
8.import
java.util.Iterator;
9.import
java.util.List;
10.
11.import
Android.content.Context;
12.import
Android.graphics.Bitmap;
13.import
Android.graphics.Canvas;
14.import
Android.graphics.Paint;
15.import
Android.graphics.Path;
16.import
Android.graphics.Bitmap.CompressFormat;
17.import
Android.os.Environment;
18.import
Android.view.MotionEvent;
19.import
Android.view.View;
20./**
21.*
22.* @category: View实现涂鸦、撤销以及重做功能
23.* @author: 锋翼
24.* @link:
www.apkstory.com25.* @date: 2012.1.4
26.*
27.*/
28.public class TuyaView extends View {
29.
30.private Bitmap mBitmap;
31.private Canvas mCanvas;
32.private Path mPath;
33.private Paint mBitmapPaint;// 画布的画笔
34.private Paint mPaint;// 真实的画笔
35.private float mX, mY;//临时点坐标
36.private static final float TOUCH_TOLERANCE = 4;
37.
38.private int screenWidth, screenHeight;// 屏幕長寬
39.
40.public TuyaView(Context context, int w, int h) {
41. super(context);
42. screenWidth = w;
43. screenHeight = h;
44.
45. mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
46. Bitmap.Config.ARGB_8888);
47. // 保存一次一次绘制出来的图形
48. mCanvas = new Canvas(mBitmap);
49.
50. mBitmapPaint = new Paint(Paint.DITHER_FLAG);
51. mPaint = new Paint();
52. mPaint.setAntiAlias(true);
53. mPaint.setStyle(Paint.Style.STROKE);
54. mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘
55. mPaint.setStrokeCap(Paint.Cap.SQUARE);// 形状
56. mPaint.setStrokeWidth(5);// 画笔宽度
57.
58.}
59.
60.@Override
61.public void onDraw(Canvas canvas) {
62. canvas.drawColor(0xFFAAAAAA);
63. // 将前面已经画过得显示出来
64. canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
65. if (mPath != null) {
66. // 实时的显示
67. canvas.drawPath(mPath, mPaint);
68. }
69.}
70.
71.private void touch_start(float x, float y) {
72. mPath.moveTo(x, y);
73. mX = x;
74. mY = y;
75.}
76.
77.private void touch_move(float x, float y) {
78. float dx = Math.abs(x - mX);
79. float dy = Math.abs(mY - y);
80.
81. 触摸间隔大于阈值才绘制路径
82. if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
83. // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
84. mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
85. mX = x;
86. mY = y;
87. }
88.}
89.
90.private void touch_up() {
91. mPath.lineTo(mX, mY);
92. mCanvas.drawPath(mPath, mPaint);
93. }
94.
95.@Override
96.public boolean onTouchEvent(MotionEvent event) {
97. float x = event.getX();
98. float y = event.getY();
99.
100. switch (event.getAction()) {
101. case MotionEvent.ACTION_DOWN:
102. // 每次down下去重新new一个Path
103. mPath = new Path();
104.
105. touch_start(x, y);
106. invalidate();
107. break;
108. case MotionEvent.ACTION_MOVE:
109. touch_move(x, y);
110. invalidate();
111. break;
112. case MotionEvent.ACTION_UP:
113. touch_up();
114. invalidate();
115. break;
116. }
117. return true;
118.}
119.
120.}
上一讲当中,已经讲解了普通View实现涂鸦的功能,现在再来给涂鸦添加上撤销与重做的功能吧。撤销与重做在很多地方都是很重要的功能,比如PS里面、
word里面等等,而且大部分童鞋都能够想到要实现该功能应该需要用到堆栈,对于一些大牛的话可能就直接想到设计模式上面去了,比如命令模式就可以解决撤销与重做的问题。我们这里要讲解的是利用集合来完成该功能,其实也就是模拟栈,我相信你懂得。
老规矩,先上效果图:
代码如下:
1.package cn.ych.tuya;
2.
3.import
java.io.File;
4.import
java.io.FileNotFoundException;
5.import
java.io.FileOutputStream;
6.import
java.io.IOException;
7.import
java.util.ArrayList;
8.import
java.util.Iterator;
9.import
java.util.List;
10.
11.import
Android.content.Context;
12.import
Android.graphics.Bitmap;
13.import
Android.graphics.Canvas;
14.import
Android.graphics.Paint;
15.import
Android.graphics.Path;
16.import
Android.graphics.Bitmap.CompressFormat;
17.import
Android.os.Environment;
18.import
Android.view.MotionEvent;
19.import
Android.view.View;
20./**
21.*
22.* @category: View实现涂鸦、撤销以及重做功能
23.* @author: 锋翼
24.* @link:
www.apkstory.com25.* @date: 2012.1.4
26.*
27.*/
28.public class TuyaView extends View {
29.
30.private Bitmap mBitmap;
31.private Canvas mCanvas;
32.private Path mPath;
33.private Paint mBitmapPaint;// 画布的画笔
34.private Paint mPaint;// 真实的画笔
35.private float mX, mY;// 临时点坐标
36.private static final float TOUCH_TOLERANCE = 4;
37.
38.// 保存Path路径的集合,用List集合来模拟栈
39.private static List<DrawPath> savePath;
40.// 记录Path路径的对象
41.private DrawPath dp;
42.
43.private int screenWidth, screenHeight;// 屏幕長寬
44.
45.private class DrawPath {
46. public Path path;// 路径
47. public Paint paint;// 画笔
48.}
49.
50.public TuyaView(Context context, int w, int h) {
51. super(context);
52. screenWidth = w;
53. screenHeight = h;
54.
55. mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
56. Bitmap.Config.ARGB_8888);
57. // 保存一次一次绘制出来的图形
58. mCanvas = new Canvas(mBitmap);
59.
60. mBitmapPaint = new Paint(Paint.DITHER_FLAG);
61. mPaint = new Paint();
62. mPaint.setAntiAlias(true);
63. mPaint.setStyle(Paint.Style.STROKE);
64. mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘
65. mPaint.setStrokeCap(Paint.Cap.SQUARE);// 形状
66. mPaint.setStrokeWidth(5);// 画笔宽度
67.
68. savePath = new ArrayList<DrawPath>();
69.}
70.
71.@Override
72.public void onDraw(Canvas canvas) {
73. canvas.drawColor(0xFFAAAAAA);
74. // 将前面已经画过得显示出来
75. canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
76. if (mPath != null) {
77. // 实时的显示
78. canvas.drawPath(mPath, mPaint);
79. }
80.}
81.
82.private void touch_start(float x, float y) {
83. mPath.moveTo(x, y);
84. mX = x;
85. mY = y;
86.}
87.
88.private void touch_move(float x, float y) {
89. float dx = Math.abs(x - mX);
90. float dy = Math.abs(mY - y);
91. if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
92. // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
93. mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
94. mX = x;
95. mY = y;
96. }
97.}
98.
99.private void touch_up() {
100. mPath.lineTo(mX, mY);
101. mCanvas.drawPath(mPath, mPaint);
102. //将一条完整的路径保存下来(相当于入栈操作)
103. savePath.add(dp);
104. mPath = null;// 重新置空
105.}
106./**
107. * 撤销的核心思想就是将画布清空,
108. * 将保存下来的Path路径最后一个移除掉,
109. * 重新将路径画在画布上面。
110. */
111.public void undo() {
112. mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
113. Bitmap.Config.ARGB_8888);
114. mCanvas.setBitmap(mBitmap);// 重新设置画布,相当于清空画布
115. // 清空画布,但是如果图片有背景的话,则使用上面的重新初始化的方法,用该方法会将背景清空掉...
116. if (savePath != null ;; savePath.size() > 0) {
117. // 移除最后一个path,相当于出栈操作
118. savePath.remove(savePath.size() - 1);
119.
120. Iterator<DrawPath> iter = savePath.iterator();
121. while (iter.hasNext()) {
122. DrawPath drawPath = iter.next();
123. mCanvas.drawPath(drawPath.path, drawPath.paint);
124. }
125. invalidate();// 刷新
126.
127. /*在这里保存图片纯粹是为了方便,保存图片进行验证*/
128. String fileUrl = Environment.getExternalStorageDirectory()
129. .toString() + "/
Android/data/test.png";
130. try {
131. FileOutputStream fos = new FileOutputStream(new File(fileUrl));
132. mBitmap.compress(CompressFormat.PNG, 100, fos);
133. fos.flush();
134. fos.close();
135. } catch (FileNotFoundException e) {
136. e.printStackTrace();
137. } catch (IOException e) {
138. e.printStackTrace();
139. }
140.
141. }
142.}
143./**
144. * 重做的核心思想就是将撤销的路径保存到另外一个集合里面(栈),
145. * 然后从redo的集合里面取出最顶端对象,
146. * 画在画布上面即可。
147. */
148.public void redo(){
149. //如果撤销你懂了的话,那就试试重做吧。
150.}
151.
152.@Override
153.public boolean onTouchEvent(MotionEvent event) {
154. float x = event.getX();
155. float y = event.getY();
156.
157. switch (event.getAction()) {
158. case MotionEvent.ACTION_DOWN:
159. // 每次down下去重新new一个Path
160. mPath = new Path();
161. //每一次记录的路径对象是不一样的
162. dp = new DrawPath();
163. dp.path = mPath;
164. dp.paint = mPaint;
165. touch_start(x, y);
166. invalidate();
167. break;
168. case MotionEvent.ACTION_MOVE:
169. touch_move(x, y);
170. invalidate();
171. break;
172. case MotionEvent.ACTION_UP:
173. touch_up();
174. invalidate();
175. break;
176. }
177. return true;
178.}
179.}