属性动画 执行流程

165 阅读3分钟

Android 动画系统主要分为三大类

1. 视图动画(View Animation)

作用对象View
特点:仅改变 View 的绘制效果,不改变其真实属性(如位置、大小)。
子分类

类型描述示例
补间动画(Tween Animation)通过插值器计算中间帧,实现平移、旋转、缩放、透明度变化。AlphaAnimationTranslateAnimationScaleAnimationRotateAnimation
帧动画(Frame Animation)逐帧播放图片序列(类似GIF)。AnimationDrawable 加载多张图片。

代码示例(补间动画)

xml

复制

<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />

运行 HTML

java

复制

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

缺点

  • 点击事件仍在原始位置(未随动画移动)。
  • 只能作用于 View,无法应用于非 View 对象。

2. 属性动画(Property Animation)

作用对象:任意对象的属性(包括 View 和非 View)。
特点:通过动态修改对象的属性值实现动画(真实改变属性值)。
核心类

类/接口作用
ValueAnimator计算属性值的变化过程,需手动更新目标对象。
ObjectAnimator自动更新目标对象的属性(需有对应的 setter/getter)。
AnimatorSet组合多个动画(顺序/并行播放)。
TimeInterpolator控制动画变化速率(如加速、减速)。

代码示例

java

复制

// 透明度动画(0 → 1)
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
alphaAnim.setDuration(1000);
alphaAnim.start();

// 组合动画:平移 + 旋转
AnimatorSet set = new AnimatorSet();
set.playTogether(
    ObjectAnimator.ofFloat(view, "translationX", 0f, 100f),
    ObjectAnimator.ofFloat(view, "rotation", 0f, 360f)
);
set.start();

优点

  • 真实改变属性值,点击事件跟随动画。
  • 可作用于任意对象的任意属性。

缺点

  • 需要目标属性有 setter 方法(如 setAlpha())。

3. 过渡动画(Transition Animation)

作用场景:界面切换(Activity/Fragment/ViewGroup)时的动画效果。
子分类

类型描述
Activity 过渡动画通过 overridePendingTransition() 或 ActivityOptions 实现。
Fragment 过渡动画使用 setCustomAnimations() 设置进入/退出动画。
共享元素动画两个界面共享的 View 平滑过渡(如图片放大)。
场景动画(Scene)在 ViewGroup 中切换布局时的动画(如 TransitionManager.go())。

代码示例(Activity 过渡)

java

复制

// 启动 Activity 时设置动画
Intent intent = new Intent(this, DetailActivity.class);
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());

// 在 styles.xml 中定义过渡效果
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
    <item name="android:windowActivityTransitions">true</item>
    <item name="android:windowEnterTransition">@transition/slide_in</item>
    <item name="android:windowExitTransition">@transition/slide_out</item>
</style>

属性动画/视图动画区别

属性动画

核心机制

  • 修改真实属性值:直接调用对象的 setter 方法(如 setTranslationX()setAlpha()),永久改变属性
  • 绘制阶段生效:通过 View 的 draw() 方法实时更新属性值,触发重绘。
  • 点击事件跟随动画:因为属性值真实变化,触摸事件坐标会同步更新。

视图动画

核心机制

  • 矩阵变换(Matrix) :通过 Canvas 的变换(如平移、旋转)临时改变绘制效果,不修改真实属性
  • 布局阶段生效:在 View 的 draw() 方法中应用变换矩阵,但 View 的布局参数(如 lefttop)不变。
  • 点击事件不跟随:触摸事件仍作用在原始位置(因为属性未变)。

属性动画 源码分析

ObjectAnimator.ofFloat(view, "translationX", 0f, 100f).apply {
    duration = 1000
    start()
}

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    //创建 ObjectAnimator
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setFloatValues(values);
    return anim;
}

设置目标view,属性

private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    setPropertyName(propertyName);
}
public void setFloatValues(float... values) {
    
    if (mValues == null || mValues.length == 0) {
        //创建 PropertyValuesHolder
        setValues(PropertyValuesHolder.ofFloat("", values));
    } else {
        PropertyValuesHolder valuesHolder = mValues[0];
        valuesHolder.setFloatValues(values);
    }
    mInitialized = false;
}

FloatPropertyValuesHolder

public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    setFloatValues(values);
}

KeyframeSet

public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}

start() 开启动画

addAnimationCallback(0);

startAnimation();
initAnimation();

addAnimationCallback 设置sync回调

addAnimationCallback(0);

getProvider().postFrameCallback(mFrameCallback);

MyFrameCallbackProvider

private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

    final Choreographer mChoreographer = Choreographer.getInstance();

    @Override
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        mChoreographer.postFrameCallback(callback);
    }

    @Override
    public void postCommitCallback(Runnable runnable) {
        mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
    }

mFrameCallback

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};

initAnimation()

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

Class targetClass = target.getClass();
if (mSetter == null) {
    //反射获取 method 
    setupSetter(targetClass);
}

callback.doAnimationFrame(frameTime);

当 同步信号来到,执行 FrameCallback,

animateValue(currentIterationFraction);

void animateValue(float fraction) {
    final Object target = getTarget();
    super.animateValue(fraction);
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);
    }
}

FloatPropertyValuesHolder

void calculateValue(float fraction) {
    //通过插值器mInterpolator拿到当前动画的完成度,
    mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}

FloatPropertyValuesHolder.setAnimatedValue

mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);

流程图

image.png