• Android简单涂鸦以及撤销、重做的实现方法


    前段时间研究了下涂鸦功能的实现,其实单独的涂鸦实现起来还是挺简单的,关键的技术难点是撤销与重做功能的实现。但是这里暂时只说明下涂鸦功能的实现,高手勿喷哈,而且该功能在Android SDK提供的APIDemo当中就有的,但是如果能够将该地方的知识点搞懂的话,我认为View画图基本上是难不倒你了,特别是里面为什么要用一个中间的Bitmap。老规矩,还是先看看效果图吧:
    1.jpg


    代码如下:

    1. package cn.ych.tuya;
    2. import java.io.File;
    3. import java.io.FileNotFoundException;
    4. import java.io.FileOutputStream;
    5. import java.io.IOException;
    6. import java.util.ArrayList;
    7. import java.util.Iterator;
    8. import java.util.List;
    9. import android.content.Context;
    10. import android.graphics.Bitmap;
    11. import android.graphics.Canvas;
    12. import android.graphics.Paint;
    13. import android.graphics.Path;
    14. import android.graphics.Bitmap.CompressFormat;
    15. import android.os.Environment;
    16. import android.view.MotionEvent;
    17. import android.view.View;
    18. /**
    19. *
    20. * @category: View实现涂鸦、撤销以及重做功能
    21. * @author: 锋翼
    22. * @link: www.apkstory.com
    23. * @date: 2012.1.4
    24. *
    25. */
    26. public class TuyaView extends View {
    27. private Bitmap mBitmap;
    28. private Canvas mCanvas;
    29. private Path mPath;
    30. private Paint mBitmapPaint;// 画布的画笔
    31. private Paint mPaint;// 真实的画笔
    32. private float mX, mY;//临时点坐标
    33. private static final float TOUCH_TOLERANCE = 4;
    34. private int screenWidth, screenHeight;// 屏幕長寬
    35. public TuyaView(Context context, int w, int h) {
    36.   super(context);
    37.   screenWidth = w;
    38.   screenHeight = h;
    39.   mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
    40.     Bitmap.Config.ARGB_8888);
    41.   // 保存一次一次绘制出来的图形
    42.   mCanvas = new Canvas(mBitmap);
    43.   mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    44.   mPaint = new Paint();
    45.   mPaint.setAntiAlias(true);
    46.   mPaint.setStyle(Paint.Style.STROKE);
    47.   mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘
    48.   mPaint.setStrokeCap(Paint.Cap.SQUARE);// 形状
    49.   mPaint.setStrokeWidth(5);// 画笔宽度
    50. }
    51. @Override
    52. public void onDraw(Canvas canvas) {
    53.   canvas.drawColor(0xFFAAAAAA);
    54.   // 将前面已经画过得显示出来
    55.   canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
    56.   if (mPath != null) {
    57.    // 实时的显示
    58.    canvas.drawPath(mPath, mPaint);
    59.   }
    60. }
    61. private void touch_start(float x, float y) {
    62.   mPath.moveTo(x, y);
    63.   mX = x;
    64.   mY = y;
    65. }
    66. private void touch_move(float x, float y) {
    67.   float dx = Math.abs(x - mX);
    68.   float dy = Math.abs(mY - y);
    69.   触摸间隔大于阈值才绘制路径
    70.   if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
    71.    // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
    72.    mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
    73.    mX = x;
    74.    mY = y;
    75.   }
    76. }
    77. private void touch_up() {
    78.   mPath.lineTo(mX, mY);
    79.   mCanvas.drawPath(mPath, mPaint);
    80.   }
    81. @Override
    82. public boolean onTouchEvent(MotionEvent event) {
    83.   float x = event.getX();
    84.   float y = event.getY();
    85.   switch (event.getAction()) {
    86.   case MotionEvent.ACTION_DOWN:
    87.    // 每次down下去重新new一个Path
    88.    mPath = new Path();
    89.    touch_start(x, y);
    90.    invalidate();
    91.    break;
    92.   case MotionEvent.ACTION_MOVE:
    93.    touch_move(x, y);
    94.    invalidate();
    95.    break;
    96.   case MotionEvent.ACTION_UP:
    97.    touch_up();
    98.    invalidate();
    99.    break;
    100.   }
    101.   return true;
    102. }
    103. }
    复制代码

    上一讲当中,已经讲解了普通View实现涂鸦的功能,现在再来给涂鸦添加上撤销与重做的功能吧。撤销与重做在很多地方都是很重要的功能,比如PS里面、Word里面等等,而且大部分童鞋都能够想到要实现该功能应该需要用到堆栈,对于一些大牛的话可能就直接想到设计模式上面去了,比如命令模式就可以解决撤销与重做的问题。我们这里要讲解的是利用集合来完成该功能,其实也就是模拟栈,我相信你懂得。

    老规矩,先上效果图:
    代码如下:

    1. package cn.ych.tuya;
    2. import java.io.File;
    3. import java.io.FileNotFoundException;
    4. import java.io.FileOutputStream;
    5. import java.io.IOException;
    6. import java.util.ArrayList;
    7. import java.util.Iterator;
    8. import java.util.List;
    9. import android.content.Context;
    10. import android.graphics.Bitmap;
    11. import android.graphics.Canvas;
    12. import android.graphics.Paint;
    13. import android.graphics.Path;
    14. import android.graphics.Bitmap.CompressFormat;
    15. import android.os.Environment;
    16. import android.view.MotionEvent;
    17. import android.view.View;
    18. /**
    19. *
    20. * @category: View实现涂鸦、撤销以及重做功能
    21. * @author: 锋翼
    22. * @link: www.apkstory.com
    23. * @date: 2012.1.4
    24. *
    25. */
    26. public class TuyaView extends View {
    27. private Bitmap mBitmap;
    28. private Canvas mCanvas;
    29. private Path mPath;
    30. private Paint mBitmapPaint;// 画布的画笔
    31. private Paint mPaint;// 真实的画笔
    32. private float mX, mY;// 临时点坐标
    33. private static final float TOUCH_TOLERANCE = 4;
    34. // 保存Path路径的集合,用List集合来模拟栈
    35. private static List<DrawPath> savePath;
    36. // 记录Path路径的对象
    37. private DrawPath dp;
    38. private int screenWidth, screenHeight;// 屏幕長寬
    39. private class DrawPath {
    40.   public Path path;// 路径
    41.   public Paint paint;// 画笔
    42. }
    43. public TuyaView(Context context, int w, int h) {
    44.   super(context);
    45.   screenWidth = w;
    46.   screenHeight = h;
    47.   mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
    48.     Bitmap.Config.ARGB_8888);
    49.   // 保存一次一次绘制出来的图形
    50.   mCanvas = new Canvas(mBitmap);
    51.   mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    52.   mPaint = new Paint();
    53.   mPaint.setAntiAlias(true);
    54.   mPaint.setStyle(Paint.Style.STROKE);
    55.   mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘
    56.   mPaint.setStrokeCap(Paint.Cap.SQUARE);// 形状
    57.   mPaint.setStrokeWidth(5);// 画笔宽度
    58.   savePath = new ArrayList<DrawPath>();
    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. private void touch_start(float x, float y) {
    71.   mPath.moveTo(x, y);
    72.   mX = x;
    73.   mY = y;
    74. }
    75. private void touch_move(float x, float y) {
    76.   float dx = Math.abs(x - mX);
    77.   float dy = Math.abs(mY - y);
    78.   if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
    79.    // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
    80.    mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
    81.    mX = x;
    82.    mY = y;
    83.   }
    84. }
    85. private void touch_up() {
    86.   mPath.lineTo(mX, mY);
    87.   mCanvas.drawPath(mPath, mPaint);
    88.   //将一条完整的路径保存下来(相当于入栈操作)
    89.   savePath.add(dp);
    90.   mPath = null;// 重新置空
    91. }
    92. /**
    93.   * 撤销的核心思想就是将画布清空,
    94.   * 将保存下来的Path路径最后一个移除掉,
    95.   * 重新将路径画在画布上面。
    96.   */
    97. public void undo() {
    98.   mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
    99.     Bitmap.Config.ARGB_8888);
    100.   mCanvas.setBitmap(mBitmap);// 重新设置画布,相当于清空画布
    101.   // 清空画布,但是如果图片有背景的话,则使用上面的重新初始化的方法,用该方法会将背景清空掉...
    102.   if (savePath != null && savePath.size() > 0) {
    103.    // 移除最后一个path,相当于出栈操作
    104.    savePath.remove(savePath.size() - 1);
    105.    Iterator<DrawPath> iter = savePath.iterator();
    106.    while (iter.hasNext()) {
    107.     DrawPath drawPath = iter.next();
    108.     mCanvas.drawPath(drawPath.path, drawPath.paint);
    109.    }
    110.    invalidate();// 刷新
    111.    
    112.    /*在这里保存图片纯粹是为了方便,保存图片进行验证*/
    113.    String fileUrl = Environment.getExternalStorageDirectory()
    114.      .toString() + "/android/data/test.png";
    115.    try {
    116.     FileOutputStream fos = new FileOutputStream(new File(fileUrl));
    117.     mBitmap.compress(CompressFormat.PNG, 100, fos);
    118.     fos.flush();
    119.     fos.close();
    120.    } catch (FileNotFoundException e) {
    121.     e.printStackTrace();
    122.    } catch (IOException e) {
    123.     e.printStackTrace();
    124.    }
    125.   }
    126. }
    127. /**
    128.   * 重做的核心思想就是将撤销的路径保存到另外一个集合里面(栈),
    129.   * 然后从redo的集合里面取出最顶端对象,
    130.   * 画在画布上面即可。
    131.   */
    132. public void redo(){
    133.   //如果撤销你懂了的话,那就试试重做吧。
    134. }
    135. @Override
    136. public boolean onTouchEvent(MotionEvent event) {
    137.   float x = event.getX();
    138.   float y = event.getY();
    139.   switch (event.getAction()) {
    140.   case MotionEvent.ACTION_DOWN:
    141.    // 每次down下去重新new一个Path
    142.    mPath = new Path();
    143.    //每一次记录的路径对象是不一样的
    144.    dp = new DrawPath();
    145.    dp.path = mPath;
    146.    dp.paint = mPaint;
    147.    touch_start(x, y);
    148.    invalidate();
    149.    break;
    150.   case MotionEvent.ACTION_MOVE:
    151.    touch_move(x, y);
    152.    invalidate();
    153.    break;
    154.   case MotionEvent.ACTION_UP:
    155.    touch_up();
    156.    invalidate();
    157.    break;
    158.   }
    159.   return true;
    160. }
    161. }
    复制代码
  • 相关阅读:
    websocket初体验(能传文字和图片)
    展开折叠效果 height未知 transition无效
    微信小程序自定义键盘
    微信小程序 selectComponent 值为null
    css 斜线 animation
    【转】怎样在ubuntu12.04下创建一个启动器
    以ontouch为例说明android事件发送机制
    谈谈移动应用设计——从一个普通开发者的角度
    Launch error: Failed to connect to remote VM. Connection refused.的解决办法
    Beyond compare代码比较工具。
  • 原文地址:https://www.cnblogs.com/zhwl/p/2400491.html
Copyright © 2020-2023  润新知