MST

星途 面试题库

面试题:Kotlin Android自定义视图中的动画与性能优化

在Kotlin开发的Android自定义视图场景下,实现一个复杂的动画效果(例如逐帧动画和属性动画结合),并阐述如何对该动画进行性能优化,以确保在不同设备上流畅运行。同时分析在动画过程中可能出现的性能瓶颈及解决办法。
28.3万 热度难度
编程语言Kotlin

知识考点

AI 面试

面试题答案

一键面试

实现复杂动画效果(逐帧动画和属性动画结合)

  1. 逐帧动画实现
    • res/drawable 目录下创建一个 XML 文件,例如 frame_animation.xml
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false">
        <item android:drawable="@drawable/frame1" android:duration="100"/>
        <item android:drawable="@drawable/frame2" android:duration="100"/>
        <!-- 更多帧 -->
    </animation-list>
    
    • 在自定义视图的 onDraw 方法或者在需要展示动画的地方,通过 ImageView 加载该动画:
    val imageView = ImageView(context)
    imageView.setImageResource(R.drawable.frame_animation)
    val animation = imageView.drawable as AnimationDrawable
    animation.start()
    
  2. 属性动画实现
    • 例如,对自定义视图的某个属性(如透明度、缩放等)进行动画。假设自定义视图继承自 View,在自定义视图类中:
    class CustomView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
        private var alphaValue = 1f
        override fun onDraw(canvas: Canvas) {
            // 根据 alphaValue 绘制视图,这里简单示例,实际可能更复杂
            paint.alpha = (alphaValue * 255).toInt()
            canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
        }
        fun startAlphaAnimation() {
            ValueAnimator.ofFloat(1f, 0f).apply {
                duration = 1000
                addUpdateListener {
                    alphaValue = it.animatedValue as Float
                    invalidate()
                }
                start()
            }
        }
    }
    
    • 结合逐帧动画和属性动画,可以在逐帧动画的某个阶段触发属性动画,或者同时进行。例如,在逐帧动画播放到一半时开始属性动画:
    val animation = imageView.drawable as AnimationDrawable
    animation.addAnimationListener(object : Animation.AnimationListener {
        override fun onAnimationStart(animation: Animation?) {}
        override fun onAnimationEnd(animation: Animation?) {}
        override fun onAnimationRepeat(animation: Animation?) {
            if (animation.currentAnimationTime >= animation.duration / 2) {
                customView.startAlphaAnimation()
            }
        }
    })
    animation.start()
    

性能优化

  1. 减少不必要的绘制
    • 使用 invalidate(Rect) 方法代替 invalidate(),只重绘发生变化的区域。例如,在属性动画中,如果只是视图的某个小部分发生变化,计算出该部分的 Rect 区域,调用 invalidate(Rect)
    • 使用 View.setWillNotDraw(false) 来标记视图会自行绘制,避免系统不必要的默认绘制。
  2. 优化动画资源
    • 对于逐帧动画,减少帧的数量,尽量使用可复用的帧。压缩帧图片的大小,使用合适的图片格式(如 WebP 对于 Android 5.0+)。
    • 对于属性动画,合理设置动画的时长和帧率,避免过高的帧率导致性能浪费。例如,对于大多数设备,60fps 已经足够流畅,不需要设置更高的帧率。
  3. 硬件加速
    • 开启硬件加速,在 AndroidManifest.xml 文件中,为 application 或者特定的 activity 标签添加 android:hardwareAccelerated="true"。这可以利用 GPU 来加速绘制操作,提高动画的流畅度。但要注意,某些复杂的绘制操作在硬件加速下可能会出现问题,需要进行针对性的处理。

性能瓶颈及解决办法

  1. 内存问题
    • 瓶颈:逐帧动画中,如果帧图片过多或过大,会占用大量内存,导致内存溢出。属性动画频繁创建 ValueAnimator 等对象也可能造成内存压力。
    • 解决办法:如上述优化动画资源所述,减少帧数量和压缩图片大小。对于属性动画,复用 ValueAnimator 对象,避免频繁创建和销毁。
  2. 绘制性能瓶颈
    • 瓶颈:复杂的绘制操作,如多层重叠绘制、大量的路径绘制等,会导致 CPU 负载过高,影响动画流畅度。
    • 解决办法:简化绘制逻辑,尽量使用简单的图形绘制,避免复杂的路径计算。可以将一些静态部分预先绘制到 Bitmap 上,在动画过程中直接绘制 Bitmap,减少实时绘制的工作量。
  3. 帧率不稳定
    • 瓶颈:设备性能差异、过多的后台任务等都可能导致帧率不稳定,出现卡顿。
    • 解决办法:通过上述性能优化方法,减少动画对系统资源的占用。同时,在动画实现中,可以使用 Choreographer 来同步动画与系统的垂直同步信号(VSync),确保动画在合适的时机进行更新,提高帧率的稳定性。例如:
    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // 在这里更新动画状态
        invalidate()
        Choreographer.getInstance().postFrameCallback(this)
    }