30、动画基础:补间动画与属性动画

动画这东西,说白了就是让界面“活”起来。我刚开始做Android开发那会儿,觉得动画就是个花架子,后来才发现——好的动画能让用户体验提升一个档次。今天咱们就聊聊Android里最基础也最常用的两种动画:补间动画和属性动画。

补间动画(Tween Animation)

补间动画,也叫View动画。它只改变View的绘制效果,不改变View的真实属性。什么意思呢?你想想看,一个按钮做了平移动画后,点击事件还在原来的位置——这就是补间动画的“坑”。

补间动画支持四种效果:

  • Alpha — 透明度变化
  • Scale — 缩放变化
  • Translate — 平移变化
  • Rotate — 旋转变化

它们可以单独使用,也可以组合使用。我习惯用XML定义动画,这样复用起来方便。

XML方式定义补间动画

res/anim/ 目录下创建XML文件:

<!-- fade_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.0"
    android:toAlpha="1.0"
    android:duration="1000" />
<!-- scale_rotate.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:duration="800">
    <scale
        android:fromXScale="0.5"
        android:toXScale="1.5"
        android:fromYScale="0.5"
        android:toYScale="1.5"
        android:pivotX="50%"
        android:pivotY="50%" />
    <rotate
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%" />
</set>

在代码里加载并启动:

Animation fadeIn = AnimationUtils.loadAnimation(this, R.anim.fade_in);
view.startAnimation(fadeIn);

Animation scaleRotate = AnimationUtils.loadAnimation(this, R.anim.scale_rotate);
imageView.startAnimation(scaleRotate);
⚠️ 注意:补间动画执行完后,View会回到原来的位置和状态。除非你设置了 fillAfter="true",但即使这样,View的点击区域也不会变。我曾经因为这个bug被测试追着改了一下午……

代码方式创建补间动画

有时候动态创建更方便:

TranslateAnimation translate = new TranslateAnimation(
    Animation.RELATIVE_TO_SELF, 0f,
    Animation.RELATIVE_TO_SELF, 200f,
    Animation.RELATIVE_TO_SELF, 0f,
    Animation.RELATIVE_TO_SELF, 0f
);
translate.setDuration(500);
translate.setInterpolator(new BounceInterpolator());
view.startAnimation(translate);

嗯,这里要注意 Interpolator(插值器)。它控制动画的变化速率。默认是线性,但你可以用 AccelerateInterpolatorBounceInterpolator 等,让动画更自然。

属性动画(Property Animation)

属性动画是Android 3.0(API 11)引入的。它直接改变对象的真实属性。说白了,补间动画是“假动”,属性动画是“真动”。

核心类就一个:ObjectAnimator。用法非常简洁:

ObjectAnimator animator = ObjectAnimator.ofFloat(
    view,           // 目标对象
    "translationX", // 属性名
    0f,             // 起始值
    300f            // 结束值
);
animator.setDuration(1000);
animator.start();

常用的属性有:

  • translationX / translationY — 平移
  • rotation / rotationX / rotationY — 旋转
  • scaleX / scaleY — 缩放
  • alpha — 透明度
  • x / y — 绝对位置
💡 小技巧:属性动画要求目标对象必须有对应的setter方法。比如 setTranslationX(float)。如果自定义View没有这个setter,动画会直接崩掉。我踩过这个坑,后来养成了习惯——写自定义View时先把常用属性的setter补上。

组合动画:AnimatorSet

多个动画一起播放,用 AnimatorSet

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 0.5f, 1.2f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 0.5f, 1.2f);

AnimatorSet set = new AnimatorSet();
set.playTogether(alpha, scaleX, scaleY); // 同时播放
// set.playSequentially(alpha, scaleX);  // 顺序播放
set.setDuration(600);
set.start();

我个人习惯用 playTogether 做入场动画,用 playSequentially 做步骤引导动画。

ValueAnimator:更底层的控制

如果你需要更精细的控制,可以用 ValueAnimator。它不直接操作对象,而是不断回调数值:

ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        // 自己控制View的变化
        view.setAlpha(value);
        view.setScaleX(value);
    }
});
animator.start();

说白了,ObjectAnimatorValueAnimator 的封装版。大部分场景用 ObjectAnimator 就够了。

补间动画 vs 属性动画

对比项 补间动画 属性动画
改变真实属性 ❌ 不改变 ✅ 改变
点击区域 不变 跟随移动
支持属性 仅4种 任意有setter的属性
性能 较好(只重绘) 稍差(触发invalidate)
API级别 API 1+ API 11+

我的建议是:新项目直接用属性动画。除非你要兼容非常老的设备(API 11以下),或者只是做一个简单的淡入淡出效果。

知识体系总览

下面这张图帮你理清今天讲的内容:

Android 动画基础 补间动画 (Tween) 属性动画 (Property) Alpha 透明度 Scale 缩放 Translate 平移 Rotate 旋转 ObjectAnimator ValueAnimator AnimatorSet Interpolator 推荐:新项目使用属性动画,兼容性要求高时用补间动画

避坑指南

最后分享几个我踩过的坑:

  • 补间动画的点击事件错位 — 我曾经做了一个按钮飞入动画,结果用户怎么点都没反应。后来发现点击区域还在屏幕外。解决方案:用属性动画代替,或者手动更新点击区域。
  • 属性动画内存泄漏 — 动画是无限循环的,Activity销毁时记得调用 animator.cancel()。我习惯在 onDestroy() 里统一清理。
  • 动画卡顿 — 如果动画掉帧,检查是不是在动画过程中做了耗时操作。另外,用 ViewPropertyAnimator 可以自动优化性能:view.animate().alpha(0f).translationX(100f).start();

动画这东西,多练几次就熟了。关键是理解补间动画和属性动画的本质区别——一个改“面子”,一个改“里子”。


公众号:蓝海资料掘金营,微信deep3321