属性动画 执行流程

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

1. 视图动画(View Animation)

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

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

代码示例(补间动画)

xml

复制

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

复制

ini 复制代码
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

复制

ini 复制代码
// 透明度动画(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

复制

javascript 复制代码
// 启动 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()),永久改变属性
  • 绘制阶段生效 :通过 Viewdraw() 方法实时更新属性值,触发重绘。
  • 点击事件跟随动画:因为属性值真实变化,触摸事件坐标会同步更新。

视图动画

核心机制

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

属性动画 源码分析

scss 复制代码
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,属性

scss 复制代码
private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    setPropertyName(propertyName);
}
ini 复制代码
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

scss 复制代码
public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    setFloatValues(values);
}

KeyframeSet

ini 复制代码
public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}

start() 开启动画

scss 复制代码
addAnimationCallback(0);

startAnimation();
initAnimation();

addAnimationCallback 设置sync回调

scss 复制代码
addAnimationCallback(0);

getProvider().postFrameCallback(mFrameCallback);

MyFrameCallbackProvider

java 复制代码
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

java 复制代码
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()

ini 复制代码
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

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

callback.doAnimationFrame(frameTime);

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

animateValue(currentIterationFraction);

ini 复制代码
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

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

FloatPropertyValuesHolder.setAnimatedValue

ini 复制代码
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);

流程图

相关推荐
阿巴斯甜13 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker13 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952714 小时前
Andorid Google 登录接入文档
android
黄林晴16 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android