Android简单涂鸦以及撤销、重做的实现方法
3443 点击·0 回帖
![]() | ![]() | |
![]() | 前段时间研究了下涂鸦功能的实现,其实单独的涂鸦实现起来还是挺简单的,关键的技术难点是撤销与重做功能的实现。但是这里暂时只说明下涂鸦功能的实现,高手勿喷哈,而且该功能在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.com 25.* @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.com 25.* @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.} | |
![]() | ![]() |