Android畫板開發(fā)之橡皮擦功能
在上一篇實現(xiàn)了簡單的畫板功能, 這篇實現(xiàn)橡皮擦功能,首先分析一下應(yīng)該如何實現(xiàn),
在Andriod有個圖像混合(Xfermode)概念,利用這個概念我們就可以實現(xiàn)橡皮擦功能。

一、Xfermode
Paint有一個方法setXfermode(Xfermode),這個方法設(shè)置圖像的混合模式。參數(shù)有三個子類:
- AvoidXfermode
- PixelXorXfermode
- PorterDuffXfermode
前面兩個因為不支持硬件加速在API 16已經(jīng)已經(jīng)過時棄用了。 簡單講一下第三個。
1.1 PorterDuffXfermode
該類有且只有一個含參的構(gòu)造方法PorterDuffXfermode(PorterDuff.Mode mode),參數(shù)就是設(shè)置圖像的混合模式,下面這張圖片形象地說明了各種模式的作用

我們的做橡皮擦的時候,就是用到了PorterDuff.Mode.CLEAR這個模式清除圖像,所以說橡皮擦也是Path,只是繪制的模式不一樣了。
二、實現(xiàn)
在上一篇的文章中,實現(xiàn)了最簡單筆畫畫板,就是只有一個畫筆模式,所以首先添加一個橡皮擦的繪制模式。
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
/**
* 設(shè)置畫筆模式
*/
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擦掉原來的內(nèi)容
- 再來回切換繪制
現(xiàn)在重點是解決第2點,一個Path怎么做到不改變原來的path基礎(chǔ)上換個繪制模式繼續(xù)畫呢?
如果你考慮第2點的話,效果是這樣子的:
What the fuck?(黑人問號) 這什么情況? 其實是因為path只有一條,一直沒改變。所以,引入緩存Canvas和緩存Bitmap,添加兩個變量:
//想要繪制的內(nèi)容先繪制到這個增加的canvas對應(yīng)的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繪制的內(nèi)容,將會在這個mBufferBitmap內(nèi)
mBufferCanvas = Canvas(mBufferBitmap)
}
}
然后在onTouchEvent方面里面手指移動的時候,我們在緩存Canvas里面進行繪制path:
MotionEvent.ACTION_MOVE -> { //手指移動的時候
//繪制圓滑曲線,即貝塞爾曲線,貝塞爾曲線這個知識自行了解
mPath.quadTo(preX,preY,event.x,event.y)
//在緩存里面繪制
mBufferCanvas.drawPath(mPath,mPaint)
//重新繪制,會調(diào)用onDraw方法
invalidate()
preX = event.x
preY = event.y
}
然后onDraw的時候,就把緩存的Canvas的bitmap當前view的Canvas:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//畫出緩存bitmap的內(nèi)容
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)
//想要繪制的內(nèi)容先繪制到這個增加的canvas對應(yīng)的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繪制的內(nèi)容,將會在這個mBufferBitmap內(nèi)
mBufferCanvas = Canvas(mBufferBitmap)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//畫出緩存bitmap的內(nèi)容
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方法只會執(zhí)行一次
preX = event.x
preY = event.y
}
MotionEvent.ACTION_MOVE -> { //手指移動的時候
//繪制圓滑曲線,即貝塞爾曲線,貝塞爾曲線這個知識自行了解
mPath.quadTo(preX,preY,event.x,event.y)
//在緩存里面繪制
mBufferCanvas.drawPath(mPath,mPaint)
//重新繪制,會調(diào)用onDraw方法
invalidate()
preX = event.x
preY = event.y
}
MotionEvent.ACTION_UP ->{
//清除路徑的內(nèi)容
mPath.reset()
}
}
// true:告訴系統(tǒng),這個觸摸事件由我來處理
// false:告訴系統(tǒng),這個觸摸事件我不處理,這時系統(tǒng)會把觸摸事件傳遞給imageview的父節(jié)點
return true
}
/**
* 設(shè)置畫筆模式
*/
fun setModel(@EditMode model:Long){
mMode = model
when(model){
EDIT_MODE_PEN -> {
mPaint.xfermode = null
}
EDIT_MODE_ERASER ->{
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
}
}
}
三、清空畫布實現(xiàn)
添加一個方法,按照上面的套路,把緩存canvas繪制清除即可。
/**
* 清空畫布
*/
fun clear() {
mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
invalidate()
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android MQTT與WebSocket協(xié)議詳細講解
MQTT(消息隊列遙測傳輸)是ISO 標準(ISO/IEC PRF 20922)下基于發(fā)布/訂閱范式的消息協(xié)議。它工作在TCP/IP協(xié)議族上,是為硬件性能低下的遠程設(shè)備以及網(wǎng)絡(luò)狀況糟糕的情況下而設(shè)計的發(fā)布/訂閱型消息協(xié)議2022-11-11
Android 使用ViewPager自動滾動循環(huán)輪播效果
本文主要給大家介紹viewpager自動播放,循環(huán)滾動的效果,對android viewpager滾動相關(guān)知識感興趣的朋友可以參考下本篇文章2015-11-11
Android Studio 透明狀態(tài)欄的實現(xiàn)示例
這篇文章主要介紹了Android Studio 透明狀態(tài)欄的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
Android系統(tǒng)開發(fā)中l(wèi)og的使用方法及簡單的原理
LOG是廣泛使用的用來記錄程序執(zhí)行過程的機制,它既可以用于程序調(diào)試,也可以用于產(chǎn)品運營中的事件記錄;在平時開發(fā)過程中經(jīng)常需要與log打交道,所以很有必要了解log的使用方法及簡單的原理,感興趣的朋友可以了解下啊2013-01-01
AndroidStudio代碼達到指定字符長度時自動換行實例
這篇文章主要介紹了AndroidStudio代碼達到指定字符長度時自動換行實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03

