Android画板开发(三) 撤销反撤销功能实现

Android画板开发(一) 基本画笔的实现
Android画板开发(二) 橡皮擦实现
Android画板开发(三) 撤销反撤销功能实现
Android画板开发(四) 添加背景和保存画板内容为图片
Android画板开发(五) 添加文本文字

一、分析

这篇将会讲解撤销反撤销功能的实现,先讨论一下这个原理是怎么样实现的。

每次撤回的内容,内容是怎么定义呢? 其实就是每一笔,每一笔作为撤回的内容,那每一笔怎么算呢,就是算手指从按下-移动-放开这一个过程就是一笔。

我们只需记录这个过程为一笔,然后用一个已画列表list列表来记录这个过程的paint画笔和路径path。

撤销的时候就把后面的一个数据移到另一个撤销列表

反撤销的时候,就把撤销列表的最后面那条数据移动到已画列表。

然后,还有一个重点,就是画笔的保存数量,上面说记录每一笔画笔,这当然是有个限度,不可能画了好几百笔都记录下来,这样子内存消耗很大的,所以超出显示画笔数量的时候,我们就把以前的画死在画板上。

这里写图片描述

基本原理是这样子的。接下来跟着我实现

二、实现

如何实现撤回功能

2.1 定义数据类

首先,需要一个bean类存储每一笔的数据,这里定义一个PaintData,里面需要定义个draw方法,因为撤销的时候,需要重新绘制。

data class PaintData(
        var mPaint: Paint,    //保存画笔

        var mPath: Path         //保存路径
) {
    /**
     * 撤销和反撤销之后 重新绘制
     * @param canvas 绘制的画布
     */
    fun draw(canvas: Canvas){
        canvas.drawPath(mPath,mPaint)
    }

}

2.2 修改清空画板方法

因为多了列表,所以清空画板的方法需要把列表也清除了

    /**
     * 清空画布
     * @param isClearList 时候清空数据列表
     */
    fun clear(isClearList:Boolean) {
        if(isClearList){
            mRevokedList.clear()
            mPaintedList.clear()
        }
        mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
        invalidate()
    }

2.3 实现撤销方法

在view定义两个列表,一个是已经画的内容列表,一个是撤销内容的列表

    //储存已经写的笔画
    private var mPaintedList: MutableList<PaintData> =  ArrayList<PaintData>()

    //已经撤销的列表
    private var mRevokedList: MutableList<PaintData> =  ArrayList<PaintData>()


添加固话层canvas和bitmap,超出记录的画笔就写死在固化层了

    //固化层,超出最大笔画就先绘制到这个层
    private lateinit var mHoldBitmap: Bitmap
    private lateinit var mHoldCanvas: Canvas
    
    //最多记录20画笔迹
    private val MAX_PAINT_RECORED = 20  
    
    
    //在测量的时候进行初始化:
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if(mBufferCanvas == null){
            mBufferBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)

            //canvas绘制的内容,将会在这个mBufferBitmap内
            mBufferCanvas = Canvas(mBufferBitmap)
        }


        if(mHoldCanvas == null){
            mHoldBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
            mHoldCanvas = Canvas(mHoldBitmap)
        }
    }
    
    

然后定义撤回的方法

    /**
     * 撤回一个笔迹
     * @return 是否撤回成功
     */
    fun revoked(){
        reDraw(mPaintedList)
    }

反撤销方法基本一致,只是改了个列表:

 
    /**
     * 反撤回一个笔迹
     */
    fun unRevoked(){
        reDraw(mRevokedList)
    }

然后重新绘制的方法为:

    /**
     * 重新绘制
     * @param paintList 需要操作的list
     */
    private fun reDraw(paintList:MutableList<PaintData>){

        if(paintList.size > 0){
            val paint = paintList.removeAt(paintList.size-1)
            if(paintList === mPaintedList){
                mRevokedList.add(paint)
            }else{
                mPaintedList.add(paint)
            }
            
            //清空缓存画板
            mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
            
            invalidate()

        }
    }

然后就是画笔的保存,在触摸按下的时候,进行画笔的保存

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when(event.action){
            MotionEvent.ACTION_DOWN -> {  //手指按下的时候
                //将起始点移动到当前坐标
                mPath.moveTo(event.x,event.y)

                //记录上次触摸的坐标,注意ACTION_DOWN方法只会执行一次
                preX = event.x
                preY = event.y

                //保存画笔
                mPaintedList.add(PaintData(Paint(mPaint),Path(mPath)))

            }
            MotionEvent.ACTION_MOVE -> {  //手指移动的时候

                //绘制圆滑曲线,即贝塞尔曲线,贝塞尔曲线这个知识自行了解
                mPaintedList.get(mPaintedList.size-1).mPath.quadTo(preX,preY,event.x,event.y)

                //重新绘制,会调用onDraw方法
                invalidate()

                preX = event.x
                preY = event.y
            }
            MotionEvent.ACTION_UP ->{

                //清除路径的内容
                mPath.reset()

            }
        }

        // true:告诉系统,这个触摸事件由我来处理
        // false:告诉系统,这个触摸事件我不处理,这时系统会把触摸事件传递给imageview的父节点
        return true
    }

最后绘制的时候:


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)


        //超出缓存的就固化到缓存bitmap
        while(mPaintedList.size > MAX_PAINT_RECORED){
            val paintData =  mPaintedList.removeAt(0)
            paintData.draw(mHoldCanvas)
        }

        //绘制固化的内容到缓存Canvas
        mBufferCanvas.drawBitmap(mHoldBitmap,0f,0f,null)

        //绘制记录的画笔
        for(paint in mPaintedList){
            //重新绘制每个path
            paint.draw(mBufferCanvas)
        }


        //画出缓存bitmap的内容
        canvas.drawBitmap(mBufferBitmap,0f,0f,null)

    }

最后清除画布的时候,需要把画笔列表也清除了:

    /**
     * 清空画布
     */
    fun clear() {
        mRevokedList.clear()
        mPaintedList.clear()
        mHoldCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
        mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
        invalidate()

    }

重点就是在于利用一个bean类来保存每笔的 画笔和路径,然后撤销时候重新绘制。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KeepStudya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值