【Android】Android 的三种动画(帧动画、View 动画、属性动画)

494 阅读21分钟

【Android】Android 的三种动画(帧动画、View 动画、属性动画)

Android 的动画可以分为三种:View 动画、帧动画和属性动画,其中帧动画也属于 View 动画的一种,只不过它和平移、旋转等常见的 View 动画在表现形式上略有不同。View 动画通过对场景里的对象不断做图像变换(平移、缩放、旋转、透明度)从而产生动画效果,它是一种渐进式动画,并且 View 动画支持自定义。帧动画通过顺序播放一系列图像从而产生动画效果,可以简单理解为图片切换动画,如果图片过多过大就会导致 OOM。属性动画通过动态地改变对象的属性从而达到动画效果。

帧动画

帧动画是通过连续播放一系列图像来形成动画,类似于GIF。

帧动画的使用比较简单,首先需要通过 XML 来定义一个 AnimationDrawable, 如下所示:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/image1" android:duration="800"/>
    <item android:drawable="@drawable/image2" android:duration="800"/>
    <item android:drawable="@drawable/image3" android:duration="800"/>

</animation-list>

然后将上述的 Drawable 作为 View 的背景并通过 Drawable 来播放即可:

<ImageView
    android:id="@+id/image_view"
    android:layout_width="160dp"
    android:layout_height="160dp"
    android:layout_gravity="center"
    android:src="@drawable/frame_animation"
    android:scaleType="centerCrop" />
ImageView imageView = findViewById(R.id.image_view);
AnimationDrawable drawable = (AnimationDrawable) imageView.getDrawable();
drawable.start();

效果如下:

帧动画的使用比较简单,但是比较容易引起 OOM(内存溢出),所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。

View 动画(补间动画)

View动画是一种在视图上执行简单变换(如平移、旋转、缩放和透明度变化)的动画系统。它也被称为补间动画,因为它在视图的起始状态和结束状态之间生成中间帧。我们只需要拿到一个view,设定它开始和结束的位置,中间的view会自动由系统补齐,而不需要帧动画每一幅图都是提前准备好的。

View动画的四种基本效果对应了四个Animation的子类,如下:

要使用 View 动画,首先要在res/anim创建动画的 XML 文件。View 动画的描述文件基本结构如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="true|false"
    android:duration="整数毫秒值"
    android:fillAfter="true|false"
    android:fillBefore="true|false"
    android:repeatCount="整数|infinite"
    android:repeatMode="restart|reverse"
    android:interpolator="@android:anim/插值器资源">
    
    <!-- 包含的动画元素 -->
    <alpha/>
    <scale/>
    <rotate/>
    <translate/>
    
</set>

四种基本动画类型

1. 透明度动画 (Alpha)
<alpha
    android:fromAlpha="0.0-1.0"
    android:toAlpha="0.0-1.0"
    android:duration="毫秒"
    android:startOffset="延迟毫秒"
    android:repeatCount="整数|infinite"
    android:repeatMode="restart|reverse"/>

示例:淡入效果

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.0"
    android:toAlpha="1.0"
    android:duration="1000"/>
  • android:fromAlpha="0.0":起始透明度,完全透明。
  • android:toAlpha="1.0":结束透明度,完全不透明。
  • android:duration="1000":动画持续 1000 毫秒。

效果:控件在 1 秒内,从透明逐渐变为可见,也就是常见的 淡入效果。

2. 缩放动画 (Scale)
<scale
    android:fromXScale="起始X缩放"
    android:toXScale="结束X缩放"
    android:fromYScale="起始Y缩放"
    android:toYScale="结束Y缩放"
    android:pivotX="X轴基准点"
    android:pivotY="Y轴基准点"
    android:duration="毫秒"/>

基准点取值:

  • 50%:相对于自身中心
  • 50%p:相对于父容器中心
  • 数值:具体像素值

示例:中心放大效果

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0.5"
    android:toXScale="1.0"
    android:fromYScale="0.5"
    android:toYScale="1.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="600"/>
  • android:fromXScale="0.5":X 方向初始缩放比例为 0.5(即宽度一半)。
  • android:toXScale="1.0":X 方向最终缩放比例为 1.0(即恢复正常宽度)。
  • android:fromYScale="0.5":Y 方向初始缩放比例为 0.5(即高度一半)。
  • android:toYScale="1.0":Y 方向最终缩放比例为 1.0。
  • android:pivotX="50%":缩放的中心点 X 在控件宽度的 50%(中心位置)。
  • android:pivotY="50%":缩放的中心点 Y 在控件高度的 50%(中心位置)。
  • android:duration="600":动画持续 600 毫秒。

效果:控件从一半大小逐渐放大到正常大小,类似 “弹出效果”。

3. 旋转动画 (Rotate)
<rotate
    android:fromDegrees="起始角度"
    android:toDegrees="结束角度"
    android:pivotX="X轴旋转中心"
    android:pivotY="Y轴旋转中心"
    android:duration="毫秒"/>

示例:无限旋转

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="infinite"
    android:duration="1000"/>
  • android:fromDegrees="0":动画开始时的角度,0°。
  • android:toDegrees="360":动画结束时的角度,360°,即完整旋转一圈。
  • android:pivotX="50%":旋转中心点 X 轴在控件宽度的 50%(居中)。
  • android:pivotY="50%":旋转中心点 Y 轴在控件高度的 50%(居中)。
  • android:repeatCount="infinite":动画无限循环。
  • android:duration="1000":动画时长 1000ms(1秒),即每秒旋转一圈。

效果:控件会围绕自身中心点 不断顺时针旋转。

4. 平移动画 (Translate)
<translate
    android:fromXDelta="起始X位置"
    android:toXDelta="结束X位置"
    android:fromYDelta="起始Y位置"
    android:toYDelta="结束Y位置"
    android:duration="毫秒"/>

位置取值:

  • 数值:具体像素值
  • n%:相对于自身宽高的百分比
  • n%p:相对于父容器宽高的百分比

示例:从右侧滑入

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="100%p"
    android:toXDelta="0"
    android:duration="400"/>
  • android:fromXDelta="100%p":动画开始时,X 方向位置相对父控件宽度的 100%,也就是在屏幕外右侧。
  • android:toXDelta="0":动画结束时,X 方向位移为 0,也就是回到原位置。
  • android:duration="400":动画持续时间为 400 毫秒。

效果:让一个视图从屏幕右侧滑入到原本的位置。

组合动画 (Set)

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together|sequentially">
    
    <!-- 包含多个动画元素 -->
    <alpha .../>
    <scale .../>
    <rotate .../>
    
</set>

ordering属性:

  • together(默认):所有动画同时执行
  • sequentially:动画按顺序执行

示例:组合动画(淡入+放大)

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="true"
    android:duration="600">
    
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"/>
        
    <scale
        android:fromXScale="0.5"
        android:toXScale="1.0"
        android:fromYScale="0.5"
        android:toYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"/>
        
</set>
  • <set>:表示一个动画集合,里面可以放多个 <alpha>, <scale>, <translate>, <rotate> 等动画。
  • android:shareInterpolator="true":表示集合里所有动画共享同一个插值器(默认是加速-减速插值)。
  • android:duration="600":默认持续时间 600 毫秒,作用于所有子动画(除非子动画自己定义了 duration)。

子动画:

  1. alpha:从完全透明 0.0 → 完全不透明 1.0(淡入)。
  2. scale:从 0.5 倍大小 → 1.0 正常大小(中心点在控件的 50%,即正中间)。

效果:控件在 600ms 内,从小且透明 → 放大并逐渐显示,就像“弹出淡入”的效果。

常用属性详解

  • duration:动画持续时间(毫秒)

  • startOffset:动画开始前的延迟时间(毫秒)

  • repeatCount:重复次数(整数或" infinite ")

  • repeatMode:restart:从头开始重复(默认)reverse:反向重复

  • fillAfter:动画结束后保持结束状态

  • fillBefore:动画开始前应用初始状态(默认true)

  • fillEnabled:启用 fillBefore / fillAfter

插值器 (Interpolator)

控制动画速度变化:

android:interpolator="@android:anim/插值器资源"
  • android:shareInterpolator:表示集合中的动画是否和集合共享同一个插值器。如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或者使用默认值。

常用系统插值器:

  • @android:anim/linear_interpolator:匀速
  • @android:anim/accelerate_interpolator:加速
  • @android:anim/decelerate_interpolator:减速
  • @android:anim/accelerate_decelerate_interpolator:先加速后减速
  • @android:anim/bounce_interpolator:弹跳效果
  • @android:anim/anticipate_interpolator:先回拉再前进
  • @android:anim/overshoot_interpolator:超出终点再返回
  • @android:anim/anticipate_overshoot_interpolator:先回拉再超出

完整示例

1. 按钮点击效果(缩放)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:fromXScale="1.0"
        android:toXScale="0.9"
        android:fromYScale="1.0"
        android:toYScale="0.9"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="100"/>

    <scale
        android:fromXScale="0.9"
        android:toXScale="1.0"
        android:fromYScale="0.9"
        android:toYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:startOffset="100"
        android:duration="100"/>
</set>
  1. 第一个 <scale>
    • fromXScale="1.0" → toXScale="0.9"
    • fromYScale="1.0" → toYScale="0.9"
    • 在 100 毫秒 内,控件从正常大小缩小到 90%。
    • 看起来就像“按下去”的效果。
  2. 第二个 <scale>
    • fromXScale="0.9" → toXScale="1.0"
    • fromYScale="0.9" → toYScale="1.0"
    • 有 android:startOffset="100",所以它会在第一个动画执行完后再开始。
    • 在 100 毫秒 内,把控件从缩小的 90% 恢复到正常大小。
    • 看起来像“弹回去”。

总体效果:控件 先缩小一下,再恢复,类似点击按钮时的 按压反馈动效。

2. 列表项入场动画(顺序显示)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially"
    android:duration="400">

    <translate
        android:fromYDelta="20%"
        android:toYDelta="0"
        android:interpolator="@android:anim/decelerate_interpolator"/>

    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:interpolator="@android:anim/accelerate_interpolator"/>
</set>
  • android:ordering="sequentially":表示子动画 按顺序执行(而不是同时执行)。
  • android:duration="400":默认持续时间为 400ms(每个子动画如果没单独设置,会用这个时间)。

子动画:

  1. translate
    • fromYDelta="20%" → toYDelta="0"
    • 控件从自身高度的 20% 下方 位置,向上移动到原位。
    • @android:anim/decelerate_interpolator:减速插值器,开始快 → 结束慢。
    • 效果:控件 向上滑入。
  2. alpha
    • fromAlpha="0" → toAlpha="1"
    • 控件从完全透明 → 完全可见。
    • @android:anim/accelerate_interpolator:加速插值器,开始慢 → 结束快。
    • 效果:控件 淡入显示。

整体效果:控件先从下方滑入,然后再淡入显示。

3. 页面切换动画(滑入滑出)
<!-- res/anim/slide_in_left.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="-50%p"
        android:toXDelta="0"
        android:duration="300"/>
    <alpha
        android:fromAlpha="0.5"
        android:toAlpha="1"
        android:duration="300"/>
</set>

<!-- res/anim/slide_out_right.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%p"
        android:duration="300"/>
    <alpha
        android:fromAlpha="1"
        android:toAlpha="0.5"
        android:duration="300"/>
</set>

slide_in_left.xml

  • translate:从左边 -50% 宽度的位置滑动到 0(原位)。
  • alpha:从半透明(0.5)变为完全不透明(1.0)。
  • 效果:一个控件会从左边滑入,并逐渐清晰。

slide_out_right.xml

  • translate:从原位移动到右边屏幕外(100%)。
  • alpha:从不透明(1.0)变为半透明(0.5)。
  • 效果:一个控件会向右滑出,同时逐渐消失。

在代码中加载动画

// 加载动画资源
Animation animation = AnimationUtils.loadAnimation(context, R.anim.your_animation);

// 应用动画
View view = findViewById(R.id.your_view);
view.startAnimation(animation);

// 设置动画监听
animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        // 动画开始
    }
    
    @Override
    public void onAnimationEnd(Animation animation) {
        // 动画结束
    }
    
    @Override
    public void onAnimationRepeat(Animation animation) {
        // 动画重复
    }
});

View 动画的特殊使用场景

除了上面四种形式之外,View 动画还可以在一些特殊的场景下使用,比如在 ViewGroup 种可以控制子元素的出场效果,在 Activity 种可以实现不同 Activity 之间的切换效果。

1. LayoutAnimation

LayoutAnimation 作用于 ViewGroup,为 ViewGroup 指定一个动画,这样当它的子元素出场时都会具有这种动画效果。LayoutAnimation 也是一个 View 动画,为了给 ViewGroup 的子元素加上出场效果,遵循如下几个步骤:

(1)定义 LayoutAnimation 控制器(指定每个子项播放顺序和间隔)

如下所示:

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.15"
    android:animationOrder="normal"
    android:animation="@anim/item_anim" />

android:delay

指定每个子 View 动画开始的间隔时间(相对于动画时长的比例)比如:

  • 动画时长是 400ms;
  • delay = 0.15; → 第一个子 View 立即播放; → 第二个子 View 延迟 0.15 × 400 = 60ms; → 第三个延迟 120ms,以此类推。

android:animationOrder

控制子 View 动画的播放顺序:

  • normal:从第一个子 View 开始,按顺序播放(常用)
  • reverse:从最后一个子 View 开始,倒序播放
  • random:随机顺序播放(每个子项随机延迟)
(2)定义单个 item 的动画(滑入 + 淡入)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="400">
    <translate
        android:fromYDelta="30%"
        android:toYDelta="0" />

    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
</set>
(3)为 ViewGroup 指定 layoutAnimation属性

例如:

<ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:dividerHeight="8dp"
    android:layoutAnimation="@anim/layout_animation" />

这样 ListView 的 item 就具有出场动画了,这种方式适用于所有的 ViewGroup。

除了在 XML 种指定 LayoutAnimation 外,还可以通过 LayoutAnimationController 来实现,具体代码如下所示:

ListView listView = (ListView)findViewById(R.id.listView);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.item_anim);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.15f);
controller.setOrder(LayoutAnimationController.ORDER.NORMAL);
listView.setLayoutAnimation(controller);

效果如下:

2. Activity 的切换效果

Activity 有默认的切换效果,但是这个效果我们是可以自定义的,主要用到 overridePendingTransition(int enterAnim, int exitAnim)这个方法,这个方法必须在 startActivity(Intent)或者finish()之后被调用才能生效,它的参数含义如下:

  • enterAnim:Activity 被打开时,所需的动画资源 id
  • exitAnim:Activity 被暂停时,所需的动画资源 id

当启动一个 Activity 时,可以按照如下方式为其添加自定义的切换效果:

Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim, R.animexit_anim);

当 Activity 退出时,也可以为其指定自己的切换效果,如下所示:

@override
public void finish() {
    super.finish();
    overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}

需要注意的是,overridePendingTransition 这个方法必须位于 startActivity 或者 finish 后面,否则动画效果将不起作用。

属性动画

属性动画是Android 3.0(API 11)引入的强大动画框架,相比View动画,它能够实际改变对象的属性值,提供更灵活、更强大的动画能力。

在 Android 3.0 之前,动画系统主要依赖于 补间动画 和 帧动画。但它们有一个致命的缺陷:补间动画只是视觉上的变化,而非真正改变 View 的属性。例如,你让一个按钮从左边移动到右边(平移)。动画结束后,按钮看起来在右边,但你点击右边却没有反应,因为它的实际位置(left, top, right, bottom)还在原处。你只能点击原来的位置。这非常反直觉。属性动画 就是为了解决这个问题而诞生的。它的核心思想是:在指定的时间间隔内,通过不断地改变一个对象的真实属性的值,并同时刷新显示,来实现动画效果。

核心类介绍:

  • ValueAnimation:数值动画器,驱动数值变化。
  • ObjectAnimation:对象动画器,自动更新对象属性。
  • AnimationSet:动画集合,管理多个动画的播放顺序和组合关系。
  • PropertyValuesHolder:属性值持有者,封装属性和对应的数值序列,实现一个动画改变多个属性。
  • Keyframe:关键帧,定义动画路径中的关键节点,实现复杂动画路径。

属性动画的基本使用

值动画(ValueAnimation)

ValueAnimation是属性动画最核心的类。它的职责很简单:计算动画过程中“值”的变化。它本身不直接操作任何对象的属性,它只负责在设定的时间内,根据起始值和结束值,生成一系列连续的、过渡的“值”。可以把它理解为一个数值发生器。

核心方法:

// 创建方法
ValueAnimator.ofInt(int... values)      // 整型数值动画
ValueAnimator.ofFloat(float... values)  // 浮点型数值动画
ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) // 自定义对象动画
ValueAnimator.ofArgb(int... values)     // 颜色值动画(API 21+)

// 关键配置
setDuration(long duration)              // 设置持续时间
setRepeatCount(int count)               // 设置重复次数
setRepeatMode(int mode)                 // 设置重复模式
setInterpolator(TimeInterpolator value) // 设置插值器

// 监听器
addUpdateListener(AnimatorUpdateListener listener) // 数值更新监听

使用流程:

  1. 创建ValueAnimation,设置目标属性的起始值、结束值和持续时间。
  2. 添加更新监听器,在监听器中获取当前计算出的值。
  3. 手动将这个值赋值给目标对象的属性。

示例:让一个 TextView 上的数字从 1 平滑地变化到 100。

// 1. 创建 ValueAnimator,从 1 到 100
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.setDuration(2000); // 动画持续 2 秒
valueAnimator.setInterpolator(new LinearInterpolator()); // 线性插值器:匀速动画

// 2. 监听每一帧的动画值变化
colorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int currentValue = (int) animation.getAnimatedValue(); // 当前的数值
        textView.setText(String.valueOf(currentValue)); // 手动更新 TextView
    }
});

// 3. 启动动画
colorAnim.start();

对象动画(ObjectAnimation)

ObjectAnimator类是 ValueAnimator 的子类,也是最常用的类。它在 ValueAnimator “计算值” 的基础上,增加了一个核心功能:自动将计算出的值设置给目标对象的指定属性。不需要手动去监听和赋值了。

核心方法:

ofFloat(Object target, String propertyName, float... values)
ofInt(Object target, String propertyName, int... values)  
ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)
ofArgb(Object target, String propertyName, int... values)

使用条件:

要让 ObjectAnimator 正常工作,目标对象必须满足以下至少一个条件:

  1. 拥有该属性的 setter 方法:例如属性叫 foo,则需要有 setFoo() 方法。
  2. 使用包装器:如果对象没有 setter,你可以提供一个包装器,并通过它来设置值。
  3. 或者,你可以直接改变该属性的值(不推荐,违背封装原则)。

示例:将一个 View 在水平方向上平移 500px。

// 参数:目标对象,属性名(字符串),起始值,结束值
ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "translationX", 0f, 300f);
anim.setDuration(1000);
anim.start();

这段代码背后发生了什么?

  1. ObjectAnimator 会计算从 0f 到 500f 之间的一系列浮点数。
  2. 对于每一个计算出的值 value,它会去调用 myView.setTranslationX(value) 方法。
  3. View 的属性被改变,触发重绘,动画就产生了。

常用 View 属性:

  • translationX, translationY:平移
  • rotation, rotationX, rotationY:旋转
  • scaleX, scaleY:缩放
  • alpha:透明度
  • x, y: View 在父容器中的最终坐标(left + translationX)
  • backgroundColor:背景颜色(需要 ArgbEvaluator 支持)
组合动画(AnimationSet)

用于将多个动画(ValueAnimator, ObjectAnimator 或其他 AnimatorSet)组合在一起播放,可以设置它们的先后顺序或同时播放。

核心方法:

// 播放控制
play(Animator anim)                     // 指定要播放的动画
with(Animator anim)                     // 与当前动画同时播放
before(Animator anim)                   // 在当前动画之前播放
after(Animator anim)                    // 在当前动画之后播放

// 便捷方法
playTogether(Animator... items)         // 同时播放多个动画
playSequentially(Animator... items)     // 顺序播放多个动画
playSequentially(List<Animator> items)  // 顺序播放动画列表

示例: 让一个 View 先平移,再旋转,最后淡出。

ObjectAnimator moveIn = ObjectAnimator.ofFloat(myView, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(myView, "alpha", 1f, 0f);

AnimatorSet animSet = new AnimatorSet();
// 按顺序执行
animSet.playSequentially(moveIn, rotate, fadeOut);
// 或者同时执行
// animSet.playTogether(moveIn, rotate, fadeOut);
// 或者更复杂的组合
// animSet.play(rotate).with(fadeOut).after(moveIn);

animSet.setDuration(1000);
animSet.start();

插值器与估值器

1. 插值器(Interpolator)
  • 作用:定义了 动画变化的“节奏”或“速度曲线”。它根据时间流逝的百分比(elapsed fraction,0到1之间)计算出当前动画进度的百分比(interpolated fraction)。
  • 简单来说:输入一个线性变化的时间(比如过了总时间的50%),输出一个非线性的动画进度(比如实际上只完成了20%,或者已经完成了80%)。
  • 系统内置的插值器:
    • LinearInterpolator:线性插值器,匀速动画
    • AccelerateInterpolator:加速插值器,动画越来越快
    • DecelerateInterpolator:减速插值器,动画越来越慢
    • AccelerateDecelerateInterpolator:先加速后减速(默认)
    • BounceInterpolator:弹球效果
    • OvershootInterpolator:先快速完成动画,再回到结束样式
    • AnticipateInterpolator:先后退再加速前进

使用:

animator.setInterpolator(new BounceInterpolator());
2. 类型估值器(TypeEvaluator)
  • 作用:当动画的属性不是 int, float 或 color 这些基本类型时,或者你想对基本类型的变换规则进行自定义时,就需要它。它根据 当前动画进度的百分比(插值器计算出的) 和 起始值、结束值,计算出当前帧的具体属性值。
  • 简单来说:插值器输出“进度”,估值器根据这个“进度”算出“当前值”。
  • 系统内置的估值器:
    • IntEvaluator:用于计算 int 类型。
    • FloatEvaluator:用于计算 float 类型。
    • ArgbEvaluator:用于计算颜色值(非常重要!用于颜色过渡)。
    • RectEvaluator:用于计算 Rect 对象。

示例: 没有 ArgbEvaluator,颜色过渡会非常奇怪。

// 正确使用 ArgbEvaluator 进行颜色过渡
ObjectAnimator colorAnim = ObjectAnimator.ofInt(myView, "backgroundColor", 0xFFFF0000, 0xFF0000FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator()); // 关键!
colorAnim.start();

使用 XML 定义属性动画

与视图动画一样,属性动画也可以写在 res/animator/ 目录下的 XML 文件中,便于复用和预览。

XML 标签:

  • <animator>:对应 ValueAnimator
  • <objectAnimator>:对应 ObjectAnimator
  • <set>:对应 AnimatorSet

示例 (res/animator/scale_and_rotate.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially"> <!-- 顺序执行 -->

    <objectAnimator
        android:propertyName="scaleX"
        android:duration="1000"
        android:valueFrom="1.0"
        android:valueTo="2.0"
        android:valueType="floatType"
        android:interpolator="@android:interpolator/accelerate_decelerate" />

    <objectAnimator
        android:propertyName="scaleY"
        android:duration="1000"
        android:valueFrom="1.0"
        android:valueTo="2.0"
        android:valueType="floatType"
        android:interpolator="@android:interpolator/accelerate_decelerate" />

    <objectAnimator
        android:propertyName="rotation"
        android:duration="1500"
        android:valueFrom="0"
        android:valueTo="360"
        android:valueType="floatType" />

</set>

在代码中加载:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.scale_and_rotate);
set.setTarget(myView); // 设置目标对象
set.start();

ViewPropertyAnimator - 简化多个属性同时动画

当需要同时改变一个 View 的多个属性时(如同时移动、旋转、透明),使用 View.animate() 方法会非常简洁高效。

传统方式 (AnimatorSet):

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "translationX", 100f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "translationY", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

简化方式 (ViewPropertyAnimator):

myView.animate()
        .translationX(100f)
        .translationY(100f)
        .rotation(360f)
        .alpha(0.5f)
        .setDuration(1000)
        .start();

这种方式在性能和代码简洁性上都更优,是同时动画多个 View 属性时的首选。

属性动画的监听器

通过 AnimatorListener 和 AnimatorUpdateListener 来监听动画的各种状态。

  • AnimatorListener:监听开始、结束、取消、重复。

    animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) { }
        @Override
        public void onAnimationEnd(Animator animation) {
            // 动画结束,可以做清理工作或触发下一个动作
        }
        @Override
        public void onAnimationCancel(Animator animation) { }
        @Override
        public void onAnimationRepeat(Animator animation) { }
    });
    

    通常可以使用简化的 AnimatorListenerAdapter,只重写你需要的方法。

    animator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            // 只处理结束事件
        }
    });
    
  • AnimatorUpdateListener:监听每一帧的更新(在 ValueAnimator 中已介绍)。

PropertyValuesHolder

PropertyValuesHolder(简称 PVH)是 ObjectAnimator 的“属性容器”。

普通的 ObjectAnimator 只能动画一个属性:

ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);

而通过 PropertyValuesHolder,你可以同时指定多个属性一起变化,比如:

// 创建多个属性值持有者
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1f);
// 用一个ObjectAnimator同时动画多个属性
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, alpha, scaleX, scaleY);
animator.setDuration(1000);
animator.start();

效果:View 会在 1 秒内同时从半透明放大到正常大小。

核心方法:

ofFloat(String propertyName, float... values)
ofInt(String propertyName, int... values)
ofObject(String propertyName, TypeEvaluator evaluator, Object... values)
ofKeyframe(String propertyName, Keyframe... values)  // 与关键帧结合使用

PropertyValuesHolder 就是让一个 ObjectAnimator 同时控制多个属性变化的容器。它既能简化代码,又能让动画更流畅、同步。

关键帧(Keyframe)

**Keyframe(关键帧)表示属性在动画过程中的一个“中间状态”。**通过一组关键帧,你可以让属性在动画的不同时间点呈现出不同的值和节奏。

核心方法:

// 关键帧创建
Keyframe.ofFloat(float fraction, float value)    // 在fraction时间点值为value
Keyframe.ofInt(float fraction, int value)
Keyframe.ofObject(float fraction, Object value)

// 设置插值器
setInterpolator(TimeInterpolator interpolator)   // 设置该关键帧段的插值器

示例:

// 创建关键帧:在0%、50%、100%时间点分别设置不同的值和插值器
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);                    // 起点
Keyframe kf1 = Keyframe.ofFloat(0.5f, 300f);               // 中间点
kf1.setInterpolator(new BounceInterpolator());             // 中间段使用弹跳效果
Keyframe kf2 = Keyframe.ofFloat(1f, 200f);                 // 终点

// 将关键帧应用到属性
PropertyValuesHolder pvhY = PropertyValuesHolder.ofKeyframe("translationY", kf0, kf1, kf2);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvhY);
animator.setDuration(2000).start();

三种动画的使用场景和总结

  1. 帧动画:播放一系列图片帧(类似 GIF),性能最差,容易OOM,慎用大图或多帧。适合需要“逐帧播放”的效果,比如加载动画、角色动作、表情动画。

  2. View 动画(补间动画):只改变显示效果,不改变真实位置(坐标不变),性能最好,适合大量元素的简单动画。适合简单的 UI 动效,比如控件出现、隐藏、切换。

  3. 属性动画:直接修改对象属性值,真实改变 View 状态或任意对象的属性。适合复杂的、交互性强的动画,例如按钮点击放大、卡片滑动、颜色渐变等。

动画类型适合场景实例
View Animation(补间动画)简单 UI 动效(出现/隐藏/切换)按钮淡入、页面切换、列表进入
Drawable Animation(帧动画)图片帧动画(类似 GIF)加载动画、角色动作、表情动画
Property Animation(属性动画)复杂 UI 交互、属性变化缩放、平移、颜色渐变、旋转、曲线移动

在现在的Android开发中:

  • 属性动画已经成为主流选择
  • View动画主要用于简单的过渡效果
  • 帧动画逐渐被Lottie、SVG动画等替代
  • MotionLayout结合了属性动画和约束布局,成为复杂交互动画的新选择