首页 > 代码库 > android项目 之 记事本(8) ----- 画板功能之撤销、恢复和清空

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"/>

         

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




       

 

 

 

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