目录
  1. 1. 简介
  2. 2. 属性动画体系总览
  3. 3. ValueAnimator:属性动画的基石
    1. 3.1. 基本用法
    2. 3.2. ofInt / ofFloat / ofObject
    3. 3.3. setIntValues / setFloatValues 的内部实现
    4. 3.4. ObjectAnimator:自动设置属性值
      1. 3.4.1. 依赖 getter/setter 的机制
      2. 3.4.2. 自定义属性动画:没有 getter/setter 怎么办?
  4. 4. AnimatorSet:编排多个动画
    1. 4.1. 基本用法
    2. 4.2. 核心方法
      1. 4.2.1. playTogether 和 playSequentially
      2. 4.2.2. Builder 模式:before / after / with
    3. 4.3. AnimatorSet 内部的动画依赖图
  5. 5. 插值器(Interpolator / TimeInterpolator)
    1. 5.1. TimeInterpolator 接口
    2. 5.2. 常用插值器深度解析
      1. 5.2.1. AccelerateDecelerateInterpolator(加速减速插值器)
      2. 5.2.2. OvershootInterpolator(超出插值器)
      3. 5.2.3. BounceInterpolator(弹跳插值器)
      4. 5.2.4. PathInterpolator(路径插值器,API 21+)
      5. 5.2.5. AnticipateOvershootInterpolator(预期超出插值器)
      6. 5.2.6. 自定义插值器
  6. 6. 估值器(TypeEvaluator)
    1. 6.1. 内置估值器
      1. 6.1.1. IntEvaluator
      2. 6.1.2. FloatEvaluator
      3. 6.1.3. ArgbEvaluator(颜色估值器)
    2. 6.2. 自定义估值器
  7. 7. ViewPropertyAnimator:为 View 优化的动画接口
    1. 7.1. 基本用法
    2. 7.2. 内部实现
    3. 7.3. ViewPropertyAnimator 的优势
  8. 8. 动画驱动的底层原理
    1. 8.1. Choreographer 驱动的帧循环
    2. 8.2. doAnimationFrame 的详细流程
  9. 9. SpringAnimation:物理弹簧动画(androidx)
    1. 9.1. 基本原理
    2. 9.2. 使用方式
  10. 10. XML 中定义属性动画
    1. 10.1. animator 标签(对应 ValueAnimator)
    2. 10.2. objectAnimator 标签(对应 ObjectAnimator)
    3. 10.3. set 标签(对应 AnimatorSet)
    4. 10.4. 在代码中加载
  11. 11. 常见陷阱与最佳实践
    1. 11.1. 内存泄漏:记得取消动画
    2. 11.2. 硬件层优化
    3. 11.3. 不要在动画回调中做耗时操作
    4. 11.4. View 属性动画的”点击问题”
    5. 11.5. 正确使用 AnimatorListener
  12. 12. 关键源码文件汇总
  13. 13. 总结
UI进阶之属性动画

简介

属性动画(Property Animation)是 Android 3.0(API 11)引入的全新动画框架,用于弥补补间动画(View Animation)的不足。补间动画只能对 View 做平移、旋转、缩放、透明度四种变换,而且只是视觉上的变化,View 的实际位置(点击区域)不会改变。属性动画则可以直接修改对象的任意属性,既能改变视觉效果也能改变实际属性值,极大扩展了动画的应用场景。

本文从 AOSP 源码角度深入分析属性动画系统的核心组件和运行机制,涵盖 ValueAnimator、ObjectAnimator、AnimatorSet、ViewPropertyAnimator、SpringAnimation 五大动画类,以及 Interpolator(插值器)、TypeEvaluator(估值器)两大辅助系统。最后讨论动画驱动的底层原理和常见陷阱。

注意:源码分析基于 Android-30(Android 11),核心源码位于 frameworks/base/core/java/android/animation/ 目录。

属性动画体系总览

属性动画的核心类关系如下:

java.lang.Object
└── android.animation.Animator (抽象类)
├── android.animation.ValueAnimator
│ └── android.animation.ObjectAnimator
└── android.animation.AnimatorSet

辅助类和接口:

android.animation.TimeInterpolator (接口) — 插值器
android.animation.TypeEvaluator (接口) — 估值器
android.animation.Keyframe — 关键帧
android.animation.PropertyValuesHolder — 属性值持有者

属性动画的核心思想是:在指定的时间范围内,根据插值器(Interpolator)计算时间进度(fraction),再通过估值器(TypeEvaluator)计算当前时间点对应的属性值,最后通过 setter 方法将值设置到目标对象上。

ValueAnimator:属性动画的基石

ValueAnimator 是属性动画体系的核心。它本身不操作任何对象,只是单纯地在指定时间内生成一系列动画值。

基本用法

ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
animator.setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
// 使用 value 做自己想做的事,比如更新 UI
view.setTranslationX(value);
}
});
animator.start();

ofInt / ofFloat / ofObject

源码位置:frameworks/base/core/java/android/animation/ValueAnimator.java

public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}

public static ValueAnimator ofFloat(float... values) {
ValueAnimator anim = new ValueAnimator();
anim.setFloatValues(values);
return anim;
}

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
ValueAnimator anim = new ValueAnimator();
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}

ofIntofFloat 使用内置的 IntEvaluator 和 FloatEvaluator,ofObject 则需要自定义 TypeEvaluator。

参数 values 是可变长度参数,可以传入 2 个以上值来实现多阶段动画。例如 ValueAnimator.ofFloat(0f, 50f, 100f) 会生成一个从 0 到 50 再到 100 的动画序列。

setIntValues / setFloatValues 的内部实现

public void setFloatValues(float... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
mInitialized = false;
}

PropertyValuesHolder 是 ValueAnimator 的内部容器,每个 PropertyValuesHolder 管理一个属性的动画值序列。ValueAnimator 可以有多个 PropertyValuesHolder,这意味着一个动画可以同时驱动多个属性变化。

ObjectAnimator:自动设置属性值

ObjectAnimator 继承自 ValueAnimator,它在 ValueAnimator 的基础上增加了自动将动画值设置到目标对象属性的能力。

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f);
animator.setDuration(300);
animator.start();

依赖 getter/setter 的机制

ObjectAnimator 通过反射调用目标对象的 getter 和 setter 方法。这就要求目标对象必须具备对应属性的 get<PropertyName>()set<PropertyName>(Type value) 方法。

源码位置:frameworks/base/core/java/android/animation/ObjectAnimator.java

@Override
public void start() {
// ...
super.start();
}

@Override
void initAnimation() {
if (!mInitialized) {
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}

PropertyValuesHolder 中的 setupSetterAndGetter 会通过反射查找对应的方法:

void setupSetterAndGetter(Object target) {
// ...
Class targetClass = target.getClass();
// 查找 setter: set<PropertyName>(Type)
mSetter = setupSetter(targetClass);
// 查找 getter: get<PropertyName>() 或 is<PropertyName>()
for (Class klass : tempClasses) {
mGetter = setupGetter(klass);
if (mGetter != null) {
break;
}
}
}

常见命名规则:

  • 属性名 alpha → getter: getAlpha(), setter: setAlpha(float)
  • 属性名 translationX → getter: getTranslationX(), setter: setTranslationX(float)
  • 属性名 backgroundColor → getter: getBackgroundColor(), setter: setBackgroundColor(int) (需要自定义,View 默认没有 setter)

自定义属性动画:没有 getter/setter 怎么办?

方案一:使用 ValueAnimator + addUpdateListener 手动设置

ValueAnimator animator = ValueAnimator.ofInt(currentColor, targetColor);
animator.addUpdateListener(animation -> {
int color = (int) animation.getAnimatedValue();
myView.setCustomColor(color);
});
animator.start();

方案二:使用 View 的 setTag / getTag 配合 ObjectAnimator(不推荐)

方案三:使用 Property 包装类

ObjectAnimator animator = ObjectAnimator.ofInt(myView,
new IntProperty<MyView>("customColor") {
@Override
public void setValue(MyView object, int value) {
object.setCustomColor(value);
}

@Override
public Integer get(MyView object) {
return object.getCustomColor();
}
}, startColor, endColor);
animator.start();

AnimatorSet:编排多个动画

AnimatorSet 用于将多个动画组合在一起,支持同时播放、顺序播放和延迟播放。

基本用法

ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
ObjectAnimator moveUp = ObjectAnimator.ofFloat(view, "translationY", 0f, -200f);
ObjectAnimator scale = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.5f);

AnimatorSet set = new AnimatorSet();
// 同时播放 fadeOut 和 moveUp,然后播放 scale
set.play(scale).after(fadeOut).after(moveUp);
// 或者简化为
set.play(fadeOut).with(moveUp);
set.play(scale).after(fadeOut);
set.start();

核心方法

playTogether 和 playSequentially

// 同时播放
AnimatorSet set = new AnimatorSet();
set.playTogether(anim1, anim2, anim3);
set.start();

// 顺序播放
AnimatorSet set = new AnimatorSet();
set.playSequentially(anim1, anim2, anim3);
set.start();

这些静态便利方法内部创建 AnimatorSet 并调用相应的 Builder 方法。

Builder 模式:before / after / with

源码位置:frameworks/base/core/java/android/animation/AnimatorSet.java

public Builder play(Animator anim) {
if (anim != null) {
return new Builder(anim);
}
return null;
}

public class Builder {
private Node mCurrentNode;

Builder(Animator anim) {
mCurrentNode = getNodeForAnimation(anim);
}

public Builder with(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addSibling(node);
return this;
}

public Builder before(Animator anim) {
Node node = getNodeForAnimation(anim);
node.addChild(mCurrentNode);
return this;
}

public Builder after(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addChild(node);
return this;
}

public Builder after(long delay) {
ValueAnimator delayAnim = ValueAnimator.ofFloat(0f, 1f);
delayAnim.setDuration(delay);
after(delayAnim);
return this;
}
}

内部通过 Node 节点构建一个有向无环图(DAG),每个 Node 代表一个动画,节点之间的边表示播放顺序(先播放 → 后播放)或并行关系(同时播放)。

AnimatorSet 内部的动画依赖图

AnimatorSet 内部使用 AnimatorSet.Builder 构建动画依赖关系:

// 示例:set.play(anim1).before(anim2); set.play(anim1).with(anim3);
// 依赖关系:
// anim1 ──before──> anim2
// anim1 ──with───> anim3
//
// 播放顺序:anim1 和 anim3 同时开始,anim1 结束后 anim2 开始

Node 内部维护着 parents(前驱节点列表)和 siblings(兄弟节点列表)。当一个 Node 的所有 parents 都播放完成后,该 Node 才开始播放。siblings 会同时开始。

插值器(Interpolator / TimeInterpolator)

插值器决定动画的时间曲线,即时间进度(elapsed fraction)到动画进度(animated fraction)的映射。

TimeInterpolator 接口

public interface TimeInterpolator {
float getInterpolation(float input);
}

input 参数范围是 [0, 1],表示时间进度。返回值也是 [0, 1](大多数情况),表示动画进度。LinearInterpolator 的输入输出完全一致,AccelerateDecelerateInterpolator 则呈现先慢后快再慢的曲线。

常用插值器深度解析

AccelerateDecelerateInterpolator(加速减速插值器)

源码位置:frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java

public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

曲线形状:余弦函数。在 input=0.5 处对称,是一条 S 形曲线。动画开始时缓慢加速,中间段速度最快,结束时缓慢减速。这是 Android 中很多默认动画使用的插值器。

OvershootInterpolator(超出插值器)

源码位置:frameworks/base/core/java/android/view/animation/OvershootInterpolator.java

public OvershootInterpolator(float tension) {
mTension = tension;
}

public float getInterpolation(float t) {
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}

mTension 是张力参数,默认值为 2.0f。值越大,超出目标后回弹的幅度越大。动画值会先超过目标值,然后慢慢回到目标值,产生”弹性”效果。

BounceInterpolator(弹跳插值器)

源码位置:frameworks/base/core/java/android/view/animation/BounceInterpolator.java

private static float bounce(float t) {
return t * t * 8.0f;
}

public float getInterpolation(float t) {
t *= 1.1226f;
if (t < 0.3535f) return bounce(t);
else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}

模拟物理弹跳效果。物体从高处落下,在地面弹跳多次后逐渐停止。使用分段函数模拟每次弹跳,弹跳幅度逐渐减小。

PathInterpolator(路径插值器,API 21+)

使用三次贝塞尔曲线定义动画节奏。参数 controlX1, controlY1, controlX2, controlY2 定义控制点:

PathInterpolator interpolator = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
// 等效于 Material Design 的 "Fast Out Slow In"

源码位置:frameworks/base/core/java/android/view/animation/PathInterpolator.java

public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
initCubic(controlX1, controlY1, controlX2, controlY2);
}

private void initCubic(float x1, float y1, float x2, float y2) {
Path path = new Path();
path.moveTo(0, 0);
path.cubicTo(x1, y1, x2, y2, 1, 1);
initPath(path);
}

使用 Path 对象来灵活定义曲线,包括弧线等复杂路径。

AnticipateOvershootInterpolator(预期超出插值器)

组合了 AnticipateInterpolator(先后退再前进)和 OvershootInterpolator(超出后回弹):

public AnticipateOvershootInterpolator(float tension) {
mTension = tension * 1.5f;
}

public float getInterpolation(float t) {
if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
}

效果:动画开始前先向反方向移动一小段,然后加速前进,超出目标值后再回弹到目标位置。

自定义插值器

实现 TimeInterpolator 接口即可:

public class MyEaseOutInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
// 二次缓出:f(t) = 1 - (1 - t)^2
return 1.0f - (1.0f - input) * (1.0f - input);
}
}

估值器(TypeEvaluator)

估值器负责根据动画进度(fraction)计算当前的属性值。它与插值器的区别在于:

  • TimeInterpolator:计算”时间进度 → 动画进度”的映射。输入是时间百分比,输出是动画百分比。
  • TypeEvaluator:计算”动画进度 → 具体属性值”的映射。输入是动画百分比(0~1),输出是具体的属性值。

内置估值器

IntEvaluator

public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}

FloatEvaluator

public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}

ArgbEvaluator(颜色估值器)

public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;

int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;

// 线性插值
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);

return Math.round(a * 255) << 24
| Math.round(r * 255) << 16
| Math.round(g * 255) << 8
| Math.round(b * 255);
}
}

自定义估值器

以 PointFEvaluator 为例(对 PointF 做动画):

public class PointFEvaluator implements TypeEvaluator<PointF> {
private PointF mPoint = new PointF();

@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float x = startValue.x + fraction * (endValue.x - startValue.x);
float y = startValue.y + fraction * (endValue.y - startValue.y);
mPoint.set(x, y);
return mPoint;
}
}

// 使用
ValueAnimator animator = ValueAnimator.ofObject(
new PointFEvaluator(),
new PointF(0, 0),
new PointF(100, 200)
);
animator.addUpdateListener(animation -> {
PointF point = (PointF) animation.getAnimatedValue();
view.setX(point.x);
view.setY(point.y);
});
animator.start();

ViewPropertyAnimator:为 View 优化的动画接口

ViewPropertyAnimator 是 View 动画的便捷 API,通过 view.animate() 获取。它是专门为 View 的常用属性(translationX/Y/Z、scaleX/Y、rotation/rotationX/Y、alpha、x、y、z)设计的。

基本用法

view.animate()
.x(500f)
.y(300f)
.scaleX(1.5f)
.scaleY(1.5f)
.alpha(0.5f)
.rotation(90f)
.setDuration(300)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setStartDelay(100)
.setListener(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) { }
})
.start();

内部实现

源码位置:frameworks/base/core/java/android/view/ViewPropertyAnimator.java

public ViewPropertyAnimator x(float value) {
return xBy(value - mView.mLeft);
}

public ViewPropertyAnimator xBy(float value) {
animateProperty(TRANSLATION_X, value);
return this;
}

private void animateProperty(int constantName, float toValue) {
float fromValue = getValue(constantName);
NameValuesHolder holder = new NameValuesHolder(constantName, fromValue, toValue);
mPendingAnimations.add(holder);
mView.removeCallbacks(mAnimationStarter);
mView.postOnAnimation(mAnimationStarter);
}

mAnimationStarter 是一个 Runnable,它会在下一帧动画回调时执行:

private Runnable mAnimationStarter = new Runnable() {
@Override
public void run() {
startAnimation();
}
};

private void startAnimation() {
// 构建 PropertyValuesHolder 数组
// 创建一个 ValueAnimator 来驱动所有属性变化
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
// ...设置 PropertyValuesHolder 和 update listener
mAnimator.start();
}

ViewPropertyAnimator 的优势

  1. API 简洁:链式调用,一行代码搞定动画。
  2. 自动并发:多个属性的动画自动同时进行,无需 AnimatorSet。
  3. 性能更好:内部自动优化,多个动画共享一个 ValueAnimator 和一次 VSYNC 回调。
  4. 自动处理起始值:不需要手动指定 fromValue,系统自动读取当前属性值。
  5. 资源管理更高效:视图卸载时自动取消动画。

动画驱动的底层原理

Choreographer 驱动的帧循环

属性动画的帧驱动最终依赖 Choreographer 的 VSYNC 信号。

源码位置:frameworks/base/core/java/android/animation/AnimationHandler.java

public class AnimationHandler {
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this); // 注册下一帧回调
}
}
};

void start() {
if (!mAnimationScheduled) {
mAnimationScheduled = true;
getProvider().postFrameCallback(mFrameCallback);
}
}
}

完整调用链:

VSYNC 信号
→ Choreographer.doFrame(frameTimeNanos)
→ Choreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos)
→ AnimationHandler.doAnimationFrame(frameTime)
→ ValueAnimator.doAnimationFrame(frameTime)
→ ValueAnimator.animateBasedOnTime(currentTime)
→ 调用 Interpolator.getInterpolation(fraction)
→ 调用 TypeEvaluator.evaluate(animatedFraction, ...)
→ 通过 setter 设置属性值 / 回调 AnimatorUpdateListener

doAnimationFrame 的详细流程

boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
mStartTime = mReversing ? frameTime : frameTime + (long)(mStartDelay * sDurationScale);
}

if (mPaused) {
mPauseTime = frameTime;
return false;
} else if (mResumed) {
mStartTime += (frameTime - mPauseTime);
mResumed = false;
}

if (!mRunning) {
return false;
}

final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);

if (finished) {
endAnimation();
}
return finished;
}

animateBasedOnTime 是核心方法,计算 elapsed fraction,应用插值器,然后通过 PropertyValuesHolder 设置属性值:

boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;

final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) && mRepeatCount != INFINITE;

if (scaledDuration == 0) {
done = true;
} else if (fraction >= 1f && mCurrentIteration < mRepeatCount) {
// 达到迭代边界
if (mRepeatMode == REVERSE) { ... }
else { ... }
}

mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mRepeatCount != INFINITE);
animateValue(currentIterationFraction);
}
return done;
}

void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction); // 应用插值器
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction); // 估值器计算具体值
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this); // 回调监听器
}
}
}

SpringAnimation:物理弹簧动画(androidx)

从 Support Library 25.3.0 开始,Android 引入了基于物理的弹簧动画。

基本原理

SpringAnimation 不基于时间和插值器,而是基于真实的物理模拟:在每一帧中,根据弹簧的阻尼系数(damping ratio)和刚度(stiffness)计算当前位置的加速度,更新速度和位置,当能量耗尽时动画自然停止。

使用方式

// 依赖:implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0'

SpringAnimation springAnim = new SpringAnimation(view,
DynamicAnimation.TRANSLATION_Y, 0f);

springAnim.getSpring().setStiffness(SpringForce.STIFFNESS_MEDIUM);
springAnim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY);

springAnim.setStartVelocity(2000f); // 初始速度
springAnim.start();

参数说明:

  • Stiffness(刚度):弹簧的硬度。值越大,回到平衡位置的速度越快。

    • STIFFNESS_HIGH = 10,000
    • STIFFNESS_MEDIUM = 1,500
    • STIFFNESS_LOW = 200
    • STIFFNESS_VERY_LOW = 50
  • Damping Ratio(阻尼比):控制弹簧的振荡程度。

    • DAMPING_RATIO_HIGH_BOUNCY = 0.2(明显弹跳)
    • DAMPING_RATIO_MEDIUM_BOUNCY = 0.5(中等弹跳)
    • DAMPING_RATIO_LOW_BOUNCY = 0.75(轻微弹跳)
    • DAMPING_RATIO_NO_BOUNCY = 1.0(无弹跳,刚好回到平衡位置)

SpringAnimation 的优势是动画过程在物理上自然逼真,并且可以根据手势速度动态调整(例如松手时的初始速度)。

XML 中定义属性动画

属性动画也可以在 XML 中定义,放在 res/animator/ 目录下。

animator 标签(对应 ValueAnimator)

<!-- res/animator/value_animator.xml -->
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:startOffset="100"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="100"
android:valueType="floatType"
android:interpolator="@android:interpolator/accelerate_decelerate" />

objectAnimator 标签(对应 ObjectAnimator)

<!-- res/animator/object_animator.xml -->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:propertyName="translationX"
android:valueFrom="0"
android:valueTo="100"
android:valueType="floatType" />

set 标签(对应 AnimatorSet)

<!-- res/animator/animator_set.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:duration="300"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0" />
<objectAnimator
android:duration="500"
android:propertyName="translationY"
android:valueFrom="0"
android:valueTo="-200" />
</set>

在代码中加载

// 加载 ValueAnimator
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, R.animator.value_animator);
animator.addUpdateListener(animation -> { ... });
animator.start();

// 加载 ObjectAnimator
ObjectAnimator objAnim = (ObjectAnimator) AnimatorInflater.loadAnimator(context, R.animator.object_animator);
objAnim.setTarget(view);
objAnim.start();

// 加载 AnimatorSet
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.animator_set);
set.setTarget(view);
set.start();

常见陷阱与最佳实践

内存泄漏:记得取消动画

属性动画持有对目标对象的引用,如果动画无限循环且未在 Activity/Fragment 销毁时取消,会导致目标对象(通常是 View 或 Activity)无法被 GC 回收。

@Override
protected void onDestroy() {
super.onDestroy();
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
// 或者用 AnimatorSet 统一管理
if (mAnimatorSet != null) {
mAnimatorSet.cancel();
}
}

硬件层优化

对于大范围或复杂的动画(尤其是 alpha 和 scale 动画),可以在动画期间启用硬件层:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
view.animate()
.alpha(0f)
.setDuration(300)
.withEndAction(() -> {
view.setLayerType(View.LAYER_TYPE_NONE, null);
})
.start();

原理:LAYER_TYPE_HARDWARE 会将 View 渲染到一个 GPU 纹理中,动画期间只需对这个纹理做矩阵变换,避免了每帧重新执行绘制命令。动画结束后移除层以释放显存。

不要在动画回调中做耗时操作

onAnimationUpdate 在每个动画帧都会调用(约 16ms 一次),在其中执行耗时操作会导致掉帧:

// 错误做法
animator.addUpdateListener(animation -> {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.huge_image);
view.setImageBitmap(bitmap);
});

// 正确做法:预先准备资源,回调中只做简单赋值
Bitmap preloadedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.huge_image);
animator.addUpdateListener(animation -> {
view.setImageAlpha((int) ((float) animation.getAnimatedValue() * 255));
});

View 属性动画的”点击问题”

补间动画(View Animation)只改变视觉效果,不改变 View 的实际位置。属性动画直接修改属性值,所以实际位置(包括点击区域)会跟随动画变化。但要注意:如果使用 ObjectAnimator 动画的是非位置属性(如 scaleX、scaleY),View 的触摸区域可能不在预期位置。

正确使用 AnimatorListener

如果只需要动画结束回调,可以使用 AnimatorListenerAdapter 避免实现所有方法:

animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 只需要处理动画结束
}
});

关键源码文件汇总

文件 路径
ValueAnimator.java frameworks/base/core/java/android/animation/ValueAnimator.java
ObjectAnimator.java frameworks/base/core/java/android/animation/ObjectAnimator.java
AnimatorSet.java frameworks/base/core/java/android/animation/AnimatorSet.java
PropertyValuesHolder.java frameworks/base/core/java/android/animation/PropertyValuesHolder.java
AnimationHandler.java frameworks/base/core/java/android/animation/AnimationHandler.java
ViewPropertyAnimator.java frameworks/base/core/java/android/view/ViewPropertyAnimator.java
AccelerateDecelerateInterpolator.java frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
OvershootInterpolator.java frameworks/base/core/java/android/view/animation/OvershootInterpolator.java
BounceInterpolator.java frameworks/base/core/java/android/view/animation/BounceInterpolator.java
PathInterpolator.java frameworks/base/core/java/android/view/animation/PathInterpolator.java
ArgbEvaluator.java frameworks/base/core/java/android/animation/ArgbEvaluator.java

总结

属性动画的核心框架由三层构成:

  1. 时间层(TimeInterpolator):控制动画节奏,决定时间如何映射到动画进度。
  2. 计算层(TypeEvaluator):根据动画进度计算具体的属性值。
  3. 驱动层(AnimationHandler / Choreographer):通过 VSYNC 信号驱动每一帧的计算和更新。

理解这三层的协作关系,就能灵活运用属性动画实现各种复杂的动画效果。对于日常开发,ViewPropertyAnimator 是最常用的 API,简洁高效;对于需要精确控制的场景,ValueAnimator 和 ObjectAnimator 是更灵活的选择;对于追求自然物理效果的交互,SpringAnimation 是最佳选择。无论使用哪种 API,都要注意在适当的时候取消动画以避免内存泄漏。

打赏
  • 微信
  • 支付宝

评论