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
的更新的频率下降一点
逃