Android 动画机制完整详解

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实现:

xml 复制代码
<translate
    android:duration="1000"
    android:fromXDelta="0"
    android:toXDelta="200"
    android:fromYDelta="0"
    android:toYDelta="200" />

代码实现:

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

作用: 让View放大或缩小

XML实现:

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%" />

代码实现:

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

xml 复制代码
<rotate
    android:duration="1000"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%" />

代码实现:

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

xml 复制代码
<alpha
    android:duration="1000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0" />

代码实现:

java 复制代码
AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f);
animation.setDuration(1000);
view.startAnimation(animation);

补间动画的组合

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

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

使用:

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

使用:

java 复制代码
ImageView imageView = findViewById(R.id.imageView);
imageView.setBackgroundResource(R.drawable.anim_frame);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();
代码实现
java 复制代码
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

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

基本使用:

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

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

ofObject()示例:

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

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

基本使用:

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

常用属性:

java 复制代码
// 透明度
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);

自定义属性:

java 复制代码
// 自定义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

作用: 组合多个动画

顺序执行:

java 复制代码
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator1).before(animator2);  // animator1在animator2之前
animatorSet.play(animator2).before(animator3);  // animator2在animator3之前
animatorSet.start();

同时执行:

java 复制代码
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.start();

复杂组合:

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

使用:

java 复制代码
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_alpha);
animator.setTarget(view);
animator.start();

ViewPropertyAnimator

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

使用:

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

链式调用:

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

TypeEvaluator(类型估值器)

什么是TypeEvaluator?

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

系统内置TypeEvaluator

1. ArgbEvaluator(颜色估值器)

java 复制代码
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(浮点数估值器)

java 复制代码
// ObjectAnimator内部使用FloatEvaluator
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);
animator.start();
自定义TypeEvaluator
java 复制代码
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的使用
java 复制代码
// 创建关键帧
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的插值器
java 复制代码
// 为关键帧设置插值器
Keyframe kf1 = Keyframe.ofFloat(0.5f, 200f);
kf1.setInterpolator(new OvershootInterpolator());

TypeEvaluator(类型估值器)

什么是TypeEvaluator?

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

系统内置TypeEvaluator

1. ArgbEvaluator(颜色估值器)

java 复制代码
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(浮点数估值器)

java 复制代码
// ObjectAnimator内部使用FloatEvaluator
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.0f);
animator.start();
自定义TypeEvaluator
java 复制代码
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的使用
java 复制代码
// 创建关键帧
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的插值器
java 复制代码
// 为关键帧设置插值器
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(线性插值器)

特点: 匀速变化

java 复制代码
animator.setInterpolator(new LinearInterpolator());
2. AccelerateInterpolator(加速插值器)

特点: 逐渐加速

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

特点: 逐渐减速

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

特点: 先加速后减速

java 复制代码
animator.setInterpolator(new AccelerateDecelerateInterpolator());
5. OvershootInterpolator(回弹插值器)

特点: 超过目标值后回弹

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

特点: 先向后移动再向前

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

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

java 复制代码
animator.setInterpolator(new AnticipateOvershootInterpolator());
8. BounceInterpolator(弹跳插值器)

特点: 弹跳效果

java 复制代码
animator.setInterpolator(new BounceInterpolator());
9. CycleInterpolator(循环插值器)

特点: 循环变化

java 复制代码
animator.setInterpolator(new CycleInterpolator(2.0f));  // 指定循环次数

自定义插值器

java 复制代码
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的使用
java 复制代码
// 使用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

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

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

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

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

使用Lambda简化

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

动画组合与高级用法

动画组合

顺序执行
java 复制代码
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(animator1, animator2, animator3);
animatorSet.start();
同时执行
java 复制代码
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.start();
复杂组合
java 复制代码
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();

动画循环

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

动画取消

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

动画暂停和恢复

java 复制代码
animator.pause();  // 暂停动画
animator.resume();  // 恢复动画

动画延迟

java 复制代码
animator.setStartDelay(500);  // 延迟500ms开始

View动画与属性动画对比

功能对比

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

使用建议

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

动画性能优化

1. 使用硬件加速

java 复制代码
// 在AndroidManifest.xml中启用
<application
    android:hardwareAccelerated="true">
    ...
</application>

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

java 复制代码
// ❌ 错误:在动画回调中创建对象
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

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

4. 合理设置动画时长

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

5. 及时取消动画

java 复制代码
@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator = null;
    }
}

6. 避免过度绘制

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

7. 使用缓存

java 复制代码
// 对于复杂的动画,可以使用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. 及时清理资源

java 复制代码
@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator.removeAllListeners();
        animator = null;
    }
}

5. 避免内存泄漏

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

6. 测试动画性能

java 复制代码
// 使用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的使用

添加依赖
gradle 复制代码
dependencies {
    implementation "com.airbnb.android:lottie:6.0.0"
}
XML使用
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" />
代码使用
java 复制代码
LottieAnimationView animationView = findViewById(R.id.animationView);
animationView.setAnimation("animation.json");
animationView.setRepeatCount(LottieDrawable.INFINITE);
animationView.playAnimation();

Lottie的常用方法

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

Lottie的使用场景

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

动画实战案例

案例1:Activity转场动画

java 复制代码
// 方法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转场动画

java 复制代码
// 使用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动画

java 复制代码
// 使用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:加载动画

java 复制代码
// 旋转加载动画
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:手势动画

java 复制代码
// 跟随手指移动
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:复杂动画组合

java 复制代码
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实现复杂动画

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

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

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

2. 动画性能问题

问题: 动画卡顿

解决方案:

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

3. 内存泄漏

问题: 动画导致内存泄漏

解决方案:

java 复制代码
@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:直接改变对象的属性值,自动更新

代码对比:

java 复制代码
// 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. 动画的监听器如何设置?

答案:

java 复制代码
// 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的属性动画操作,性能更好。

使用:

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

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

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

答案:

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

二、动画高级(15题)

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

答案:

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

区别:

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

答案:

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

答案:

java 复制代码
// 监听动画生命周期
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. 如何自定义插值器?

答案:

java 复制代码
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(浮点数估值器)。

java 复制代码
// 自定义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用于定义动画的关键点,可以在关键点设置不同的值,实现更复杂的动画效果。

java 复制代码
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. 如何为自定义属性添加动画?

答案:

java 复制代码
// 自定义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转场动画如何实现?

答案:

java 复制代码
// 方法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转场动画如何实现?

答案:

java 复制代码
// 使用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动画如何实现?

答案:

java 复制代码
// 使用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转场动画如何实现?

答案:

java 复制代码
// 使用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动画如何实现?

答案:

java 复制代码
// 使用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. 加载动画如何实现?

答案:

java 复制代码
// 方法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. 手势动画如何实现?

答案:

java 复制代码
// 跟随手指移动
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. 复杂动画组合如何实现?

答案:

java 复制代码
// 使用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实现复杂动画?

答案:

java 复制代码
// 使用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. 动画的监控如何实现?

答案:

java 复制代码
// 使用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以上

解决方案:

java 复制代码
// 使用兼容库
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
java 复制代码
animator.cancel();  // 取消动画,恢复初始状态
animator.end();  // 立即结束到最终状态
39. 如何避免动画导致的内存泄漏?

答案:

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

动画的兼容性处理

1. 属性动画兼容性
java 复制代码
// Android 3.0以下使用nineoldandroids库
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    // 使用属性动画
} else {
    // 使用补间动画或兼容库
}
2. 转场动画兼容性
java 复制代码
// 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. 忘记取消动画
java 复制代码
// ❌ 错误:没有取消动画
@Override
protected void onDestroy() {
    super.onDestroy();
    // 忘记取消动画,可能导致内存泄漏
}

// ✅ 正确:及时取消动画
@Override
protected void onDestroy() {
    super.onDestroy();
    if (animator != null) {
        animator.cancel();
        animator = null;
    }
}
2. 在动画中创建对象
java 复制代码
// ❌ 错误:在动画回调中创建对象
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. 动画时长设置不合理
java 复制代码
// ❌ 错误:动画时长过长
animator.setDuration(5000);  // 5秒太长,用户会感觉卡顿

// ✅ 正确:合理设置动画时长
animator.setDuration(300);  // 300ms,流畅自然
相关推荐
城东米粉儿2 小时前
android 离屏预渲染 笔记
android
iReachers2 小时前
HTML打包APK(安卓APP)中下载功能常见问题和详细介绍
前端·javascript·html·html打包apk·网页打包app·下载功能
未知名Android用户2 小时前
Android自定义 View + Canvas—声纹小球动画
android
颜酱2 小时前
前端算法必备:双指针从入门到很熟练(快慢指针+相向指针+滑动窗口)
前端·后端·算法
lichenyang4532 小时前
从零开始:使用 Docker 部署 React 前端项目完整实战
前端
明月_清风2 小时前
【开源项目推荐】Biome:让前端代码质量工具链快到飞起来
前端
愈努力俞幸运2 小时前
vue3 demo教程(Vue Devtools)
前端·javascript·vue.js
持续前行2 小时前
在 Vue3 中使用 LogicFlow 更新节点名称
前端·javascript·vue.js
Anita_Sun2 小时前
Underscore.js 整体设计思路与架构分析
前端·javascript