在 Android 应用开发中,优雅的动画效果能够极大地提升用户体验。Android 提供了多种动画系统,而视图动画(View Animation)——也被称为补间动画(Tween Animation)——是最基础也是最常用的动画框架之一。

本文将带你深入理解 Android 视图动画的工作原理,逐一剖析透明度、旋转、平移、缩放这四大经典动画的 API 细节,并结合实际代码演示如何将它们组合使用。


1. 视图动画的核心工作原理

视图动画(View Animation)框架主要定义了透明度(Alpha)、旋转(Rotate)、缩放(Scale)和位移(Translate)几种常见的渐变动画。它只控制 View 的绘制效果,而不会真正改变 View 的属性(如宽高、点击事件响应区域等)。

运行机制

视图动画的渲染 and 驱动主要在 View 绘制流程中完成:

  1. 矩阵变换:每次绘制子视图时,View 所在的 ViewGroup 中的 drawChild 函数会获取该 View 的 Animation 的 Transformation 值。
  2. 画布操作:ViewGroup 通过调用 canvas.concat(transformToApply.getMatrix()),利用矩阵运算对 Canvas 进行变换,完成动画帧的绘制。
  3. 循环驱动:如果动画未播放完毕,系统会继续调用 invalidate() 函数,触发下一次重绘,从而源源不断地生成动画帧,直到动画结束。

WARNING

关键局限性(面试高频考点)
视图动画仅仅改变了 View 绘制在屏幕上的视觉影像,而 View 真正的物理坐标和事件响应区域并未发生改变。例如:如果你将一个按钮通过位移动画平移了 300 像素,你的手指去点击新位置的按钮是无效的,必须点击原位置才能触发点击事件。若需要改变实际属性,请使用属性动画(Property Animation)


2. 四大基础补间动画详解

Android 提供了四个具体的子类来实现基础动画效果。我们在使用时既可以通过 XML 文件定义,也可以直接在 Kotlin/Java 代码中动态构建。

2.1 AlphaAnimation(透明度动画)

透明度动画用于为视图添加淡入淡出效果,非常适合用在页面转场或内容载入时。

构造函数关键参数:

参数数据类型意义
fromAlphaFloat动画起始时的透明度。取值范围为 0.0F(完全透明)到 1.0F(完全不透明)。
toAlphaFloat动画结束时的透明度。取值范围同上。

代码实现:

/**
 * 演示:将视图从完全透明(0F)渐变至完全不透明(1F)
 */
private fun playAlphaAnimation(targetView: View) {
    val alphaAnim = AlphaAnimation(0F, 1F).apply {
        duration = 1000 // 动画持续时间,单位毫秒
        fillAfter = true // 动画结束后保持结束时的状态
    }
    targetView.startAnimation(alphaAnim)
}

2.2 RotateAnimation(旋转动画)

旋转动画用于让视图绕着某个锚点进行角度旋转。例如:音乐播放器的黑胶唱片旋转、加载中的 Loading 圆圈等。

构造函数关键参数:

参数类型意义与取值
fromDegreesFloat旋转开始的角度。
toDegreesFloat旋转结束的角度(顺时针为正数,逆时针为负数)。
pivotXTypeIntX 轴旋转中心点的定位模式。
Animation.ABSOLUTE:绝对像素值。
Animation.RELATIVE_TO_SELF:相对于 View 自身宽度的百分比。
Animation.RELATIVE_TO_PARENT:相对于父布局宽度的百分比。
pivotXValueFloatX 轴中心点的值。若为百分比模式,取值范围通常为 0.0F ~ 1.0F
pivotYTypeIntY 轴旋转中心点的定位模式(取值同 pivotXType)。
pivotYValueFloatY 轴中心点的值(取值同 pivotXValue)。

代码实现:

// 示例 1:以 View 左上角 (0, 0) 为中心顺时针旋转 360 度
private fun rotateFromTopLeft(targetView: View) {
    val rotateAnim = RotateAnimation(0F, 360F, 0F, 0F).apply {
        duration = 1000
    }
    targetView.startAnimation(rotateAnim)
}
 
// 示例 2:以 View 自身的正中心为锚点旋转 360 度(最常用)
private fun rotateAroundCenter(targetView: View) {
    val rotateAnim = RotateAnimation(
        0F,
        360F,
        Animation.RELATIVE_TO_SELF, 0.5F, // 宽度的 50%
        Animation.RELATIVE_TO_SELF, 0.5F  // 高度的 50%
    ).apply {
        duration = 1000
        repeatCount = Animation.INFINITE // 无限循环
        repeatMode = Animation.RESTART   // 重新开始播放
    }
    targetView.startAnimation(rotateAnim)
}

2.3 TranslateAnimation(位移动画)

位移动画用于让视图在二维平面上做位置偏移。适合抽屉栏拉出、气泡浮动等场景。

构造函数关键参数:

位移动画需要指定 X 轴和 Y 轴的起始点与结束点,共 8 个参数:

参数说明
fromXType / toXType起始/结束 X 坐标的定位模式(ABSOLUTE / RELATIVE_TO_SELF / RELATIVE_TO_PARENT)
fromXValue / toXValue起始/结束 X 坐标的具体数值
fromYType / toYType起始/结束 Y 坐标的定位模式
fromYValue / toYValue起始/结束 Y 坐标的具体数值

代码实现:

// 示例:将视图在 1 秒内从左上角相对偏移位移到 (X: 200px, Y: 300px) 的位置
private fun translateView(targetView: View) {
    val translateAnim = TranslateAnimation(
        Animation.ABSOLUTE, 0F, Animation.ABSOLUTE, 200F,
        Animation.ABSOLUTE, 0F, Animation.ABSOLUTE, 300F
    ).apply {
        duration = 1000
        fillAfter = true
    }
    targetView.startAnimation(translateAnim)
}

2.4 ScaleAnimation(缩放动画)

缩放动画可以实现视图尺寸的放大或缩小,同样支持指定缩放中心点。

构造函数关键参数:

参数意义备注
fromX / toX水平方向的起始/结束缩放比例1.0 代表原始尺寸,0.0 代表缩放到无,2.0 代表放大两倍
fromY / toY垂直方向的起始/结束缩放比例同上
pivotXType / pivotYType缩放中心点的类型默认 ABSOLUTE,常用 RELATIVE_TO_SELF
pivotXValue / pivotYValue缩放中心点的值常用 0.5F 表示正中心

代码实现:

// 示例 1:以左上角为原点,从 0 倍无缝放大至 2 倍
private fun scaleFromTopLeft(targetView: View) {
    val scaleAnim = ScaleAnimation(0F, 2F, 0F, 2F).apply {
        duration = 1000
    }
    targetView.startAnimation(scaleAnim)
}
 
// 示例 2:以 View 自身的中心为锚点,实现收缩回弹效果
private fun scaleSelfFromCenter(targetView: View) {
    val scaleAnim = ScaleAnimation(
        0F, 1.0F, 0F, 1.0F,
        Animation.RELATIVE_TO_SELF, 0.5F,
        Animation.RELATIVE_TO_SELF, 0.5F
    ).apply {
        duration = 1000
        // 可以搭配插值器(Interpolator)来实现弹性回弹
        interpolator = android.view.animation.OvershootInterpolator()
    }
    targetView.startAnimation(scaleAnim)
}

3. AnimationSet(动画集合)

当我们需要多个动画同时或者按顺序执行时,可以使用 AnimationSet。例如:在位移的过程中,同时进行淡入与放大。

构造函数关键参数:

  • shareInterpolator:若传入 true,则集合中的所有子动画都会共用 AnimationSet 设置的插值器;若为 false,则子动画使用各自独立的插值器。

代码实现:

/**
 * 组合动画:位移的同时淡入
 */
private fun playCombinedAnimation(targetView: View) {
    // 1. 创建动画集合,并共用同一个插值器
    val animationSet = AnimationSet(true).apply {
        duration = 1000
        fillAfter = true
    }
    
    // 2. 创建淡入动画
    val alpha = AlphaAnimation(0F, 1F)
    animationSet.addAnimation(alpha)
    
    // 3. 创建平移动画
    val translate = TranslateAnimation(
        Animation.RELATIVE_TO_SELF, 0F, Animation.RELATIVE_TO_SELF, 0.5F,
        Animation.RELATIVE_TO_SELF, -1F, Animation.RELATIVE_TO_SELF, 0F
    )
    animationSet.addAnimation(translate)
    
    // 4. 开始播放集合动画
    targetView.startAnimation(animationSet)
}

4. 总结与开发建议

  1. 简单效果首选:如果你只需要简单的页面转场动画、Loading 旋转、控件淡入淡出,且不需要交互(点击事件),视图动画(补间动画)是性能极佳且轻量化的选择。
  2. 交互控件避坑:如果你的动画控件在移动或缩放后还需要接受用户的点击、滑动等输入事件,请务必使用属性动画(Property Animation),否则会产生点击事件留在原处的尴尬 Bug。
  3. 生命周期管理:在 Activity 或 Fragment 销毁时,建议调用 view.clearAnimation() 清除正在运行的动画,防止可能导致的内存泄漏。