灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:3083回复:0

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

楼主#
更多 发布于:2012-09-06 13:46

前段时间研究了下涂鸦功能的实现,其实单独的涂鸦实现起来还是挺简单的,关键的技术难点是撤销与重做功能的实现。但是这里暂时只说明下涂鸦功能的实现,高手勿喷哈,而且该功能在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.}


喜欢0 评分0
游客

返回顶部