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

基本原理是這樣子的。接下來跟著我實(shí)現(xiàn)
二、實(shí)現(xiàn)
如何實(shí)現(xiàn)撤回功能
2.1 定義數(shù)據(jù)類
首先,需要一個(gè)bean類存儲(chǔ)每一筆的數(shù)據(jù),這里定義一個(gè)PaintData,里面需要定義個(gè)draw方法,因?yàn)槌蜂N的時(shí)候,需要重新繪制。
data class PaintData(
var mPaint: Paint, //保存畫筆
var mPath: Path //保存路徑
) {
/**
* 撤銷和反撤銷之后 重新繪制
* @param canvas 繪制的畫布
*/
fun draw(canvas: Canvas){
canvas.drawPath(mPath,mPaint)
}
}
2.2 修改清空畫板方法
因?yàn)槎嗔肆斜?,所以清空畫板的方法需要把列表也清除?/p>
/**
* 清空畫布
* @param isClearList 時(shí)候清空數(shù)據(jù)列表
*/
fun clear(isClearList:Boolean) {
if(isClearList){
mRevokedList.clear()
mPaintedList.clear()
}
mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
invalidate()
}
2.3 實(shí)現(xiàn)撤銷方法
在view定義兩個(gè)列表,一個(gè)是已經(jīng)畫的內(nèi)容列表,一個(gè)是撤銷內(nèi)容的列表
//儲(chǔ)存已經(jīng)寫的筆畫 private var mPaintedList: MutableList<PaintData> = ArrayList<PaintData>() //已經(jīng)撤銷的列表 private var mRevokedList: MutableList<PaintData> = ArrayList<PaintData>()
添加固話層canvas和bitmap,超出記錄的畫筆就寫死在固化層了
//固化層,超出最大筆畫就先繪制到這個(gè)層
private lateinit var mHoldBitmap: Bitmap
private lateinit var mHoldCanvas: Canvas
//最多記錄20畫筆跡
private val MAX_PAINT_RECORED = 20
//在測(cè)量的時(shí)候進(jìn)行初始化:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if(mBufferCanvas == null){
mBufferBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
//canvas繪制的內(nèi)容,將會(huì)在這個(gè)mBufferBitmap內(nèi)
mBufferCanvas = Canvas(mBufferBitmap)
}
if(mHoldCanvas == null){
mHoldBitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
mHoldCanvas = Canvas(mHoldBitmap)
}
}
然后定義撤回的方法
/**
* 撤回一個(gè)筆跡
* @return 是否撤回成功
*/
fun revoked(){
reDraw(mPaintedList)
}
反撤銷方法基本一致,只是改了個(gè)列表:
/**
* 反撤回一個(gè)筆跡
*/
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()
}
}
然后就是畫筆的保存,在觸摸按下的時(shí)候,進(jìn)行畫筆的保存
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action){
MotionEvent.ACTION_DOWN -> { //手指按下的時(shí)候
//將起始點(diǎn)移動(dòng)到當(dāng)前坐標(biāo)
mPath.moveTo(event.x,event.y)
//記錄上次觸摸的坐標(biāo),注意ACTION_DOWN方法只會(huì)執(zhí)行一次
preX = event.x
preY = event.y
//保存畫筆
mPaintedList.add(PaintData(Paint(mPaint),Path(mPath)))
}
MotionEvent.ACTION_MOVE -> { //手指移動(dòng)的時(shí)候
//繪制圓滑曲線,即貝塞爾曲線,貝塞爾曲線這個(gè)知識(shí)自行了解
mPaintedList.get(mPaintedList.size-1).mPath.quadTo(preX,preY,event.x,event.y)
//重新繪制,會(huì)調(diào)用onDraw方法
invalidate()
preX = event.x
preY = event.y
}
MotionEvent.ACTION_UP ->{
//清除路徑的內(nèi)容
mPath.reset()
}
}
// true:告訴系統(tǒng),這個(gè)觸摸事件由我來處理
// false:告訴系統(tǒng),這個(gè)觸摸事件我不處理,這時(shí)系統(tǒng)會(huì)把觸摸事件傳遞給imageview的父節(jié)點(diǎn)
return true
}
最后繪制的時(shí)候:
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){
//重新繪制每個(gè)path
paint.draw(mBufferCanvas)
}
//畫出緩存bitmap的內(nèi)容
canvas.drawBitmap(mBufferBitmap,0f,0f,null)
}
最后清除畫布的時(shí)候,需要把畫筆列表也清除了:
/**
* 清空畫布
*/
fun clear() {
mRevokedList.clear()
mPaintedList.clear()
mHoldCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
mBufferCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
invalidate()
}
重點(diǎn)就是在于利用一個(gè)bean類來保存每筆的 畫筆和路徑,然后撤銷時(shí)候重新繪制。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android實(shí)現(xiàn)簡(jiǎn)單的畫畫板實(shí)例代碼
- Android實(shí)現(xiàn)畫板、寫字板功能(附源碼下載)
- Android自定義SurfaceView實(shí)現(xiàn)畫板功能
- Android采用雙緩沖技術(shù)實(shí)現(xiàn)畫板
- Android畫板開發(fā)之添加背景和保存畫板內(nèi)容為圖片
- Android畫板開發(fā)之橡皮擦功能
- Android實(shí)現(xiàn)畫畫板案例
- Android編程實(shí)現(xiàn)畫板功能的方法總結(jié)【附源碼下載】
- Android多媒體之畫畫板開發(fā)案例分享
- Android自定義View實(shí)現(xiàn)簡(jiǎn)易畫板功能
相關(guān)文章
詳解Android中fragment和viewpager的那點(diǎn)事兒
本文主要對(duì)Android中fragment和viewpager進(jìn)行詳細(xì)介紹,具有一定的參考價(jià)值,需要的朋友一起來看下吧2016-12-12
android中關(guān)于call撥號(hào)功能的實(shí)現(xiàn)方法
這篇文章主要介紹了android中關(guān)于call撥號(hào)功能實(shí)現(xiàn)的記錄,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
Android studio報(bào)錯(cuò):The emulator process for AVD (xxx) was kill
這篇文章主要介紹了Android studio報(bào)錯(cuò):The emulator process for AVD (xxx) was killed,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Android編程使用光線傳感器獲取光線強(qiáng)弱的方法【LightSensorManager封裝類】
這篇文章主要介紹了Android編程使用光線傳感器獲取光線強(qiáng)弱的方法,結(jié)合實(shí)例形式分析了Android光線傳感器管理封裝類LightSensorManager的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-11-11
Android 6.0開發(fā)實(shí)現(xiàn)關(guān)機(jī)菜單添加重啟按鈕的方法
這篇文章主要介紹了Android 6.0開發(fā)實(shí)現(xiàn)關(guān)機(jī)菜單添加重啟按鈕的方法,涉及Android6.0針對(duì)相關(guān)源碼的修改與功能添加操作技巧,需要的朋友可以參考下2017-09-09
詳解Flutter如何繪制曲線,折線圖及波浪動(dòng)效
這篇文章主要為大家介紹線條類圖形的繪制(正弦曲線、折線圖),并且結(jié)合 Animation 實(shí)現(xiàn)了常見的波浪動(dòng)效,感興趣的小伙伴可以了解一下2022-03-03
Android使用GridView實(shí)現(xiàn)橫向滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android使用GridView實(shí)現(xiàn)橫向滾動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android實(shí)現(xiàn)系統(tǒng)狀態(tài)欄的隱藏和顯示功能
這篇文章主要介紹了Android實(shí)現(xiàn)系統(tǒng)狀態(tài)欄的隱藏和顯示功能,文中還給大家?guī)硭姆N方法,大家可以根據(jù)自己需要參考下2018-07-07

