自定义SurfaceView实现一个高性能图片平移动画

253 阅读1分钟

利用handlerThread,在子线程渲染SurfaceView的canvas。

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

    companion object {
        private const val MSG_POST_WHAT = 888

        private const val MSG_RENDER_WHAT = 999

        private const val DEFAULT_RENDER_TIME = 16
    }

    var renderTime = DEFAULT_RENDER_TIME

    @Volatile
    private var mCanRun = false

    private lateinit var mHandlerThread: HandlerThread

    private lateinit var mHandler: Handler

    private val taskList = mutableListOf<Model>()

    private val evaluator = PointFEvaluator(PointF())

    private val mMatrix = Matrix()

    init {
        setZOrderOnTop(true)
        holder.setFormat(PixelFormat.TRANSLUCENT)
        holder.addCallback(this)
    }

    fun postTask(model: Model) {
        if (!mCanRun) {
            return
        }
        val msg = mHandler.obtainMessage(MSG_POST_WHAT)
        msg.obj = model
        mHandler.sendMessageAtFrontOfQueue(msg)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        mHandlerThread = HandlerThread("xxx")
        mHandlerThread.start()
        mHandler = Handler(mHandlerThread.looper, this)
        mCanRun = true

    }

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

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        if (mCanRun) {
            mHandlerThread.quit()
            taskList.clear()
        }
        mCanRun = false
    }

    override fun handleMessage(msg: Message): Boolean {
        val curMillis = SystemClock.uptimeMillis()
        if (!mCanRun) return true
        if (msg.what == MSG_POST_WHAT) { // 提交新任务
            taskList.add(msg.obj as Model)
            if (!mHandler.hasMessages(MSG_RENDER_WHAT)) {
                mHandler.sendEmptyMessage(MSG_RENDER_WHAT)
            }
        } else if (msg.what == MSG_RENDER_WHAT) { // 渲染任务
            val canvas = holder.lockCanvas() ?: return true
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
            val hashTask = taskList.isNotEmpty()
            if (hashTask) {
                val iterator = taskList.listIterator()
                while (iterator.hasNext()) {
                    val model = iterator.next()
                    model.updateFraction(curMillis)
                    model.draw(canvas)
                    if (model.isCompleted) { // 任务完成
                        iterator.remove()
                    }
                }
            }
            holder.unlockCanvasAndPost(canvas)
            if (hashTask) { // 是否还有渲染任务
                // 保持60帧
                mHandler.sendEmptyMessageDelayed(MSG_RENDER_WHAT, renderTime.minus(SystemClock.uptimeMillis() - curMillis).minus(3).coerceAtLeast(0))
            }
        }
        return true
    }

    private fun Model.draw(canvas: Canvas) {
        val pointF = getBitmapPointF(evaluator)
        mMatrix.reset()
        if (scale != null) {
            mMatrix.postScale(scale, scale)
        }
        mMatrix.postTranslate(pointF.x, pointF.y)
        canvas.drawBitmap(bitmap, mMatrix, null)
    }

}

data class Model(
    val bitmap: Bitmap,
    val from: PointF,
    val to: PointF,
    val duration: Long,
    val scaleSize: Size? = null,
) {
    val scale = scaleSize?.let {
        it.width.toFloat().div(bitmap.width).coerceAtMost(it.height.toFloat().div(bitmap.height))
    }

    private val readerWidth = scale?.times(bitmap.width)?.toInt() ?: bitmap.width

    private val readerHeight = scale?.times(bitmap.height)?.toInt() ?: bitmap.height

    private var firstFrame = -1L

    private var fraction = 0f

    fun updateFraction(curMillis: Long) {
        if (firstFrame == -1L) {
            firstFrame = curMillis
        } else {
            fraction = curMillis.minus(firstFrame).toFloat().div(duration)
            if (fraction > 1f) {
                fraction = 1f
            }
        }
    }
    
    val isCompleted: Boolean
        get() = fraction >= 1f

    fun getBitmapPointF(evaluator: PointFEvaluator): PointF {
        return evaluator.evaluate(fraction, from, to).also {
            it.set(it.x.minus(readerWidth.div(2)), it.y.minus(readerHeight.div(2)))
        }
    }
}