Android 动画系统主要分为三大类
1. 视图动画(View Animation)
作用对象 :View
特点 :仅改变 View
的绘制效果,不改变其真实属性(如位置、大小)。
子分类:
类型 | 描述 | 示例 |
---|---|---|
补间动画(Tween Animation) | 通过插值器计算中间帧,实现平移、旋转、缩放、透明度变化。 | AlphaAnimation 、TranslateAnimation 、ScaleAnimation 、RotateAnimation |
帧动画(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()
),永久改变属性。 - 绘制阶段生效 :通过
View
的draw()
方法实时更新属性值,触发重绘。 - 点击事件跟随动画:因为属性值真实变化,触摸事件坐标会同步更新。
视图动画
核心机制
- 矩阵变换(Matrix) :通过
Canvas
的变换(如平移、旋转)临时改变绘制效果,不修改真实属性。 - 布局阶段生效 :在
View
的draw()
方法中应用变换矩阵,但 View 的布局参数(如left
、top
)不变。 - 点击事件不跟随:触摸事件仍作用在原始位置(因为属性未变)。
属性动画 源码分析
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);