上一节讨论了手写功能中的删除、恢复和清空功能,那么,画板也就是涂鸦怎么能没有撤销、恢复与清空的功能呢,今天就来实现下。
终于会做gif图了,看下面的动态图,是不是和QQ白板功能很像。
之前就简单的只实现了在画板上绘图的功能,所以当时将自定义view直接写在了activity中,这一节由于要实现撤销、恢复及清空的功能,所以将分离出来,单独写成了一个java文件PaintView.java,在该自定义view中实现画板的基本操作。
因为将自定义view单独分离出来,所以需要改到activity的布局文件:如下
activity_paint.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.example.notes.PaintView
android:id="@+id/paint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
></com.example.notes.PaintView>
<GridView
android:id="@+id/paintBottomMenu"
android:layout_width="match_parent"
android:layout_height="45dp"
android:numColumns="auto_fit"
android:background="@drawable/navigationbar_bg"
android:horizontalSpacing="10dp"
android:layout_alignParentBottom="true"
></GridView>
</RelativeLayout>
其中com.example.notes.PaintView为自定义view
这节要实现的操作有撤销,恢复,清空和保存,下面分别讨论三个操作的主要思想:
要实现撤销与恢复,这里有个前提,就是要将每次绘制的路径存入栈中,这里是存入List中。
1. 撤销功能:
前提:将每次绘制的路径存入List中,即存入savePath中
步骤:
1) 将画布清空,这里可以使用画成的初始化操作
2) 将savePath中的最后一个路径保存到另一个List中,即deletePath(用于恢复),并且将此路径从savePath中删除
3) 取出savePath中的所有的路径,重绘在画布上面
2. 恢复功能:
前提:将每次撤销的路径存入List中,即存入deletePath中
步骤:
1) 取出deletePath中的最后一个路径,并保存到savePath中
2) 将取出的路径重绘在画布上
3) 从deletePath中删除最后一个路径
3.清空功能:
1) 直接清空画布,调用画布的初始化操作
2) 将两个保存路径的List清空
4.保存功能:
1) 获得当前的时间,以时间作为绘图文件名(避免覆盖)
2) 因为画布是建立在Bitmap上的,所以将绘制好的BItMap保存在SD卡上
3) 返回绘制文件的路径
以上操作并不难,重点是要将画布上绘制的每个路径保存在List中,这里需要注意,当按下时,应该重新创建路径,抬起时,应该将按下时创建的路径设置为null,只有这样,才能保存每个路径。
下面直接给出自定义view的代码,里面也有注释:
package com.example.notes;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
/**
*
* @category: View实现涂鸦、撤销以及重做功能
* @author: jesson20121020
* @link: blog.csdn.net/jesson20121020
* @date: 2014.9.19
*
*/
public class PaintView extends View {
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;
private Bitmap mBitmap;
private Paint mPaint;
private ArrayList<DrawPath> savePath;
private ArrayList<DrawPath> deletePath;
private DrawPath dp;
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private int bitmapWidth;
private int bitmapHeight;
public PaintView(Context c) {
super(c);
//得到屏幕的分辨率
DisplayMetrics dm = new DisplayMetrics();
((Activity) c).getWindowManager().getDefaultDisplay().getMetrics(dm);
bitmapWidth = dm.widthPixels;
bitmapHeight = dm.heightPixels - 2 * 45;
initCanvas();
savePath = new ArrayList<DrawPath>();
deletePath = new ArrayList<DrawPath>();
}
public PaintView(Context c, AttributeSet attrs) {
super(c,attrs);
//得到屏幕的分辨率
DisplayMetrics dm = new DisplayMetrics();
((Activity) c).getWindowManager().getDefaultDisplay().getMetrics(dm);
bitmapWidth = dm.widthPixels;
bitmapHeight = dm.heightPixels - 2 * 45;
initCanvas();
savePath = new ArrayList<DrawPath>();
deletePath = new ArrayList<DrawPath>();
}
//初始化画布
public void initCanvas(){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFF00FF00);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(10);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
//画布大小
mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
Bitmap.Config.RGB_565);
mCanvas = new Canvas(mBitmap); //所有mCanvas画的东西都被保存在了mBitmap中
mCanvas.drawColor(Color.WHITE);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); //显示旧的画布
if (mPath != null) {
// 实时的显示
canvas.drawPath(mPath, mPaint);
}
}
//路径对象
class DrawPath{
Path path;
Paint paint;
}
/**
* 撤销的核心思想就是将画布清空,
* 将保存下来的Path路径最后一个移除掉,
* 重新将路径画在画布上面。
*/
public void undo(){
System.out.println(savePath.size()+"--------------");
if(savePath != null && savePath.size() > 0){
//调用初始化画布函数以清空画布
initCanvas();
//将路径保存列表中的最后一个元素删除 ,并将其保存在路径删除列表中
DrawPath drawPath = savePath.get(savePath.size() - 1);
deletePath.add(drawPath);
savePath.remove(savePath.size() - 1);
//将路径保存列表中的路径重绘在画布上
Iterator<DrawPath> iter = savePath.iterator(); //重复保存
while (iter.hasNext()) {
DrawPath dp = iter.next();
mCanvas.drawPath(dp.path, dp.paint);
}
invalidate();// 刷新
}
}
/**
* 恢复的核心思想就是将撤销的路径保存到另外一个列表里面(栈),
* 然后从redo的列表里面取出最顶端对象,
* 画在画布上面即可
*/
public void redo(){
if(deletePath.size() > 0){
//将删除的路径列表中的最后一个,也就是最顶端路径取出(栈),并加入路径保存列表中
DrawPath dp = deletePath.get(deletePath.size() - 1);
savePath.add(dp);
//将取出的路径重绘在画布上
mCanvas.drawPath(dp.path, dp.paint);
//将该路径从删除的路径列表中去除
deletePath.remove(deletePath.size() - 1);
invalidate();
}
}
/*
* 清空的主要思想就是初始化画布
* 将保存路径的两个List清空
* */
public void removeAllPaint(){
//调用初始化画布函数以清空画布
initCanvas();
invalidate();//刷新
savePath.clear();
deletePath.clear();
}
/*
* 保存所绘图形
* 返回绘图文件的存储路径
* */
public String saveBitmap(){
//获得系统当前时间,并以该时间作为文件名
SimpleDateFormat formatter = new SimpleDateFormat ("yyyyMMddHHmmss");
Date curDate = new Date(System.currentTimeMillis());//获取当前时间
String str = formatter.format(curDate);
String paintPath = "";
str = str + "paint.png";
File dir = new File("/sdcard/notes/");
File file = new File("/sdcard/notes/",str);
if (!dir.exists()) {
dir.mkdir();
}
else{
if(file.exists()){
file.delete();
}
}
try {
FileOutputStream out = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
//保存绘图文件路径
paintPath = "/sdcard/notes/" + str;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return paintPath;
}
private void touch_start(float x, float y) {
mPath.reset();//清空path
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
//mPath.quadTo(mX, mY, x, y);
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);//源代码是这样写的,可是我没有弄明白,为什么要这样?
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
savePath.add(dp);
mPath = null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath = new Path();
dp = new DrawPath();
dp.path = mPath;
dp.paint = mPaint;
touch_start(x, y);
invalidate(); //清屏
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}
接下来,就是在Activity中调用自定义的View中的这些操作方法:
private PaintView paintView;
private GridView paint_bottomMenu;paint_bottomMenu = (GridView)findViewById(R.id.paintBottomMenu); paint_bottomMenu.setOnItemClickListener(new MenuClickEvent()); paintView = (PaintView)findViewById(R.id.paint_layout);
//设置菜单项监听器
class MenuClickEvent implements OnItemClickListener{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Intent intent;
switch(position){
//画笔大小
case 0:
break;
//颜色
case 1:
break;
//撤销
case 2:
paintView.undo();
break;
//恢复
case 3:
paintView.redo();
break;
//清空
case 4 :
paintView.removeAllPaint();
break;
default :
break;
}
}
}
这里因为有保存文件,所以要有读写SD卡的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
至此,已完成了画板的撤销,恢复,清空,保存的功能,至于其他的功能,以后慢慢实现。
android项目 之 记事本(8) ----- 画板功能之撤销、恢复和清空
原文地址:http://blog.csdn.net/jesson20121020/article/details/39397015