Android画板开发之橡皮擦功能
作者:tpnet 发布时间:2022-10-23 02:48:52
在上一篇实现了简单的画板功能, 这篇实现橡皮擦功能,首先分析一下应该如何实现,
在Andriod有个图像混合(Xfermode)概念,利用这个概念我们就可以实现橡皮擦功能。
一、Xfermode
Paint有一个方法setXfermode(Xfermode),这个方法设置图像的混合模式。参数有三个子类:
AvoidXfermode
PixelXorXfermode
PorterDuffXfermode
前面两个因为不支持硬件加速在API 16已经已经过时弃用了。 简单讲一下第三个。
1.1 PorterDuffXfermode
该类有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),参数就是设置图像的混合模式,下面这张图片形象地说明了各种模式的作用
我们的做橡皮擦的时候,就是用到了PorterDuff.Mode.CLEAR这个模式清除图像,所以说橡皮擦也是Path,只是绘制的模式不一样了。
二、实现
在上一篇的文章中,实现了最简单笔画画板,就是只有一个画笔模式,所以首先添加一个橡皮擦的绘制模式。
companion object {
const val EDIT_MODE_PEN = 0x1L //画笔模式
const val EDIT_MODE_ERASER = 0x2L //橡皮擦模式
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(EDIT_MODE_PEN, EDIT_MODE_ERASER)
annotation class EditMode
//当前编辑模式默认为画笔模式
@EditMode
private var mMode: Long = EDIT_MODE_PEN
/**
* 设置画笔模式
*/
fun setModel(@EditMode model:Long){
mMode = model
when(model){
EDIT_MODE_PEN -> {
//画线
mPaint.xfermode = null
}
EDIT_MODE_ERASER ->{
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
}
}
然后捋一下整个流程:
画笔模式,在onTouch时候画出Path,绘制到view上
然后切换到橡皮擦模式,画出Path,clear擦掉原来的内容
再来回切换绘制
现在重点是解决第2点,一个Path怎么做到不改变原来的path基础上换个绘制模式继续画呢?
如果你考虑第2点的话,效果是这样子的:
What the fuck?(黑人问号) 这什么情况? 其实是因为path只有一条,一直没改变。所以,引入缓存Canvas和缓存Bitmap,添加两个变量:
//想要绘制的内容先绘制到这个增加的canvas对应的bitmap上,
// 写完后再把这个bitmap的ARGB信息一次提交给上下文的canvas去绘制
private lateinit var mBufferBitmap: Bitmap
private lateinit var mBufferCanvas: Canvas
然后在onMeasure中进行初始化:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if(mBufferCanvas == null){
mBufferBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
//canvas绘制的内容,将会在这个mBufferBitmap内
mBufferCanvas = Canvas(mBufferBitmap)
}
}
然后在onTouchEvent方面里面手指移动的时候,我们在缓存Canvas里面进行绘制path:
MotionEvent.ACTION_MOVE -> { //手指移动的时候
//绘制圆滑曲线,即贝塞尔曲线,贝塞尔曲线这个知识自行了解
mPath.quadTo(preX,preY,event.x,event.y)
//在缓存里面绘制
mBufferCanvas.drawPath(mPath,mPaint)
//重新绘制,会调用onDraw方法
invalidate()
preX = event.x
preY = event.y
}
然后onDraw的时候,就把缓存的Canvas的bitmap当前view的Canvas:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//画出缓存bitmap的内容
canvas.drawBitmap(mBufferBitmap,0f,0f,null)
}
就可以了,看看完整的代码100多行:
class TPEraserView(context: Context, attr: AttributeSet) : View(context,attr) {
companion object {
const val EDIT_MODE_PEN = 0x1L //画笔模式
const val EDIT_MODE_ERASER = 0x2L //橡皮擦模式
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(EDIT_MODE_PEN, EDIT_MODE_ERASER)
annotation class EditMode
//当前编辑模式默认为画笔模式
@EditMode
private var mMode: Long = EDIT_MODE_PEN
private var preX: Float = 0.0f //上一次的触摸点x坐标
private var preY: Float = 0.0f //上一次触摸点y坐标
private var mPath = Path() //path路径
//画笔
private var mPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
//想要绘制的内容先绘制到这个增加的canvas对应的bitmap上,
// 写完后再把这个bitmap的ARGB信息一次提交给上下文的canvas去绘制
private lateinit var mBufferBitmap: Bitmap
private lateinit var mBufferCanvas: Canvas
init {
mPaint.style = Paint.Style.STROKE //画笔为实心
mPaint.color = Color.RED //颜色
mPaint.strokeCap = Paint.Cap.ROUND //笔触为圆形
mPaint.strokeWidth = 10f //画笔大小
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int){
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mBufferBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
//canvas绘制的内容,将会在这个mBufferBitmap内
mBufferCanvas = Canvas(mBufferBitmap)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//画出缓存bitmap的内容
canvas.drawBitmap(mBufferBitmap,0f,0f,null)
}
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
}
MotionEvent.ACTION_MOVE -> { //手指移动的时候
//绘制圆滑曲线,即贝塞尔曲线,贝塞尔曲线这个知识自行了解
mPath.quadTo(preX,preY,event.x,event.y)
//在缓存里面绘制
mBufferCanvas.drawPath(mPath,mPaint)
//重新绘制,会调用onDraw方法
invalidate()
preX = event.x
preY = event.y
}
MotionEvent.ACTION_UP ->{
//清除路径的内容
mPath.reset()
}
}
// true:告诉系统,这个触摸事件由我来处理
// false:告诉系统,这个触摸事件我不处理,这时系统会把触摸事件传递给imageview的父节点
return true
}
/**
* 设置画笔模式
*/
fun setModel(@EditMode model:Long){
mMode = model
when(model){
EDIT_MODE_PEN -> {
mPaint.xfermode = null
}
EDIT_MODE_ERASER ->{
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
}
}
}
三、清空画布实现
添加一个方法,按照上面的套路,把缓存canvas绘制清除即可。
/**
* 清空画布
*/
fun clear() {
mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
invalidate()
}
来源:https://blog.csdn.net/niubitianping/article/details/78400966


猜你喜欢
- 一、首先我们要获取Logcat中的日志如何获取呢?首先我们要先定义一个String[]数组,里面的代码是//第一个是Logcat ,也就是我
- 本文由老王家组装电脑引出——建造者设计模式,详细介绍建造者模式的基本概念和实现代码,为了便于理解建造
- 我们使用控件AutoCompleteTextView 自动提示时,有时需要设置BaseAdapter,设置BaseAdapter时,需要实现
- 前言我昨天做了个梦,我梦见我在一条路走,走的时候经过一个房间,里面关着一条边牧和鸡和猪,后来我醒了,我知道那只边牧就是小叶子(哈仔十一的边牧
- 前言跳过废话,直接看正文当年入坑Java是因为它的跨平台优势。那时我认为,”编写一次,处处运行。”这听上去多么牛逼,应该是所有语言发展的终极
- 1. 问题描述:自己修改了下 ${M2_HOME}/conf/settings.xml中的本地repository地址,但是重新执行mvn的
- 对Element和Node有困惑是因为对xml整个结构不了解,以下作为一个简要概述:以下图为w3c.org网站的xml文档树图:从上图可以看
- 本文实例为大家分享了C#控制台实现飞行棋小游戏的具体代码,供大家参考,具体内容如下游戏标题static void ShowTitle() &
- 如果有一个值不太会变化,我们经常使用const和readonly,这2者有何不同呢?有时候,我们也会在readonly之前加上关键字stat
- 1 什么是条件变量condition_variable是一个类,常和mutex搭配使用。condition_variable类是一个同步原语
- Spring读取配置文件Document在XmlBeanDefinitionReader.doLoadBeanDefinitions(Inp
- 本文实例讲述了C#调用Oracle存储过程的方法。分享给大家供大家参考。具体实现方法如下:Oracle数据库代码如下:create or r
- 为什么我们需要IntentService ?Android中的IntentService是继承自Service类的,在我们讨论IntentS
- 本文实例为大家分享了C#实现俄罗斯方块的具体代码,供大家参考,具体内容如下1.调色板代码namespace Tetris{ class Pa
- 1. Spring简介Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。2. Spring的优势 1.方便解耦, 简
- 嗯,就是BASE64,不用多想,本来计划是要跟上一篇字符串压缩一起写的,用来实现将一个文件可以用json或者text等方式进行接口之间的传输
- 一:将String字符串放在最前面防止发生NullPointerException异常,我们通常把String字符串放在equals方法的左
- 本文实例讲述了Android编程实现播放视频的方法。分享给大家供大家参考,具体如下:播放视频文件其实并不比播放音频文件复杂,主要是使用 Vi
- 模拟登陆的原理很简单,就是发送一个Http 请求服务器获得响应,然后客户端获取到cookie即可实现模拟登陆,比如一些抢票软件的原理无非也是
- 在图文混排的文档中,我们可以根据需要将文档中的文字信息或者图片提取出来,通过C#代码可以提取Word和PDF文件中的文本和图片,那么同样的,