Android畫板開發(fā)之撤銷反撤銷功能
一、分析
這篇將會講解撤銷反撤銷功能的實現(xiàn),先討論一下這個原理是怎么樣實現(xiàn)的。
每次撤回的內(nèi)容,內(nèi)容是怎么定義呢? 其實就是每一筆,每一筆作為撤回的內(nèi)容,那每一筆怎么算呢,就是算手指從按下-移動-放開這一個過程就是一筆。
我們只需記錄這個過程為一筆,然后用一個已畫列表list列表來記錄這個過程的paint畫筆和路徑path。
撤銷的時候就把后面的一個數(shù)據(jù)移到另一個撤銷列表
反撤銷的時候,就把撤銷列表的最后面那條數(shù)據(jù)移動到已畫列表。
然后,還有一個重點,就是畫筆的保存數(shù)量,上面說記錄每一筆畫筆,這當(dāng)然是有個限度,不可能畫了好幾百筆都記錄下來,這樣子內(nèi)存消耗很大的,所以超出顯示畫筆數(shù)量的時候,我們就把以前的畫死在畫板上。

基本原理是這樣子的。接下來跟著我實現(xiàn)
二、實現(xiàn)
如何實現(xiàn)撤回功能
2.1 定義數(shù)據(jù)類
首先,需要一個bean類存儲每一筆的數(shù)據(jù),這里定義一個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 時候清空數(shù)據(jù)列表
*/
fun clear(isClearList:Boolean) {
if(isClearList){
mRevokedList.clear()
mPaintedList.clear()
}
mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
invalidate()
}
2.3 實現(xiàn)撤銷方法
在view定義兩個列表,一個是已經(jīng)畫的內(nèi)容列表,一個是撤銷內(nèi)容的列表
//儲存已經(jīng)寫的筆畫 private var mPaintedList: MutableList<PaintData> = ArrayList<PaintData>() //已經(jīng)撤銷的列表 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
//在測量的時候進(jìn)行初始化:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if(mBufferCanvas == null){
mBufferBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
//canvas繪制的內(nèi)容,將會在這個mBufferBitmap內(nèi)
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()
}
}
然后就是畫筆的保存,在觸摸按下的時候,進(jìn)行畫筆的保存
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action){
MotionEvent.ACTION_DOWN -> { //手指按下的時候
//將起始點移動到當(dāng)前坐標(biāo)
mPath.moveTo(event.x,event.y)
//記錄上次觸摸的坐標(biāo),注意ACTION_DOWN方法只會執(zhí)行一次
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)
//重新繪制,會調(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
}
最后繪制的時候:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//超出緩存的就固化到緩存bitmap
while(mPaintedList.size > MAX_PAINT_RECORED){
val paintData = mPaintedList.removeAt(0)
paintData.draw(mHoldCanvas)
}
//繪制固化的內(nèi)容到緩存Canvas
mBufferCanvas.drawBitmap(mHoldBitmap,0f,0f,null)
//繪制記錄的畫筆
for(paint in mPaintedList){
//重新繪制每個path
paint.draw(mBufferCanvas)
}
//畫出緩存bitmap的內(nèi)容
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類來保存每筆的 畫筆和路徑,然后撤銷時候重新繪制。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Android中fragment和viewpager的那點事兒
本文主要對Android中fragment和viewpager進(jìn)行詳細(xì)介紹,具有一定的參考價值,需要的朋友一起來看下吧2016-12-12
android中關(guān)于call撥號功能的實現(xiàn)方法
這篇文章主要介紹了android中關(guān)于call撥號功能實現(xiàn)的記錄,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-05-05
Android studio報錯:The emulator process for AVD (xxx) was kill
這篇文章主要介紹了Android studio報錯:The emulator process for AVD (xxx) was killed,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Android編程使用光線傳感器獲取光線強(qiáng)弱的方法【LightSensorManager封裝類】
這篇文章主要介紹了Android編程使用光線傳感器獲取光線強(qiáng)弱的方法,結(jié)合實例形式分析了Android光線傳感器管理封裝類LightSensorManager的具體實現(xiàn)技巧,需要的朋友可以參考下2017-11-11
Android 6.0開發(fā)實現(xiàn)關(guān)機(jī)菜單添加重啟按鈕的方法
這篇文章主要介紹了Android 6.0開發(fā)實現(xiàn)關(guān)機(jī)菜單添加重啟按鈕的方法,涉及Android6.0針對相關(guān)源碼的修改與功能添加操作技巧,需要的朋友可以參考下2017-09-09
Android使用GridView實現(xiàn)橫向滾動效果
這篇文章主要為大家詳細(xì)介紹了Android使用GridView實現(xiàn)橫向滾動效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07
Android實現(xiàn)系統(tǒng)狀態(tài)欄的隱藏和顯示功能
這篇文章主要介紹了Android實現(xiàn)系統(tǒng)狀態(tài)欄的隱藏和顯示功能,文中還給大家?guī)硭姆N方法,大家可以根據(jù)自己需要參考下2018-07-07

