View实现涂鸦、撤销以及重做功能

时间:2023-03-09 17:44:40
View实现涂鸦、撤销以及重做功能
  1. import java.io.File;
  2. import java.io.FileNotFoundException;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. import android.content.Context;
  9. import android.graphics.Bitmap;
  10. import android.graphics.Canvas;
  11. import android.graphics.Paint;
  12. import android.graphics.Path;
  13. import android.graphics.Bitmap.CompressFormat;
  14. import android.os.Environment;
  15. import android.view.MotionEvent;
  16. import android.view.View;
  17. /**
  18. * View实现涂鸦、撤销以及重做功能
  19. */
  20. public class TuyaView extends View {
  21. private Bitmap mBitmap;
  22. private Canvas mCanvas;
  23. private Path mPath;
  24. private Paint mBitmapPaint;// 画布的画笔
  25. private Paint mPaint;// 真实的画笔
  26. private float mX, mY;// 临时点坐标
  27. private static final float TOUCH_TOLERANCE = 4;
  28. // 保存Path路径的集合,用List集合来模拟栈
  29. private static List<DrawPath> savePath;
  30. // 记录Path路径的对象
  31. private DrawPath dp;
  32. private int screenWidth, screenHeight;
  33. private class DrawPath {
  34. public Path path;// 路径
  35. public Paint paint;// 画笔
  36. }
  37. public TuyaView(Context context, int w, int h) {
  38. super(context);
  39. screenWidth = w;
  40. screenHeight = h;
  41. mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
  42. // 保存一次一次绘制出来的图形
  43. mCanvas = new Canvas(mBitmap);
  44. mBitmapPaint = new Paint(Paint.DITHER_FLAG);
  45. mPaint = new Paint();
  46. mPaint.setAntiAlias(true);
  47. mPaint.setStyle(Paint.Style.STROKE);
  48. mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘
  49. mPaint.setStrokeCap(Paint.Cap.ROUND);// 形状
  50. mPaint.setStrokeWidth(5);// 画笔宽度
  51. savePath = new ArrayList<DrawPath>();
  52. }
  53. @Override
  54. public void onDraw(Canvas canvas) {
  55. canvas.drawColor(0xFFAAAAAA);
  56. // 将前面已经画过得显示出来
  57. canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
  58. if (mPath != null) {
  59. // 实时的显示
  60. canvas.drawPath(mPath, mPaint);
  61. }
  62. }
  63. private void touch_start(float x, float y) {
  64. mPath.moveTo(x, y);
  65. mX = x;
  66. mY = y;
  67. }
  68. private void touch_move(float x, float y) {
  69. float dx = Math.abs(x - mX);
  70. float dy = Math.abs(mY - y);
  71. if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
  72. // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
  73. mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
  74. mX = x;
  75. mY = y;
  76. }
  77. }
  78. private void touch_up() {
  79. mPath.lineTo(mX, mY);
  80. mCanvas.drawPath(mPath, mPaint);
  81. //将一条完整的路径保存下来(相当于入栈操作)
  82. savePath.add(dp);
  83. mPath = null;// 重新置空
  84. }
  85. /**
  86. * 撤销的核心思想就是将画布清空,
  87. * 将保存下来的Path路径最后一个移除掉,
  88. * 重新将路径画在画布上面。
  89. */
  90. public void undo() {
  91. if (savePath != null && savePath.size() > 0) {
  92. savePath.remove(savePath.size() - 1);
  93. redrawOnBitmap();
  94. }
  95. }
  96. /**
  97. * 重做
  98. */
  99. public void redo(){
  100. if (savePath != null && savePath.size() > 0) {
  101. savePath.clear();
  102. redrawOnBitmap();
  103. }
  104. }
  105. private void redrawOnBitmap(){
  106. mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
  107. Bitmap.Config.ARGB_8888);
  108. mCanvas.setBitmap(mBitmap);// 重新设置画布,相当于清空画布
  109. Iterator<DrawPath> iter = savePath.iterator();
  110. while (iter.hasNext()) {
  111. DrawPath drawPath = iter.next();
  112. mCanvas.drawPath(drawPath.path, drawPath.paint);
  113. }
  114. invalidate();// 刷新
  115. }
  116. @Override
  117. public boolean onTouchEvent(MotionEvent event) {
  118. float x = event.getX();
  119. float y = event.getY();
  120. switch (event.getAction()) {
  121. case MotionEvent.ACTION_DOWN:
  122. // 每次down下去重新new一个Path
  123. mPath = new Path();
  124. //每一次记录的路径对象是不一样的
  125. dp = new DrawPath();
  126. dp.path = mPath;
  127. dp.paint = mPaint;
  128. touch_start(x, y);
  129. invalidate();
  130. break;
  131. case MotionEvent.ACTION_MOVE:
  132. touch_move(x, y);
  133. invalidate();
  134. break;
  135. case MotionEvent.ACTION_UP:
  136. touch_up();
  137. invalidate();
  138. break;
  139. }
  140. return true;
  141. }
  142. public void saveToSDCard(){
  143. String fileUrl = Environment.getExternalStorageDirectory()
  144. .toString() + "/android/data/test.png";
  145. try {
  146. FileOutputStream fos = new FileOutputStream(new File(fileUrl));
  147. mBitmap.compress(CompressFormat.PNG, 100, fos);
  148. fos.flush();
  149. fos.close();
  150. } catch (FileNotFoundException e) {
  151. e.printStackTrace();
  152. } catch (IOException e) {
  153. e.printStackTrace();
  154. }
  155. }
  156. }
  1. import android.app.Activity;
  2. import android.os.Bundle;
  3. import android.util.DisplayMetrics;
  4. import android.util.Log;
  5. import android.view.KeyEvent;
  6. public class TuyaActivity extends Activity {
  7. private TuyaView tuyaView = null;
  8. @Override
  9. public void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. DisplayMetrics dm = new DisplayMetrics();
  12. getWindowManager().getDefaultDisplay().getMetrics(dm);
  13. tuyaView = new TuyaView(this, dm.widthPixels, dm.heightPixels);
  14. setContentView(tuyaView);
  15. }
  16. @Override
  17. public boolean onKeyDown(int keyCode, KeyEvent event) {
  18. if (keyCode == KeyEvent.KEYCODE_BACK) {// 返回键
  19. tuyaView.undo();
  20. return true;
  21. }else if(keyCode == KeyEvent.KEYCODE_MENU){//MENU
  22. tuyaView.redo();
  23. return true;
  24. }
  25. return super.onKeyDown(keyCode, event);
  26. }
  27. }

View实现涂鸦、撤销以及重做功能 

  • Tuya.rar (234.4 KB)
  • 下载次数: 202