简介
Android的窗口界面是由多个View组成的View Hierachy树形结构,WMS会从DecorView进入对整个View Hierachy进行管理控制UI的显示,因此整个View Hierachy在WMS中有一个对应的WindowState,
普通的Android控件都是将自己绘制到宿主窗口的绘图表面上,即都是在同一块图型缓冲区操作,为什么会这样做呢?因为如果andorid把更新UI这件事交由我们自己在不同线程中更新的话,就需要大量的同步,开销很大,因此需要保证所有UI相关的操作都在一个线程中执行。所以android 就把它们放到了主线程中执行,也就是把它们全放到了同一块缓冲区。这也就是需要我们尽量避免耗时的UI操作,否则会进程无响应并且报ANR错。
这时就需要SurfaceView了
SurfaceView终究继承了View,其内嵌着专门用于绘制的Surface(绘图表面),有着自己独立的绘图表明、缓冲区,也就是这个控件不将自己的UI数据填入到它的宿主窗口的绘图表面的图形缓冲区里面去。因此SurfaceView相当于在Window上挖个洞,用来进行绘制。所以在SurfaceView里的Surface和绘制的View是不在UI线程的View hierachy中的,创建Surface时会在SuraceFlinger内部建立对应的Layer,同时在WMS又有自己对应的WIndowState
与宿主窗口分离,因此可以不在UI线程进行渲染,也就不会阻塞主线程的事件响应。可以控制刷新频率,同时利用双缓存机制,也就是为了绘制时不会闪烁,会把要绘制的放到内存,然后再一次型绘制出来
用法:
class MySurfaceView : SurfaceView, SurfaceHolder.Callback, Runnable {
//SurfaceHolder
private var surfaceHolder: SurfaceHolder? = null
private var canvas: Canvas? = null
private var paint: Paint? = null
private var isDrawing = false
fun setIsDrawing(b:Boolean){
isDrawing = b
}
fun getIsDrawing(): Boolean {
return isDrawing
}
constructor(context: Context?) : super(context) {
initView()
}
constructor(context: Context?, set: AttributeSet?) : super(
context,
set
) {
initView()
}
private fun initView() {
//获取父类创建的SurfaceHolder,对holder进行初始化
surfaceHolder = getHolder()
//注册 SurfaceHolder 的回调方法
surfaceHolder?.addCallback(this)
//可获取焦点
isFocusable = true
isFocusableInTouchMode = true
//屏幕常亮
this.keepScreenOn = true
paint = Paint()
paint!!.isAntiAlias = true
paint!!.color = Color.RED
paint!!.textSize = 80f
paint!!.style = Paint.Style.FILL
}
override fun surfaceCreated(holder: SurfaceHolder) {
//子线程中绘制
Thread(this).start()
}
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int, width: Int, height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
isDrawing = false
}
override fun run() {
var i = 0
while(true){
if (isDrawing){
draw(i++)
}
Thread.sleep(20)
}
}
private fun draw(i: Int) {
if (i == 100) isDrawing = false
try {
//锁定画布
canvas = surfaceHolder!!.lockCanvas()
//绘制背景
canvas?.drawColor(Color.WHITE)
//绘制文字
canvas?.drawText(""+i,100f,100f, paint!!)
} catch (e: Exception) {
Log.d("MySurfaceView", "draw() Exception")
} finally {
//解锁画布
if (canvas != null) surfaceHolder!!.unlockCanvasAndPost(canvas)
}
}
}
class MainActivity : AppCompatActivity() {
var surfaceView:MySurfaceView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
surfaceView = findViewById<MySurfaceView>(R.id.sv)
}
fun click(view: View) {
if (surfaceView?.getIsDrawing()!!){
surfaceView?.setIsDrawing(false)
}else{
surfaceView?.setIsDrawing(true)
}
}
}
双缓存机制带来的问题:
我再调式代码时遇到的问题:
原因:
当我第二次调lockCanvas()的时候,系统就会给我们第二块缓存块,这个缓存块上面并没有你刚刚画的数字1,然后你在上面画了数字2,再调用unlockCanvasAndPost的时候,第二块缓存块就被绘制到屏幕上,我们看到的就只有2,然后我们再lockCanvas()的时候就会把第一块缓存块给你,上面有数字1,我们再画3,当调用unlockCanvasAndPost的时候就会出现重叠问题,解决办法也就是上面我们调用的 canvas?.drawColor(Color.WHITE)即可进行清屏作用
覆盖问题:
解决办法:在addView之后使用SurfaceView.setZOrderOnTop(true)