面试题答案
一键面试实现复杂动画效果(逐帧动画和属性动画结合)
- 逐帧动画实现:
- 在
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()
- 在
- 属性动画实现:
- 例如,对自定义视图的某个属性(如透明度、缩放等)进行动画。假设自定义视图继承自
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()
- 例如,对自定义视图的某个属性(如透明度、缩放等)进行动画。假设自定义视图继承自
性能优化
- 减少不必要的绘制:
- 使用
invalidate(Rect)
方法代替invalidate()
,只重绘发生变化的区域。例如,在属性动画中,如果只是视图的某个小部分发生变化,计算出该部分的Rect
区域,调用invalidate(Rect)
。 - 使用
View.setWillNotDraw(false)
来标记视图会自行绘制,避免系统不必要的默认绘制。
- 使用
- 优化动画资源:
- 对于逐帧动画,减少帧的数量,尽量使用可复用的帧。压缩帧图片的大小,使用合适的图片格式(如 WebP 对于 Android 5.0+)。
- 对于属性动画,合理设置动画的时长和帧率,避免过高的帧率导致性能浪费。例如,对于大多数设备,60fps 已经足够流畅,不需要设置更高的帧率。
- 硬件加速:
- 开启硬件加速,在
AndroidManifest.xml
文件中,为application
或者特定的activity
标签添加android:hardwareAccelerated="true"
。这可以利用 GPU 来加速绘制操作,提高动画的流畅度。但要注意,某些复杂的绘制操作在硬件加速下可能会出现问题,需要进行针对性的处理。
- 开启硬件加速,在
性能瓶颈及解决办法
- 内存问题:
- 瓶颈:逐帧动画中,如果帧图片过多或过大,会占用大量内存,导致内存溢出。属性动画频繁创建
ValueAnimator
等对象也可能造成内存压力。 - 解决办法:如上述优化动画资源所述,减少帧数量和压缩图片大小。对于属性动画,复用
ValueAnimator
对象,避免频繁创建和销毁。
- 瓶颈:逐帧动画中,如果帧图片过多或过大,会占用大量内存,导致内存溢出。属性动画频繁创建
- 绘制性能瓶颈:
- 瓶颈:复杂的绘制操作,如多层重叠绘制、大量的路径绘制等,会导致 CPU 负载过高,影响动画流畅度。
- 解决办法:简化绘制逻辑,尽量使用简单的图形绘制,避免复杂的路径计算。可以将一些静态部分预先绘制到
Bitmap
上,在动画过程中直接绘制Bitmap
,减少实时绘制的工作量。
- 帧率不稳定:
- 瓶颈:设备性能差异、过多的后台任务等都可能导致帧率不稳定,出现卡顿。
- 解决办法:通过上述性能优化方法,减少动画对系统资源的占用。同时,在动画实现中,可以使用
Choreographer
来同步动画与系统的垂直同步信号(VSync),确保动画在合适的时机进行更新,提高帧率的稳定性。例如:
Choreographer.getInstance().postFrameCallback { frameTimeNanos -> // 在这里更新动画状态 invalidate() Choreographer.getInstance().postFrameCallback(this) }