android项目 之 记事本(8) ----- 画板功能之撤销、恢复和清空

上一节讨论了手写功能中的删除、恢复和清空功能,那么,画板也就是涂鸦怎么能没有撤销、恢复与清空的功能呢,今天就来实现下。

终于会做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"/>

至此,已完成了画板的撤销,恢复,清空,保存的功能,至于其他的功能,以后慢慢实现。

时间: 2024-08-19 16:36:37

android项目 之 记事本(8) ----- 画板功能之撤销、恢复和清空的相关文章

android项目 之 记事本(9) ----- 画板功能之橡皮擦、画笔大小和画笔颜色

上节已实现了画板中的绘制,删除,恢复入清空的功能,还有橡皮擦,设置画笔大小和画笔颜色没有实现,这节就将这几个功能逐一实现. 先看效果图: 以上图中,第一个展示了设置画笔颜色的功能,第二个展示了设置画笔大小的颜色,而第三个则展示了橡皮擦的功能,细心的可以发现,这节将图标颜色设置为了蓝色,并且,增加了最左边的按钮(其实,就是在gridview中多增加了一个item). 下面分别讨论,橡皮擦,设置画笔大小,设置画笔颜色的主要思想:     1. 橡皮擦功能:                    基本

android项目 之 记事本(7)----- 手写功能之删除、恢复和清空

上一节,为记事本添加了手写的功能,但是没有实现底部按钮的各种功能,这节就先实现撤销,恢复和清空的功用. 因为不会录制屏幕成gif图片,所以就以图片形式给出吧,不是很形象,凑合着看: 显然,需要为底部GridView的添加item单击事件: private GridView paint_bottomMenu; paint_bottomMenu = (GridView)findViewById(R.id.paintBottomMenu); paint_bottomMenu.setOnItemClic

android项目 之 记事本(6)----- 添加手写

想必大家都用过QQ的白板功能,里面主要有两项,一个是涂鸦功能,其实类似于上节的画板功能,而另一个就是手写,那记事本怎么能没有这个功能呢,今天就来为我们的记事本添加手写功能. 先上图,看看效果: 看了效果图,是不是心动了呢?那就赶紧着手做吧,其实,手写功能并不难实现,大体就是全屏书写,定时发送handle消息,更新activity. 实现手写功能的主要步骤: 1. 自定义两个View,一个是TouchView,用于在上面画图,另一个是EditText,用于将手写的字显示在其中,并且,要将两个自定义

android项目 之 记事本(15) ----- 保存手写及绘图

本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020 之前,忘了写如何将手写和绘图保存,现在补上. 首先看如何保存绘图,先看效果图: 因为记事本的绘图功能主要用到了画布,而在构建画布时,指定了Bitmap,也就是说在画布上的所画的东西都被保存在了Bitmap中,因此,我们只要保存该Bitmap,就可以将我们的所绘制的图形以图片的形式保存,主要代码如下: /* * 保存所绘图形 * 返回绘图文件的存储路径 * */ public S

android项目 之 记事本(10) ----- 手写功能之设置画笔大小和画笔颜色

上一节,实现了画板的所有功能,包括设置画笔大小,设置画笔颜色,橡皮擦等功能,而手写,也可以添加设置笔迹大小和颜色的功能,这节就顺势实现手写的调整笔迹大小和调整笔迹的颜色. 先看图: 其实,手写和画板的这些功能都类似,直接复用之前的代码就行,原理如下:      1. 设置画笔大小的功能:                    1)初始化画笔. 2)设置画笔的大小为所选择的大小. 3)用一个变量记住当前画笔的大小,用于在进行其他操作后还保持之前设置的画笔大小. 2. 设置画笔颜色的功能:     

android项目 之 记事本(11) ----- 添加数据库

这节就来为我们的记事本添加数据库支持,这样,就可以在添加记事后将其保存在数据库中,方便下次浏览,修改,删除等. 先看效果图: 三张图片分别演示了保存记事,查看记事,删除记事. 对于数据库而言,无非就是涉及到数据库的创建,增删改查. 为了将数据库的操作封装起来,单独写了一个类,如下: 数据库操作 DatabaseOperation.java [java] view plaincopy import android.content.Context; import android.database.C

android项目 之 记事本(12) ----- 图片的等比例缩放及给图片添加边框

本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020 在Android的UI开发中经常会遇到图片的缩放,就比如记事本,现在的图片都比较大,如果将原图不经缩放直接放在屏幕上,则会占满整个屏幕,而且有时图片会比屏幕还大,这时就不能完全的显示整个图片,所以,必须要进行缩放,但在缩放时,该如何缩放呢,长和宽的缩放比例设置为多少合适呢,为了保持原图的纵横比,所以要最好的方法就是约束缩放比例,也就是等比例缩放,相信大家都用过PS中的缩放图片的

android项目 之 记事本(5)----- 添加录音

有时,需要将重要的事以语音的形式记录下来,这个在生活中很常见,今天就为记事本添加录音的功能,先看图: 其实在第一节界面设计中,可以看出记事本的功能选项,其中底部选项栏的第三个就是添加录音.   主要步骤如下:               1.   录音Activity的界面设计.               2.   在语音按钮的监听器中添加Intent,跳转到录音Activity,这里同样是用startActivityforResult(Intent intent,int requestCod

android项目 之 记事本(13) ----- 查看图片及播放录音

本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020 今天就来实现下查看图片及录音的功能,在编辑或者浏览记事时,点击图片,打开一个自定义Activity(当然了,也可以调用系统的图库来查看)来查看所添加的图片的原始图片,而不是缩放后的图片,同理,用自定义Activity来查看录音文件,实现播放录音的功能.上图: 从图中也可以看出,我们首先要创建两个Activity,当然了,布局文件也是少不了的,如下: activity_show_