Android 动画机制完整详解

70 阅读27分钟

Android 动画机制完整详解

本文档全面详解Android动画机制,包含补间动画、帧动画、属性动画、插值器、性能优化等所有内容,覆盖所有相关面试题。

Android动画概述

什么是Android动画?

Android动画是让View或属性在一段时间内平滑变化的效果,用于提升用户体验,使界面更加生动和流畅。

Android动画的分类

Android提供了三种动画类型:

  1. 补间动画(Tween Animation):也叫View动画,只能改变View的显示效果,不能改变View的属性
  2. 帧动画(Frame Animation):逐帧播放图片序列,形成动画效果
  3. 属性动画(Property Animation):可以改变对象的任意属性,功能最强大

三种动画的对比

动画类型作用对象改变内容适用场景性能
补间动画View显示效果(位置、大小、透明度、旋转)简单的View动画较好
帧动画View图片序列加载动画、图标动画一般
属性动画任意对象任意属性复杂动画、自定义动画最好

动画的设计思想

  • 平滑过渡:通过插值器实现平滑的动画效果
  • 性能优化:使用硬件加速提高动画性能
  • 灵活控制:支持动画的组合、循环、取消等操作

补间动画(Tween Animation)

什么是补间动画?

补间动画是Android最早的动画类型,通过指定动画的开始和结束状态,系统自动计算中间帧,实现平滑的动画效果。

补间动画的特点

  1. 只能改变View的显示效果:不能改变View的实际属性(如位置、大小)
  2. 动画结束后View会恢复原状:动画只是视觉效果
  3. 性能较好:使用硬件加速
  4. 功能有限:只能实现平移、缩放、旋转、透明度变化

补间动画的类型

1. 平移动画(TranslateAnimation)

作用: 让View在X轴或Y轴上移动

XML实现:

<translate
    android:duration="1000"
    android:fromXDelta="0"
    android:toXDelta="200"
    android:fromYDelta="0"
    android:toYDelta="200" />

代码实现:

TranslateAnimation animation = new TranslateAnimation(
    0, 200,  // fromXDelta, toXDelta
    0, 200   // fromYDelta, toYDelta
);
animation.setDuration(1000);
view.startAnimation(animation);
2. 缩放动画(ScaleAnimation)

作用: 让View放大或缩小

XML实现:

<scale
    android:duration="1000"
    android:fromXScale="1.0"
    android:toXScale="2.0"
    android:fromYScale="1.0"
    android:toYScale="2.0"
    android:pivotX="50%"
    android:pivotY="50%" />

代码实现:

ScaleAnimation animation = new ScaleAnimation(
    1.0f, 2.0f,  // fromXScale, toXScale
    1.0f, 2.0f,  // fromYScale, toYScale
    Animation.RELATIVE_TO_SELF, 0.5f,  // pivotX
    Animation.RELATIVE_TO_SELF, 0.5f   // pivotY
);
animation.setDuration(1000);
view.startAnimation(animation);
3. 旋转动画(RotateAnimation)

作用: 让View旋转

XML实现:

<rotate
    android:duration="1000"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%" />

代码实现:

RotateAnimation animation = new RotateAnimation(
    0, 360,  // fromDegrees, toDegrees
    Animation.RELATIVE_TO_SELF, 0.5f,  // pivotX
    Animation.RELATIVE_TO_SELF, 0.5f   // pivotY
);
animation.setDuration(1000);
view.startAnimation(animation);
4. 透明度动画(AlphaAnimation)

作用: 改变View的透明度

XML实现:

<alpha
    android:duration="1000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0" />

代码实现:

AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f);
animation.setDuration(1000);
view.startAnimation(animation);

补间动画的组合

可以使用AnimationSet组合多个动画:

AnimationSet animationSet = new AnimationSet(true);

TranslateAnimation translate = new TranslateAnimation(0, 200, 0, 200);
ScaleAnimation scale = new ScaleAnimation(1.0f, 2.0f, 1.0f, 2.0f, 
    Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);

animationSet.addAnimation(translate);
animationSet.addAnimation(scale);
animationSet.setDuration(1000);
animationSet.setInterpolator(new AccelerateDecelerateInterpolator());

view.startAnimation(animationSet);

补间动画的XML配置

res/anim/anim_translate.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:fillAfter="true">
    
    <translate
        android:fromXDelta="0"
        android:toXDelta="200"
        android:fromYDelta="0"
        android:toYDelta="200" />
</set>

使用:

Animation animation = AnimationUtils.loadAnimation(context, R.anim.anim_translate);
view.startAnimation(animation);

补间动画的常用属性

属性说明取值
android:duration动画持续时间毫秒数
android:fillAfter动画结束后是否保持结束状态true/false
android:fillBefore动画开始前是否保持开始状态true/false
android:repeatCount重复次数数字或infinite
android:repeatMode重复模式restart/reverse
android:interpolator插值器插值器资源

补间动画的局限性

  1. 不能改变View的实际属性:动画只是视觉效果
  2. 不能改变非View对象:只能作用于View
  3. 功能有限:只能实现平移、缩放、旋转、透明度变化
  4. 动画结束后恢复原状:fillAfter只是视觉效果

帧动画(Frame Animation)

什么是帧动画?

帧动画是通过逐帧播放图片序列,形成动画效果,类似于GIF动画。

帧动画的特点

  1. 逐帧播放:按顺序播放每一帧图片
  2. 资源占用较大:需要多张图片
  3. 适合简单动画:加载动画、图标动画
  4. 性能一般:需要加载多张图片

帧动画的实现

XML实现

res/drawable/anim_frame.xml:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    
    <item
        android:drawable="@drawable/frame1"
        android:duration="100" />
    <item
        android:drawable="@drawable/frame2"
        android:duration="100" />
    <item
        android:drawable="@drawable/frame3"
        android:duration="100" />
    <!-- 更多帧 -->
</animation-list>

使用:

ImageView imageView = findViewById(R.id.imageView);
imageView.setBackgroundResource(R.drawable.anim_frame);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();
代码实现
AnimationDrawable animationDrawable = new AnimationDrawable();
animationDrawable.addFrame(getResources().getDrawable(R.drawable.frame1), 100);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.frame2), 100);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.frame3), 100);
animationDrawable.setOneShot(false);  // 是否只播放一次

ImageView imageView = findViewById(R.id.imageView);
imageView.setBackground(animationDrawable);
animationDrawable.start();

帧动画的属性

属性说明取值
android:oneshot是否只播放一次true/false
android:duration每帧的持续时间毫秒数

帧动画的注意事项

  1. 资源优化:使用合适的图片格式和大小
  2. 内存管理:及时停止动画,释放资源
  3. 性能优化:避免使用过多帧或过大的图片

属性动画(Property Animation)

什么是属性动画?

属性动画是Android 3.0引入的动画系统,可以改变对象的任意属性,功能最强大。

属性动画的特点

  1. 可以改变任意属性:不仅限于View,可以改变任何对象的属性
  2. 改变实际属性值:动画会真正改变对象的属性值
  3. 功能强大:支持复杂的动画效果
  4. 性能最好:使用硬件加速

属性动画的核心类

1. ValueAnimator

作用: 在指定时间内平滑改变某个值

基本使用:

ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        // 使用value更新View
        view.setAlpha(value);
    }
});
animator.start();

ofInt()示例:

ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setDuration(1000);
animator.addUpdateListener(animation -> {
    int value = (int) animation.getAnimatedValue();
    view.setTranslationX(value);
});
animator.start();

ofObject()示例:

ValueAnimator animator = ValueAnimator.ofObject(
    new ArgbEvaluator(),
    Color.RED,
    Color.BLUE
);
animator.setDuration(1000);
animator.addUpdateListener(animation -> {
    int color = (int) animation.getAnimatedValue();
    view.setBackgroundColor(color);
});
animator.start();
2. ObjectAnimator

作用: 直接改变对象的属性值

基本使用:

ObjectAnimator animator = ObjectAnimator.ofFloat(
    view,
    "alpha",  // 属性名
    1.0f,     // 起始值
    0.0f      // 结束值
);
animator.setDuration(1000);
animator.start();

常用属性:

// 透明度
ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);

// 平移
ObjectAnimator.ofFloat(view, "translationX", 0f, 200f);
ObjectAnimator.ofFloat(view, "translationY", 0f, 200f);

// 缩放
ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 2.0f);
ObjectAnimator.ofFloat(view, "scaleY", 1.0f, 2.0f);

// 旋转
ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator.ofFloat(view, "rotationX", 0f, 360f);
ObjectAnimator.ofFloat(view, "rotationY", 0f, 360f);

自定义属性:

// 自定义View需要提供getter和setter方法
public class CustomView extends View {
    private float progress;
    
    public float getProgress() {
        return progress;
    }
    
    public void setProgress(float progress) {
        this.progress = progress;
        invalidate();  // 重绘
    }
}

// 使用
ObjectAnimator animator = ObjectAnimator.ofFloat(
    customView,
    "progress",
    0f,
    100f
);
animator.start();
3. AnimatorSet

作用: 组合多个动画

顺序执行:

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator1).before(animator2);  // animator1在animator2之前
animatorSet.play(animator2).before(animator3);  // animator2在animator3之前
animatorSet.start();

同时执行:

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.start();

复杂组合:

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator1).with(animator2);  // animator1和animator2同时
animatorSet.play(animator3).after(animator1);  // animator3在animator1之后
animatorSet.start();

属性动画的XML配置

res/animator/anim_alpha.xml:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="alpha"
    android:valueFrom="1.0"
    android:valueTo="0.0"
    android:valueType="floatType" />

使用:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_alpha);
animator.setTarget(view);
animator.start();

ViewPropertyAnimator

作用: 简化View的属性动画操作

使用:

view.animate()
    .alpha(0.0f)
    .translationX(200f)
    .translationY(200f)
    .scaleX(2.0f)
    .scaleY(2.0f)
    .rotation(360f)
    .setDuration(1000)
    .setInterpolator(new AccelerateDecelerateInterpolator())
    .start();

链式调用:

view.animate()
    .alpha(0.0f)
    .setDuration(500)
    .withEndAction(() -> {
        // 动画结束后的操作
        view.setVisibility(View.GONE);
    });

TypeEvaluator(类型估值器)

什么是TypeEvaluator?

TypeEvaluator用于计算动画的中间值,系统提供了ArgbEvaluator(颜色估值器)和FloatEvaluator(浮点数估值器)。

系统内置TypeEvaluator

1. ArgbEvaluator(颜色估值器)

ValueAnimator animator = ValueAnimator.ofObject(
    new ArgbEvaluator(),
    Color.RED,
    Color.BLUE
);
animator.addUpdateListener(animation -> {
    int color = (int) animation.getAnimatedValue();
    view.setBackgroundColor(color);
});
animator.start();

2. FloatEvaluator(浮点数估值器)

// ObjectAnimator内部使用FloatEvaluator
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);
animator.start();
自定义TypeEvaluator
public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
        int y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
        return new Point(x, y);
    }
}

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

Keyframe(关键帧)

什么是Keyframe?

Keyframe用于定义动画的关键点,可以在关键点设置不同的值,实现更复杂的动画效果。

Keyframe的使用
// 创建关键帧
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);      // 0%时,值为0
Keyframe kf1 = Keyframe.ofFloat(0.5f, 200f); // 50%时,值为200
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);      // 100%时,值为0

// 使用PropertyValuesHolder
PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
animator.setDuration(1000);
animator.start();
Keyframe的插值器
// 为关键帧设置插值器
Keyframe kf1 = Keyframe.ofFloat(0.5f, 200f);
kf1.setInterpolator(new OvershootInterpolator());

TypeEvaluator(类型估值器)

什么是TypeEvaluator?

TypeEvaluator用于计算动画的中间值,系统提供了ArgbEvaluator(颜色估值器)和FloatEvaluator(浮点数估值器)。

系统内置TypeEvaluator

1. ArgbEvaluator(颜色估值器)

ValueAnimator animator = ValueAnimator.ofObject(
    new ArgbEvaluator(),
    Color.RED,
    Color.BLUE
);
animator.addUpdateListener(animation -> {
    int color = (int) animation.getAnimatedValue();
    view.setBackgroundColor(color);
});
animator.start();

2. FloatEvaluator(浮点数估值器)

// ObjectAnimator内部使用FloatEvaluator
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);
animator.start();
自定义TypeEvaluator
public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
        int y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
        return new Point(x, y);
    }
}

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

Keyframe(关键帧)

什么是Keyframe?

Keyframe用于定义动画的关键点,可以在关键点设置不同的值,实现更复杂的动画效果。

Keyframe的使用
// 创建关键帧
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);      // 0%时,值为0
Keyframe kf1 = Keyframe.ofFloat(0.5f, 200f); // 50%时,值为200
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);      // 100%时,值为0

// 使用PropertyValuesHolder
PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
animator.setDuration(1000);
animator.start();
Keyframe的插值器
// 为关键帧设置插值器
Keyframe kf1 = Keyframe.ofFloat(0.5f, 200f);
kf1.setInterpolator(new OvershootInterpolator());

属性动画的常用方法

方法说明
setDuration(long duration)设置动画持续时间
setStartDelay(long delay)设置动画延迟时间
setRepeatCount(int count)设置重复次数
setRepeatMode(int mode)设置重复模式
setInterpolator(TimeInterpolator interpolator)设置插值器
setEvaluator(TypeEvaluator evaluator)设置类型估值器
start()开始动画
cancel()取消动画
pause()暂停动画
resume()恢复动画

动画插值器(Interpolator)

什么是插值器?

插值器定义了动画的变化速率,控制动画如何加速或减速。

插值器的作用

插值器将动画的进度(0.0到1.0)转换为实际的变化值,实现不同的动画效果。

系统内置插值器

1. LinearInterpolator(线性插值器)

特点: 匀速变化

animator.setInterpolator(new LinearInterpolator());
2. AccelerateInterpolator(加速插值器)

特点: 逐渐加速

animator.setInterpolator(new AccelerateInterpolator());
animator.setInterpolator(new AccelerateInterpolator(2.0f));  // 指定加速因子
3. DecelerateInterpolator(减速插值器)

特点: 逐渐减速

animator.setInterpolator(new DecelerateInterpolator());
animator.setInterpolator(new DecelerateInterpolator(2.0f));  // 指定减速因子
4. AccelerateDecelerateInterpolator(加速减速插值器)

特点: 先加速后减速

animator.setInterpolator(new AccelerateDecelerateInterpolator());
5. OvershootInterpolator(回弹插值器)

特点: 超过目标值后回弹

animator.setInterpolator(new OvershootInterpolator());
animator.setInterpolator(new OvershootInterpolator(2.0f));  // 指定回弹力度
6. AnticipateInterpolator(预期插值器)

特点: 先向后移动再向前

animator.setInterpolator(new AnticipateInterpolator());
animator.setInterpolator(new AnticipateInterpolator(2.0f));  // 指定预期力度
7. AnticipateOvershootInterpolator(预期回弹插值器)

特点: 先向后移动,超过目标值后回弹

animator.setInterpolator(new AnticipateOvershootInterpolator());
8. BounceInterpolator(弹跳插值器)

特点: 弹跳效果

animator.setInterpolator(new BounceInterpolator());
9. CycleInterpolator(循环插值器)

特点: 循环变化

animator.setInterpolator(new CycleInterpolator(2.0f));  // 指定循环次数

自定义插值器

public class CustomInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        // input: 0.0 到 1.0
        // 返回: 0.0 到 1.0
        // 可以根据input计算任意曲线
        return input * input;  // 二次函数,加速效果
    }
}

// 使用
animator.setInterpolator(new CustomInterpolator());

PathInterpolator

什么是PathInterpolator?

PathInterpolator是Android 5.0引入的插值器,可以通过Path定义自定义的插值曲线,实现更灵活的动画效果。

PathInterpolator的使用
// 使用Path定义插值曲线
Path path = new Path();
path.moveTo(0, 0);
path.quadTo(0.5f, 1.0f, 1.0f, 0.5f);
PathInterpolator interpolator = new PathInterpolator(path);
animator.setInterpolator(interpolator);

// 使用预设曲线
PathInterpolator interpolator = new PathInterpolator(0.4f, 0.0f, 0.2f, 1.0f);
// 参数:控制点1的x、y,控制点2的x、y
PathInterpolator的优势
  1. 更灵活:可以定义任意曲线
  2. 更精确:通过Path精确控制动画速度
  3. 更流畅:可以实现更自然的动画效果

插值器对比表

插值器效果适用场景
LinearInterpolator匀速简单动画
AccelerateInterpolator加速进入动画
DecelerateInterpolator减速退出动画
AccelerateDecelerateInterpolator先加速后减速通用动画
OvershootInterpolator回弹强调动画
AnticipateInterpolator预期准备动画
AnticipateOvershootInterpolator预期回弹复杂动画
BounceInterpolator弹跳趣味动画
CycleInterpolator循环循环动画

动画监听器

AnimatorListener

作用: 监听动画的生命周期

animator.addListener(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) {
        // 动画重复
    }
});

AnimatorUpdateListener

作用: 监听动画的每一帧更新

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        // 更新View
        view.setAlpha(value);
    }
});

使用Lambda简化

animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        // 只需要实现需要的方法
    }
});

动画组合与高级用法

动画组合

顺序执行
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(animator1, animator2, animator3);
animatorSet.start();
同时执行
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.start();
复杂组合
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator1).with(animator2);  // animator1和animator2同时
animatorSet.play(animator3).after(animator1);  // animator3在animator1之后
animatorSet.play(animator4).before(animator3);  // animator4在animator3之前
animatorSet.start();

动画循环

animator.setRepeatCount(ValueAnimator.INFINITE);  // 无限循环
animator.setRepeatCount(3);  // 循环3次
animator.setRepeatMode(ValueAnimator.RESTART);  // 重新开始
animator.setRepeatMode(ValueAnimator.REVERSE);  // 反向播放

动画取消

animator.cancel();  // 取消动画
animator.end();  // 立即结束动画到最终状态

动画暂停和恢复

animator.pause();  // 暂停动画
animator.resume();  // 恢复动画

动画延迟

animator.setStartDelay(500);  // 延迟500ms开始

View动画与属性动画对比

功能对比

特性补间动画属性动画
作用对象只能作用于View可以作用于任意对象
改变内容只能改变显示效果可以改变实际属性
动画结束后View恢复原状View保持最终状态
功能平移、缩放、旋转、透明度可以改变任意属性
性能较好最好
使用场景简单动画复杂动画

使用建议

  • 简单动画:使用补间动画
  • 复杂动画:使用属性动画
  • 需要改变实际属性:必须使用属性动画
  • 性能要求高:优先使用属性动画

动画性能优化

1. 使用硬件加速

// 在AndroidManifest.xml中启用
<application
    android:hardwareAccelerated="true">
    ...
</application>

2. 避免在动画中创建对象

// ❌ 错误:在动画回调中创建对象
animator.addUpdateListener(animation -> {
    Paint paint = new Paint();  // 每次更新都创建新对象
    canvas.drawCircle(x, y, radius, paint);
});

// ✅ 正确:提前创建对象
private Paint paint = new Paint();

animator.addUpdateListener(animation -> {
    canvas.drawCircle(x, y, radius, paint);  // 复用对象
});

3. 使用ViewPropertyAnimator

// ViewPropertyAnimator性能更好
view.animate()
    .alpha(0.0f)
    .translationX(200f)
    .setDuration(1000)
    .start();

4. 合理设置动画时长

// 动画时长不宜过长,一般300-500ms
animator.setDuration(300);

5. 及时取消动画

@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator = null;
    }
}

6. 避免过度绘制

// 使用clipRect减少绘制区域
canvas.clipRect(left, top, right, bottom);
// 绘制内容

7. 使用缓存

// 对于复杂的动画,可以使用Bitmap缓存
private Bitmap cacheBitmap;
private Canvas cacheCanvas;

private void initCache() {
    cacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    cacheCanvas = new Canvas(cacheBitmap);
}

动画最佳实践

1. 选择合适的动画类型

  • 简单View动画:使用补间动画
  • 复杂动画:使用属性动画
  • 图片序列:使用帧动画

2. 合理设置动画时长

  • 短动画:100-300ms
  • 中等动画:300-500ms
  • 长动画:500-1000ms

3. 使用合适的插值器

  • 进入动画:使用AccelerateInterpolator
  • 退出动画:使用DecelerateInterpolator
  • 通用动画:使用AccelerateDecelerateInterpolator

4. 及时清理资源

@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator.removeAllListeners();
        animator = null;
    }
}

5. 避免内存泄漏

// 使用弱引用或及时移除监听器
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        // 移除监听器
        animation.removeListener(this);
    }
});

6. 测试动画性能

// 使用Choreographer监控帧率
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        // 监控帧率
    }
});

Lottie动画

什么是Lottie?

Lottie是Airbnb开源的动画库,可以播放After Effects导出的JSON动画文件。

Lottie的特点

  1. 跨平台:支持Android、iOS、Web
  2. 文件小:JSON文件比视频或GIF小
  3. 可缩放:矢量动画,任意缩放不失真
  4. 易用:使用简单,功能强大

Lottie的使用

添加依赖
dependencies {
    implementation "com.airbnb.android:lottie:6.0.0"
}
XML使用
<com.airbnb.lottie.LottieAnimationView
    android:id="@+id/animationView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:lottie_fileName="animation.json"
    app:lottie_loop="true"
    app:lottie_autoPlay="true" />
代码使用
LottieAnimationView animationView = findViewById(R.id.animationView);
animationView.setAnimation("animation.json");
animationView.setRepeatCount(LottieDrawable.INFINITE);
animationView.playAnimation();

Lottie的常用方法

animationView.playAnimation();  // 播放动画
animationView.pauseAnimation();  // 暂停动画
animationView.cancelAnimation();  // 取消动画
animationView.setProgress(0.5f);  // 设置进度
animationView.setSpeed(2.0f);  // 设置播放速度

Lottie的使用场景

  1. 加载动画:替代GIF加载动画
  2. 图标动画:动态图标
  3. 交互动画:按钮点击、页面转场
  4. 品牌动画:Logo动画

动画实战案例

案例1:Activity转场动画

// 方法1:共享元素转场(推荐)
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
    this,
    view,
    "shared_element"  // 共享元素名称
);
startActivity(intent, options.toBundle());

// 方法2:使用overridePendingTransition
startActivity(intent);
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);

// 方法3:在主题中设置
<style name="AppTheme">
    <item name="android:windowEnterTransition">@transition/slide</item>
    <item name="android:windowExitTransition">@transition/slide</item>
</style>

案例2:Fragment转场动画

// 使用FragmentTransaction设置转场动画
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(
    R.anim.slide_in_right,  // 进入动画
    R.anim.slide_out_left,  // 退出动画
    R.anim.slide_in_left,   // 返回进入动画
    R.anim.slide_out_right  // 返回退出动画
);
transaction.replace(R.id.container, fragment);
transaction.commit();

案例3:RecyclerView动画

// 使用DefaultItemAnimator
RecyclerView.ItemAnimator animator = new DefaultItemAnimator();
animator.setAddDuration(300);
animator.setRemoveDuration(300);
animator.setMoveDuration(300);
animator.setChangeDuration(300);
recyclerView.setItemAnimator(animator);

// 自定义ItemAnimator
public class CustomItemAnimator extends DefaultItemAnimator {
    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        // 自定义添加动画
        View view = holder.itemView;
        view.setAlpha(0f);
        view.animate()
            .alpha(1f)
            .setDuration(300)
            .start();
        return super.animateAdd(holder);
    }
}

案例4:加载动画

// 旋转加载动画
ObjectAnimator animator = ObjectAnimator.ofFloat(
    loadingView,
    "rotation",
    0f,
    360f
);
animator.setDuration(1000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.start();

// 使用Lottie加载动画
LottieAnimationView animationView = findViewById(R.id.animationView);
animationView.setAnimation("loading.json");
animationView.loop(true);
animationView.playAnimation();

案例5:手势动画

// 跟随手指移动
private float initialX, initialY;

view.setOnTouchListener((v, event) -> {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            initialX = event.getX();
            initialY = event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            view.animate()
                .translationX(event.getX() - initialX)
                .translationY(event.getY() - initialY)
                .setDuration(0)
                .start();
            break;
        case MotionEvent.ACTION_UP:
            // 松手后回弹
            view.animate()
                .translationX(0)
                .translationY(0)
                .setDuration(300)
                .setInterpolator(new OvershootInterpolator())
                .start();
            break;
    }
    return true;
});

案例6:复杂动画组合

AnimatorSet animatorSet = new AnimatorSet();

// 先放大
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 1.5f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, 1.5f);

// 再旋转
ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);

// 最后淡出
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);

animatorSet.play(scaleX).with(scaleY);
animatorSet.play(rotation).after(scaleX);
animatorSet.play(alpha).after(rotation);
animatorSet.setDuration(1000);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();

案例7:使用Keyframe实现复杂动画

// 使用Keyframe实现回弹效果
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(0.5f, 300f);
Keyframe kf2 = Keyframe.ofFloat(0.8f, 250f);
Keyframe kf3 = Keyframe.ofFloat(1f, 300f);

PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2, kf3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
animator.setDuration(1000);
animator.start();

常见问题与解决方案

1. 动画结束后View恢复原状

问题: 补间动画结束后View恢复原状

解决方案: 使用属性动画或设置fillAfter

// 方法1:使用属性动画
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 200f);
animator.start();

// 方法2:设置fillAfter
animation.setFillAfter(true);

2. 动画性能问题

问题: 动画卡顿

解决方案:

  1. 启用硬件加速
  2. 避免在动画中创建对象
  3. 使用ViewPropertyAnimator
  4. 减少动画复杂度

3. 内存泄漏

问题: 动画导致内存泄漏

解决方案:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator.removeAllListeners();
        animator = null;
    }
}

4. 动画不执行

问题: 动画没有执行

解决方案:

  1. 检查View是否可见
  2. 检查动画是否已启动
  3. 检查属性名是否正确
  4. 检查View是否有getter/setter方法

面试题大全

一、动画基础(15题)

1. Android的动画类型有哪些?

答案: Android提供了三种动画类型:

  1. 补间动画(Tween Animation):也叫View动画,只能改变View的显示效果
  2. 帧动画(Frame Animation):逐帧播放图片序列
  3. 属性动画(Property Animation):可以改变对象的任意属性
2. 补间动画(Tween Animation)的特点是什么?

答案:

  1. 只能改变View的显示效果:不能改变View的实际属性
  2. 动画结束后View会恢复原状:动画只是视觉效果
  3. 性能较好:使用硬件加速
  4. 功能有限:只能实现平移、缩放、旋转、透明度变化
3. 帧动画(Frame Animation)的特点是什么?

答案:

  1. 逐帧播放:按顺序播放每一帧图片
  2. 资源占用较大:需要多张图片
  3. 适合简单动画:加载动画、图标动画
  4. 性能一般:需要加载多张图片
4. 属性动画(Property Animation)的特点是什么?

答案:

  1. 可以改变任意属性:不仅限于View,可以改变任何对象的属性
  2. 改变实际属性值:动画会真正改变对象的属性值
  3. 功能强大:支持复杂的动画效果
  4. 性能最好:使用硬件加速
5. ValueAnimator和ObjectAnimator的区别是什么?

答案:

  • ValueAnimator:只改变值,需要通过监听器手动更新View
  • ObjectAnimator:直接改变对象的属性值,自动更新

代码对比:

// ValueAnimator:需要手动更新
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(animation -> {
    float value = (float) animation.getAnimatedValue();
    view.setAlpha(value);  // 手动更新
});

// ObjectAnimator:自动更新
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);
animator.start();  // 自动更新alpha属性
6. 动画的插值器(Interpolator)是什么?

答案: 插值器定义了动画的变化速率,控制动画如何加速或减速。常用的插值器有:

  • LinearInterpolator:匀速
  • AccelerateInterpolator:加速
  • DecelerateInterpolator:减速
  • AccelerateDecelerateInterpolator:先加速后减速
  • BounceInterpolator:弹跳
7. 动画的监听器如何设置?

答案:

// AnimatorListener:监听动画生命周期
animator.addListener(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) {}
});

// AnimatorUpdateListener:监听每一帧更新
animator.addUpdateListener(animation -> {
    float value = (float) animation.getAnimatedValue();
    // 更新View
});
8. 动画的性能优化有哪些?

答案:

  1. 使用硬件加速:在AndroidManifest.xml中启用
  2. 避免在动画中创建对象:提前创建并复用
  3. 使用ViewPropertyAnimator:性能更好
  4. 合理设置动画时长:一般300-500ms
  5. 及时取消动画:避免内存泄漏
  6. 避免过度绘制:使用clipRect减少绘制区域
9. 动画的最佳实践有哪些?

答案:

  1. 选择合适的动画类型:简单动画用补间动画,复杂动画用属性动画
  2. 合理设置动画时长:短动画100-300ms,中等动画300-500ms
  3. 使用合适的插值器:进入动画用加速,退出动画用减速
  4. 及时清理资源:在onDestroy中取消动画
  5. 避免内存泄漏:使用弱引用或及时移除监听器
10. Lottie动画的使用场景是什么?

答案:

  1. 加载动画:替代GIF加载动画
  2. 图标动画:动态图标
  3. 交互动画:按钮点击、页面转场
  4. 品牌动画:Logo动画
11. 补间动画的四种类型是什么?

答案:

  1. 平移动画(TranslateAnimation):让View在X轴或Y轴上移动
  2. 缩放动画(ScaleAnimation):让View放大或缩小
  3. 旋转动画(RotateAnimation):让View旋转
  4. 透明度动画(AlphaAnimation):改变View的透明度
12. 属性动画的核心类有哪些?

答案:

  1. ValueAnimator:在指定时间内平滑改变某个值
  2. ObjectAnimator:直接改变对象的属性值
  3. AnimatorSet:组合多个动画
13. ViewPropertyAnimator的作用是什么?

答案: ViewPropertyAnimator是View的扩展方法,用于简化View的属性动画操作,性能更好。

使用:

view.animate()
    .alpha(0.0f)
    .translationX(200f)
    .setDuration(1000)
    .start();
14. 如何实现动画的组合?

答案: 使用AnimatorSet组合多个动画:

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator1).with(animator2);  // 同时执行
animatorSet.play(animator3).after(animator1);  // 顺序执行
animatorSet.start();
15. 如何实现动画的循环?

答案:

animator.setRepeatCount(ValueAnimator.INFINITE);  // 无限循环
animator.setRepeatCount(3);  // 循环3次
animator.setRepeatMode(ValueAnimator.RESTART);  // 重新开始
animator.setRepeatMode(ValueAnimator.REVERSE);  // 反向播放

二、动画高级(15题)

16. 动画的取消如何实现?

答案:

animator.cancel();  // 取消动画,会触发onAnimationCancel,View恢复到初始状态
animator.end();  // 立即结束动画到最终状态,不会触发onAnimationCancel,View保持最终状态

区别:

  • cancel():取消动画,View恢复到初始状态
  • end():立即结束到最终状态,View保持最终状态
19. 动画的暂停和恢复如何实现?

答案:

animator.pause();  // 暂停动画
animator.resume();  // 恢复动画
20. 动画的监听如何实现?

答案:

// 监听动画生命周期
animator.addListener(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) {}
});

// 监听每一帧更新
animator.addUpdateListener(animation -> {
    float value = (float) animation.getAnimatedValue();
    // 更新View
});
21. 动画的性能优化有哪些?

答案:

  1. 使用硬件加速:在AndroidManifest.xml中启用
  2. 避免在动画中创建对象:提前创建并复用
  3. 使用ViewPropertyAnimator:性能更好
  4. 合理设置动画时长:一般300-500ms
  5. 及时取消动画:避免内存泄漏
  6. 避免过度绘制:使用clipRect减少绘制区域
22. 动画的内存优化有哪些?

答案:

  1. 及时取消动画:在onDestroy中取消
  2. 移除监听器:避免持有View引用
  3. 使用弱引用:避免内存泄漏
  4. 及时释放资源:动画结束后释放Bitmap等资源
23. 如何自定义插值器?

答案:

public class CustomInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        // input: 0.0 到 1.0
        // 返回: 0.0 到 1.0
        return input * input;  // 二次函数,加速效果
    }
}

// 使用
animator.setInterpolator(new CustomInterpolator());
24. TypeEvaluator(类型估值器)的作用是什么?

答案: TypeEvaluator用于计算动画的中间值,系统提供了ArgbEvaluator(颜色估值器)和FloatEvaluator(浮点数估值器)。

// 自定义TypeEvaluator
public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
        int y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
        return new Point(x, y);
    }
}

// 使用
ValueAnimator animator = ValueAnimator.ofObject(
    new PointEvaluator(),
    new Point(0, 0),
    new Point(100, 100)
);
25. Keyframe(关键帧)的作用是什么?

答案: Keyframe用于定义动画的关键点,可以在关键点设置不同的值,实现更复杂的动画效果。

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(0.5f, 200f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);

PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
animator.start();
26. 如何为自定义属性添加动画?

答案:

// 自定义View需要提供getter和setter方法
public class CustomView extends View {
    private float progress;
    
    public float getProgress() {
        return progress;
    }
    
    public void setProgress(float progress) {
        this.progress = progress;
        invalidate();  // 重绘
    }
}

// 使用
ObjectAnimator animator = ObjectAnimator.ofFloat(
    customView,
    "progress",
    0f,
    100f
);
animator.start();
27. Activity转场动画如何实现?

答案:

// 方法1:共享元素转场(推荐,Android 5.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
        this,
        view,
        "shared_element"
    );
    startActivity(intent, options.toBundle());
} else {
    startActivity(intent);
    overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
}

// 方法2:使用overridePendingTransition(兼容所有版本)
startActivity(intent);
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
28. Fragment转场动画如何实现?

答案:

// 使用FragmentTransaction设置转场动画
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(
    R.anim.slide_in_right,  // 进入动画
    R.anim.slide_out_left,   // 退出动画
    R.anim.slide_in_left,    // 返回进入动画
    R.anim.slide_out_right   // 返回退出动画
);
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);
transaction.commit();
29. RecyclerView动画如何实现?

答案:

// 使用DefaultItemAnimator
RecyclerView.ItemAnimator animator = new DefaultItemAnimator();
animator.setAddDuration(300);
animator.setRemoveDuration(300);
animator.setMoveDuration(300);
animator.setChangeDuration(300);
recyclerView.setItemAnimator(animator);

// 自定义ItemAnimator
public class CustomItemAnimator extends DefaultItemAnimator {
    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        View view = holder.itemView;
        view.setAlpha(0f);
        view.animate()
            .alpha(1f)
            .setDuration(300)
            .start();
        return super.animateAdd(holder);
    }
}
30. 动画的测试如何进行?

答案:

  1. 单元测试:测试动画的逻辑和计算
  2. UI测试:使用Espresso测试动画的显示效果
  3. 性能测试:使用性能分析工具测试动画性能
  4. 使用Choreographer监控帧率:确保动画流畅(60fps)

三、动画实战(10题)

28. Fragment转场动画如何实现?

答案:

// 使用FragmentTransaction设置转场动画
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(
    R.anim.slide_in_right,  // 进入动画
    R.anim.slide_out_left,   // 退出动画
    R.anim.slide_in_left,    // 返回进入动画
    R.anim.slide_out_right   // 返回退出动画
);
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);
transaction.commit();
29. RecyclerView动画如何实现?

答案:

// 使用DefaultItemAnimator
RecyclerView.ItemAnimator animator = new DefaultItemAnimator();
animator.setAddDuration(300);
animator.setRemoveDuration(300);
animator.setMoveDuration(300);
animator.setChangeDuration(300);
recyclerView.setItemAnimator(animator);

// 自定义ItemAnimator
public class CustomItemAnimator extends DefaultItemAnimator {
    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        View view = holder.itemView;
        view.setAlpha(0f);
        view.animate()
            .alpha(1f)
            .setDuration(300)
            .start();
        return super.animateAdd(holder);
    }
}
30. 动画的测试如何进行?

答案:

  1. 单元测试:测试动画的逻辑和计算
  2. UI测试:使用Espresso测试动画的显示效果
  3. 性能测试:使用性能分析工具测试动画性能
  4. 使用Choreographer监控帧率:确保动画流畅(60fps)
31. 加载动画如何实现?

答案:

// 方法1:旋转加载动画
ObjectAnimator animator = ObjectAnimator.ofFloat(
    loadingView,
    "rotation",
    0f,
    360f
);
animator.setDuration(1000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.start();

// 方法2:使用Lottie加载动画
LottieAnimationView animationView = findViewById(R.id.animationView);
animationView.setAnimation("loading.json");
animationView.loop(true);
animationView.playAnimation();

// 方法3:使用帧动画
AnimationDrawable animationDrawable = (AnimationDrawable) loadingView.getBackground();
animationDrawable.start();
32. 手势动画如何实现?

答案:

// 跟随手指移动
private float initialX, initialY;

view.setOnTouchListener((v, event) -> {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            initialX = event.getX();
            initialY = event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            view.animate()
                .translationX(event.getX() - initialX)
                .translationY(event.getY() - initialY)
                .setDuration(0)
                .start();
            break;
        case MotionEvent.ACTION_UP:
            // 松手后回弹
            view.animate()
                .translationX(0)
                .translationY(0)
                .setDuration(300)
                .setInterpolator(new OvershootInterpolator())
                .start();
            break;
    }
    return true;
});
33. 复杂动画组合如何实现?

答案:

// 使用AnimatorSet组合多个动画
AnimatorSet animatorSet = new AnimatorSet();

// 先放大
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 1.5f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, 1.5f);

// 再旋转
ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);

// 最后淡出
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);

animatorSet.play(scaleX).with(scaleY);
animatorSet.play(rotation).after(scaleX);
animatorSet.play(alpha).after(rotation);
animatorSet.setDuration(1000);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();
34. 如何使用Keyframe实现复杂动画?

答案:

// 使用Keyframe实现回弹效果
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(0.5f, 300f);
Keyframe kf2 = Keyframe.ofFloat(0.8f, 250f);
Keyframe kf3 = Keyframe.ofFloat(1f, 300f);

// 为关键帧设置插值器
kf1.setInterpolator(new OvershootInterpolator());

PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2, kf3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
animator.setDuration(1000);
animator.start();
35. 动画的监控如何实现?

答案:

// 使用Choreographer监控帧率
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    private long lastFrameTime = 0;
    
    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTime != 0) {
            long frameTime = (frameTimeNanos - lastFrameTime) / 1000000;  // 转换为毫秒
            if (frameTime > 16) {  // 超过16ms,可能掉帧
                Log.w("Animation", "Frame dropped: " + frameTime + "ms");
            }
        }
        lastFrameTime = frameTimeNanos;
        Choreographer.getInstance().postFrameCallback(this);
    }
});
36. 动画的兼容性问题有哪些?

答案:

  1. 属性动画兼容性:Android 3.0以上才支持,低版本需要使用nineoldandroids库
  2. 硬件加速兼容性:某些设备不支持硬件加速
  3. 插值器兼容性:PathInterpolator需要Android 5.0以上
  4. 转场动画兼容性:Activity转场动画需要Android 5.0以上

解决方案:

// 使用兼容库
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    // 使用属性动画
    ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f).start();
} else {
    // 使用补间动画
    AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f);
    animation.setDuration(1000);
    view.startAnimation(animation);
}
37. 动画的调试方法有哪些?

答案:

  1. 使用开发者选项:开启"显示布局边界"和"GPU渲染模式分析"
  2. 使用Log监控:在动画回调中打印日志
  3. 使用Choreographer:监控每一帧的渲染时间
  4. 使用Systrace:分析动画性能
38. cancel()和end()的区别是什么?

答案:

  • cancel():取消动画,View恢复到初始状态,会触发onAnimationCancel
  • end():立即结束到最终状态,View保持最终状态,不会触发onAnimationCancel
animator.cancel();  // 取消动画,恢复初始状态
animator.end();  // 立即结束到最终状态
39. 如何避免动画导致的内存泄漏?

答案:

// 方法1:及时取消动画
@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator.removeAllListeners();
        animator = null;
    }
}

// 方法2:使用弱引用
private static class WeakAnimatorListener extends AnimatorListenerAdapter {
    private WeakReference<View> viewRef;
    
    public WeakAnimatorListener(View view) {
        viewRef = new WeakReference<>(view);
    }
    
    @Override
    public void onAnimationEnd(Animator animation) {
        View view = viewRef.get();
        if (view != null) {
            // 处理动画结束
        }
        animation.removeListener(this);
    }
}
40. 动画的总结

答案: Android动画系统提供了三种动画类型:补间动画、帧动画、属性动画。属性动画功能最强大,可以改变任意属性。使用动画时要注意性能优化,及时清理资源,避免内存泄漏。选择合适的动画类型、插值器和时长,可以提升用户体验。


总结

本文档全面覆盖了Android动画机制的所有知识点和面试题,包括:

  • ✅ 三种动画类型(补间动画、帧动画、属性动画)
  • ✅ 动画插值器和监听器
  • ✅ 动画组合与高级用法
  • ✅ 动画性能优化
  • ✅ 动画最佳实践
  • ✅ Lottie动画
  • ✅ 动画实战案例
  • ✅ 40道面试题及详细答案

核心要点:

  1. 补间动画只能改变显示效果,属性动画可以改变实际属性
  2. 属性动画功能最强大,推荐使用
  3. 使用硬件加速和ViewPropertyAnimator提高性能
  4. 及时清理资源,避免内存泄漏

补充知识点

动画的调试方法

1. 使用开发者选项
// 开启"显示布局边界"查看动画效果
// 开启"GPU渲染模式分析"查看动画性能
2. 使用Log监控
animator.addUpdateListener(animation -> {
    float value = (float) animation.getAnimatedValue();
    Log.d("Animation", "Value: " + value);
});
3. 使用Choreographer
Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {
    // 监控每一帧
});

动画的兼容性处理

1. 属性动画兼容性
// Android 3.0以下使用nineoldandroids库
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    // 使用属性动画
} else {
    // 使用补间动画或兼容库
}
2. 转场动画兼容性
// Activity转场动画需要Android 5.0以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(...);
    startActivity(intent, options.toBundle());
} else {
    startActivity(intent);
    overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
}

动画的常见错误

1. 忘记取消动画
// ❌ 错误:没有取消动画
@Override
protected void onDestroy() {
    super.onDestroy();
    // 忘记取消动画,可能导致内存泄漏
}

// ✅ 正确:及时取消动画
@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator = null;
    }
}
2. 在动画中创建对象
// ❌ 错误:在动画回调中创建对象
animator.addUpdateListener(animation -> {
    Paint paint = new Paint();  // 每次更新都创建新对象
    canvas.drawCircle(x, y, radius, paint);
});

// ✅ 正确:提前创建对象
private Paint paint = new Paint();
animator.addUpdateListener(animation -> {
    canvas.drawCircle(x, y, radius, paint);  // 复用对象
});
3. 动画时长设置不合理
// ❌ 错误:动画时长过长
animator.setDuration(5000);  // 5秒太长,用户会感觉卡顿

// ✅ 正确:合理设置动画时长
animator.setDuration(300);  // 300ms,流畅自然