欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Animation in SurfaceView

程序员文章站 2022-03-30 16:34:29
...

最近写动画控件。设计师那边给出了一个动画效果,有点悸动的感觉。看起来很复杂的样子,但实际上挺简单的,把运动轨迹和参数按设计师的要求调好,一个ObjectAnimator就搞定了。

自测过程中发现这个动画很耗费CPU,CPU的消耗大概在25-30%(Nexus5)
然后我就想着把动画独立到另外一个线程中,用SurfaceView来做,花了一点时间,写了一个出来。发现性能反而还下降了。CPU消耗大概在30%~35%

思路是直接用SurfaceView+HanderThread。在UI线程算好位置,然后发到渲染线程绘制。

这部分代码不会用在生产环境,给自己有需要的时候做笔记吧。

SurfaceView内绘制动画 DEMO

class DynamicTestWidget : SurfaceView, SurfaceHolder.Callback {
    private var mGLThread: GLThread? = null
    private val mBitmaps = ArrayList<Bitmap>(3)
    private val mPaint: Paint
    private var mAnimSet: ValueAnimator? = null

    @Volatile
    private var mRunning = true

    constructor(context: Context?) : this(context, null)

    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr) {
        setZOrderOnTop(true)
        setZOrderMediaOverlay(true)
        isFocusable = false
        holder.addCallback(this)

        mPaint = Paint()
        mBitmaps.add(BitmapFactory.decodeResource(
                resources, R.drawable.ebw_dynamic_header_login_left_button))
        mBitmaps.add(BitmapFactory.decodeResource(
                resources, R.drawable.ebw_dynamic_header_login_left_top))
        mBitmaps.add(BitmapFactory.decodeResource(
                resources, R.drawable.ebw_dynamic_header_login_right))
    }

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

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        mAnimSet?.cancel()
        mAnimSet = null
        if (holder != null) {
            synchronized(holder) { mRunning = false }
        } else mRunning = false
        mGLThread?.looper?.quit()
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        if (holder == null) return
        mGLThread = GLThread()
        mGLThread?.setHolder(holder)
        mGLThread?.start()
        mRunning = true
        this.test()
    }

    private fun test() {
        val p = Path()
        p.addCircle(-20f, 0f, 20f, Path.Direction.CW)
        val pathMeasure = PathMeasure(p, true)
        val set = ValueAnimator.ofFloat(0F, pathMeasure.length)
        set.duration = 3000
        set.startDelay = (Math.random() * 1000).toLong()
        set.interpolator = LinearInterpolator()
        set.repeatCount = ValueAnimator.INFINITE
        set.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            private val coordinates = FloatArray(2)
            override fun onAnimationUpdate(animation: ValueAnimator) {
                val distance = animation.animatedValue as Float
                pathMeasure.getPosTan(distance, coordinates, null)

                val list = ArrayList<AnimBean>(1)
                list.add(AnimBean(0, coordinates[0], coordinates[1] - 158.toPx()))
                list.add(AnimBean(1, coordinates[0] - 100, coordinates[1]))
                list.add(AnimBean(2, coordinates[0] + 200, coordinates[1]))
                if (mRunning) this@DynamicTestWidget.mGLThread?.async(list)
            }
        })
        set.start()
        mAnimSet?.cancel()
        mAnimSet = null
        this.mAnimSet = set
    }

    internal inner class GLThread : HandlerThread(
            "gl_thread", android.os.Process.THREAD_PRIORITY_DISPLAY) {
        private var mHandler: Handler? = null
        private var mHolder: SurfaceHolder? = null

        override fun onLooperPrepared() {
            this.mHandler = Handler(looper)
        }

        fun setHolder(holder: SurfaceHolder?) {
            this.mHolder = holder
        }

        fun async(beans: List<AnimBean>) {
            mHandler?.post {
                if (mHolder == null) return@post
                synchronized(mHolder!!) {
                    if (!mRunning) return@post
                    val canvas = mHolder!!.lockCanvas()
                    try {
                        canvas.drawColor(Color.WHITE)
                        beans.forEach {
                            canvas.drawBitmap(mBitmaps[it.index], it.x, it.y, mPaint)
                        }
                    } catch (e: Exception) {
                        e.printStackTrace()
                    } finally {
                        try {
                            mHolder?.unlockCanvasAndPost(canvas)
                        } catch (e: Throwable) {/*we try our best,ignore it*/
                        }
                    }
                }
            }
        }
    }

    class AnimBean(val index: Int, val x: Float, val y: Float)
}

代码复杂高了不少,而且还要处理SurfaceView的初屏黑屏。感觉这笔买卖不划算。

留个问题,直接SurfaceView渲染为什么会更耗CPU呢?

猜测:
1,和图片的大小有关
2,把ValueAnimator的更新的频率下降一点

参考资料:https://software.intel.com/en-us/android/articles/2d-animation-for-android-seriescomparing-and-contrasting-different-ways-of-doing-the-same#03_combiningtechniques