Android Animation in SurfaceView

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

猜你喜欢

转载自blog.csdn.net/yeshennet/article/details/80625069