概述
首先說明啊旭愧,不是標(biāo)題黨啊椭住,在子線程中更新UI的方式有很多中
- 通過Looper在主線程中的Handler更新
- 通過runUIThread
- 通過view的post
但是上述幾種方式最終都是通過主線程來繪制的,所以今天要說的是利用SurfaceView在子線程中來更新界面
自定義一個SurfaceView
首先我們創(chuàng)建一個自定義SurfaceView场勤,復(fù)寫其onDraw()方法们拙,繪制一個圓
package com.tx.txcustomview.menu
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.SurfaceView
/**
* create by xu.tian
* @date 2021/9/11
*/
class SubSurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs) {
var paint = Paint()
init {
paint.color = Color.YELLOW
paint.style = Paint.Style.FILL
}
var centerX = 0f
var centerY = 0f
var radius = 0f
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawCircle(centerX,centerY,radius,paint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
centerX = (w/2).toFloat()
centerY = (h/2).toFloat()
radius = centerX/10*9
}
}
代碼很簡單锡移,我們看看運(yùn)行效果
中間的黑色矩形就是我們的自定義SurfaceView,很明顯我們復(fù)寫onDraw方法并沒有效果,那么是為啥呢崔赌?這個要從一個函數(shù)說起了
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
這個函數(shù)的功能簡單來說就是判斷要不要在這個View中調(diào)用onDraw()方法繪制內(nèi)容意蛀,因為一些ViewGroup是不需要繪制本身的,這樣就可以提高代碼的執(zhí)行效率峰鄙,而SurfaceView同樣被這樣設(shè)置了浸间,所以谷歌官方也是不建議通過復(fù)寫SurfaceView的onDraw()這樣的方式來繪制的
正確姿勢
fun startDraw(){
var thread = Thread(Runnable {
var canvas = holder.lockCanvas()
if (canvas!=null){
canvas?.drawCircle(centerX,centerY,radius,paint)
holder.unlockCanvasAndPost(canvas)
}else{
Log.d(tag,"canvas is null")
}
})
thread.start()
}
我們這里通過surfaceView的holder來獲取到Canvas對象,然后進(jìn)行繪制,吟榴,這里我們需要注意的是魁蒜,當(dāng)其他線程使用畫布或者畫布未被創(chuàng)建完成時,這里的Canvs對象是可能為空的吩翻,所以使用的時候需要非常小心
再看執(zhí)行效果
那么這種方式是否可以在主線程種使用呢兜看,那肯定是可以的了,主線程也是可以通過這種方式來更新SurfaceView的狭瞎。
更新SurfaceView局部內(nèi)容
fun startDraw(){
var thread = Thread(Runnable {
var canvas = holder.lockCanvas()
if (canvas!=null){
canvas?.drawCircle(centerX,centerY,radius,paint)
holder.unlockCanvasAndPost(canvas)
}else{
Log.d(tag,"canvas is null")
}
Thread.sleep(500)
var rectCanvas = holder.lockCanvas(Rect(width/4,height/4,width/4*3,height/4*3))
if (rectCanvas!=null){
var rect = rectCanvas.clipBounds
paint.color = Color.GREEN
rectCanvas.drawCircle(rect.centerX().toFloat(),
rect.centerY().toFloat(), (rect.centerX()/2).toFloat(),paint)
holder.unlockCanvasAndPost(rectCanvas)
}else{
Log.d(tag,"canvas is null")
}
})
thread.start()
}
運(yùn)行效果
我們先用lockCanvas()獲取到一個完整Canvas對象然后進(jìn)行繪制细移,然后將線程休眠500ms再取Canvas其中的一部分來進(jìn)行繪制
這里能夠很明顯的看到我們局部更新是成功的
Surface,SurfaceView,SurfaceHolder的關(guān)系
這里的這三者和MVC模式中三者的對應(yīng)關(guān)系一樣
- Surface-Model 保存繪制過程中的相關(guān)數(shù)據(jù)
- SurfaceView - View 負(fù)責(zé)繪制和顯示
- SurfaceHolder - Controller 負(fù)責(zé)具體的頁面實(shí)現(xiàn)
SurfaceView監(jiān)聽聲明周期
holder.addCallback(object : SurfaceHolder.Callback{
override fun surfaceCreated(holder: SurfaceHolder?) {
TODO("Not yet implemented")
}
override fun surfaceChanged(
holder: SurfaceHolder?,
format: Int,
width: Int,
height: Int
) {
TODO("Not yet implemented")
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
TODO("Not yet implemented")
}
})
SurfaceView雙緩沖技術(shù)
Surface使用了一種叫做雙緩沖的技術(shù)來渲染程序,這套技術(shù)需要兩套圖形緩沖區(qū)熊锭,一個叫前端緩沖區(qū)弧轧,一個叫后端緩沖區(qū)雪侥,我們通過lockCanvas方式拿到的均為后端緩沖區(qū),當(dāng)我們調(diào)用unlockCanvas相關(guān)函數(shù)時就是將前緩沖區(qū)與后緩沖區(qū)交換精绎。
那么是不是前緩沖區(qū)和后緩沖區(qū)就是各自擁有一塊畫布呢速缨?
那當(dāng)然不是了,前緩沖區(qū)肯定是一塊代乃,因為最終顯示的是一個固定的內(nèi)容旬牲,但后緩沖區(qū)會根據(jù)實(shí)際情況來創(chuàng)建畫布數(shù)量,以應(yīng)對多個線程同時對SurfaceView進(jìn)行操作搁吓。
SurfaceView和普通View的使用場景
- 當(dāng)需要與用戶發(fā)生頻繁交互原茅,或者需要實(shí)時給予用戶反饋時使用普通View菇民,比如按鈕雾家,滑動控件
- 當(dāng)需要屏幕主動頻繁刷新且不需要與用戶交互時我們就可以使用SurfaceView,比如Camera預(yù)覽寞埠,Video播放贮预。
總結(jié)
SurfaceView是子線程更新UI的一個常用控件贝室,一般做視頻開發(fā)或者Camera相關(guān)的就比較容易接觸,記得怎么更新SurfaceView界面就可以