Android画板开发之撤销反撤销功能
作者:tpnet 发布时间:2023-11-28 10:08:37
一、分析
这篇将会讲解撤销反撤销功能的实现,先讨论一下这个原理是怎么样实现的。
每次撤回的内容,内容是怎么定义呢? 其实就是每一笔,每一笔作为撤回的内容,那每一笔怎么算呢,就是算手指从按下-移动-放开这一个过程就是一笔。
我们只需记录这个过程为一笔,然后用一个已画列表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类来保存每笔的 画笔和路径,然后撤销时候重新绘制。
来源:https://blog.csdn.net/niubitianping/article/details/78408603
猜你喜欢
- Android ScrollView 下嵌套 ListView 或 GridView出现问题解决办法ScrollView 下嵌套 ListV
- ThreadLocal,直译为“线程本地”或“本地线程”,如果你真的这么认为,那就错了!其实,它就是一个容器,用于存放线程的局部变量,我认为
- 方法1:以textbox为例①:先设置textbox的属性Multiline为true②:组织好显示字符串:FistLine(第一行要显示的
- Java为什么不浪(long)学而时习之不亦说乎,继续温习Java。今天使用switch时,不小心写了如下代码,报错如下。 public s
- Java从json串中获取某个值java对象是不能直接传输,只有json对象 转成字符串 可以进行传输 故 传输中都是json进行的 接收到
- 命名空间using EnvDTE;using EnvDTE80;private DTE2 _applicationObject;
- 不是很难的知识,但是今天犯错了,记录一下什么是 stream 流我们在使用集合或数组对元素进行操作时往往会遇到这种情况:通过对不同类型的存储
- 方式一: 配置文件 application.propertiesserver.port=7788方式二: java启动命令# 以应用参数的方
- 作者:sparkdev出处:http://www.cnblogs.com/sparkdev/注意,本文所说的断点续传特指 HTTP 协议中的
- 以下实例演示了如何使用 equals ()方法来判断数组是否相等:import java.util.Arrays; public class
- 这篇文章主要介绍了java读取xml配置参数代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友
- 去公司面试,对方一口一个controller,一口一个service,dao,搞得我很紧张。其实都是很简单的东西,只是自己当时不知道罢了,接
- 定义可理解为 适配广泛的类型,即参数化类型,可以把类型像方法的参数那样进行传递。// 以ArrayList为示例// 泛型T可以是任意类pu
- java 同步、异步、阻塞和非阻塞分析概要:正常情况下,我们的程序以同步非阻塞的方式在运行。但是我们的程序总会出现一些耗时操作,比如复杂的计
- 前言经过前面对 Kotlin 的介绍,相信大家已经能对 Kotlin 有了一个基本的认识。 从这节开始,我就为大家讲解 Kotlin的方法以
- 在协程启动模式中已经知道async是可以返回结果的,但是只返回一个,那么在复杂场景下就会不够用了,所以Channel就出现了。1.认识Cha
- logback-classic 使用testCompile的打包今天使用gradle打包的时候logback-classic一直打包不进去。
- 前言:阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消
- 目录一、泛型类型二、为什么需要泛型三、类型擦除四、类型擦除的后遗症五、Kotlin 泛型六、上界约束七、类型通配符 & 星号投影八、
- 问题背景实际项目碰到一个上游服务商接口有10秒的查询限制(同个账号)。项目中有一个需求是要实时统计一些数据,一个应用下可能有多个相同的账号。