时间:2020-12-04 03:22:24 | 栏目:Android代码 | 点击:次
在上一篇实现了简单的画板功能, 这篇实现橡皮擦功能,首先分析一下应该如何实现,
在Andriod有个图像混合(Xfermode)概念,利用这个概念我们就可以实现橡皮擦功能。
一、Xfermode
Paint有一个方法setXfermode(Xfermode),这个方法设置图像的混合模式。参数有三个子类:
前面两个因为不支持硬件加速在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) } } }
然后捋一下整个流程:
现在重点是解决第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() }