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

         

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




       

 

 

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。