SurfaceView

0 阅读2分钟

简介

SurfaceView 是 Android 中一种特殊的 View,它与其他的 View 的区别是:

  • 它有自己独立的绘图表面(Surface),它在持有该 SurfaceView 的 Window 的后面,与该 SurfaceView 同一层级的普通 View(比如 TextView、Button 等)会显示在该 SurfaceView 的上面。
  • SurfaceView 的 UI 可以在子线程中渲染。

SurfaceView 一般用来显示比较复杂的图像、动画或视频。

简单使用

下面是一个 SurfaceView 的简单实例,该实例会显示一个跟随手指移动的足球:

class MySurfaceView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : SurfaceView(context, attrs, defStyleAttr), Runnable, SurfaceHolder.Callback {

    companion object {
        private const val TAG = "MySurfaceView"
        private const val REQ_SIZE = 120
    }

    private var football: Bitmap? = null
    private var x: Float = 0f
    private var y: Float = 0f
    private val holder: SurfaceHolder = getHolder()
    private var isRunning = false
    private var thread: Thread? = null

    init {
        holder.addCallback(this)
        // 获取 football 的 Bitmap
        loadBitmap()
    }

    private fun loadBitmap(){
        val options =  BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, R.mipmap.football, options)
        val height = options.outHeight
        val width = options.outWidth
        var inSampleSize = 1
        if((height > REQ_SIZE) or (width > REQ_SIZE)) {
            inSampleSize = (height / REQ_SIZE).coerceAtLeast(width / REQ_SIZE)
        }
        options.inSampleSize = inSampleSize
        options.inJustDecodeBounds =false
        football = BitmapFactory.decodeResource(resources, R.mipmap.football, options)
    }

    override fun onTouchEvent(me: MotionEvent): Boolean {
        when(me.action){
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE->{
                x = me.x
                y = me.y
                return true
            }
        }
        return super.onTouchEvent(me)
    }

    override fun run() {
        while(isRunning){
            if(!holder.surface.isValid){
                continue
            }

            val canvas = holder.lockCanvas()
            canvas.drawColor(Color.GREEN)
            football?.let {
                canvas.drawBitmap(it, x - it.width / 2f, y - it.height / 2f, null)
            }
            holder.unlockCanvasAndPost(canvas)
        }
    }

    private fun pause(){
        isRunning = false
        thread?.join()
        thread = null
    }

    private fun resume(){
        if (isRunning || thread != null) return

        isRunning = true
        thread = Thread(this)
        thread?.start()
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        resume()
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        pause()
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        pause()
        football?.recycle()
        football = null
    }
}

运行后显示如下:

image.png

API

下面来学习一下常用的 API:

getHolder(),通过这个函数可以拿到 SurfaceHolder,用于获取和控制 SurfaceView 对应的 surface。

在这里通过 holder.addCallback(this) 给当前 SurfaceView 添加 SurfaceHolder.Callback 回调,这样当 surface 创建后会马上调用 surfaceCreated() 函数,开发者需要在这里开启渲染;surfaceDestroyed() 函数在 surface 销毁之前回调,开发者需要在这里停止渲染;surfaceChanged() 函数在 surface 结构(格式或尺寸)变化的时候回调,它在 surfaceCreated() 后至少会调用一次。注意 SurfaceHolder.Callback 需要在持有 SurfaceView 的 Window 的线程调用(一般是主线程)。

在子线程中渲染 SurfaceView 时,你必须保证对应的 surface 是可用的(可以通过 isValid() 函数校验)。

在修改 surface 中的像素之前要调用 lock​Canvas(Rect dirty),修改完成后要调用 unlock​Canvas​And​Post(Canvas canvas)。