Android 动画机制完整详解
本文档全面详解Android动画机制,包含补间动画、帧动画、属性动画、插值器、性能优化等所有内容,覆盖所有相关面试题。
Android动画概述
什么是Android动画?
Android动画是让View或属性在一段时间内平滑变化的效果,用于提升用户体验,使界面更加生动和流畅。
Android动画的分类
Android提供了三种动画类型:
- 补间动画(Tween Animation):也叫View动画,只能改变View的显示效果,不能改变View的属性
- 帧动画(Frame Animation):逐帧播放图片序列,形成动画效果
- 属性动画(Property Animation):可以改变对象的任意属性,功能最强大
三种动画的对比
| 动画类型 | 作用对象 | 改变内容 | 适用场景 | 性能 |
|---|---|---|---|---|
| 补间动画 | View | 显示效果(位置、大小、透明度、旋转) | 简单的View动画 | 较好 |
| 帧动画 | View | 图片序列 | 加载动画、图标动画 | 一般 |
| 属性动画 | 任意对象 | 任意属性 | 复杂动画、自定义动画 | 最好 |
动画的设计思想
- 平滑过渡:通过插值器实现平滑的动画效果
- 性能优化:使用硬件加速提高动画性能
- 灵活控制:支持动画的组合、循环、取消等操作
补间动画(Tween Animation)
什么是补间动画?
补间动画是Android最早的动画类型,通过指定动画的开始和结束状态,系统自动计算中间帧,实现平滑的动画效果。
补间动画的特点
- 只能改变View的显示效果:不能改变View的实际属性(如位置、大小)
- 动画结束后View会恢复原状:动画只是视觉效果
- 性能较好:使用硬件加速
- 功能有限:只能实现平移、缩放、旋转、透明度变化
补间动画的类型
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 |
插值器 | 插值器资源 |
补间动画的局限性
- 不能改变View的实际属性:动画只是视觉效果
- 不能改变非View对象:只能作用于View
- 功能有限:只能实现平移、缩放、旋转、透明度变化
- 动画结束后恢复原状:fillAfter只是视觉效果
帧动画(Frame Animation)
什么是帧动画?
帧动画是通过逐帧播放图片序列,形成动画效果,类似于GIF动画。
帧动画的特点
- 逐帧播放:按顺序播放每一帧图片
- 资源占用较大:需要多张图片
- 适合简单动画:加载动画、图标动画
- 性能一般:需要加载多张图片
帧动画的实现
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 |
每帧的持续时间 | 毫秒数 |
帧动画的注意事项
- 资源优化:使用合适的图片格式和大小
- 内存管理:及时停止动画,释放资源
- 性能优化:避免使用过多帧或过大的图片
属性动画(Property Animation)
什么是属性动画?
属性动画是Android 3.0引入的动画系统,可以改变对象的任意属性,功能最强大。
属性动画的特点
- 可以改变任意属性:不仅限于View,可以改变任何对象的属性
- 改变实际属性值:动画会真正改变对象的属性值
- 功能强大:支持复杂的动画效果
- 性能最好:使用硬件加速
属性动画的核心类
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的优势
- 更灵活:可以定义任意曲线
- 更精确:通过Path精确控制动画速度
- 更流畅:可以实现更自然的动画效果
插值器对比表
| 插值器 | 效果 | 适用场景 |
|---|---|---|
| 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的特点
- 跨平台:支持Android、iOS、Web
- 文件小:JSON文件比视频或GIF小
- 可缩放:矢量动画,任意缩放不失真
- 易用:使用简单,功能强大
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的使用场景
- 加载动画:替代GIF加载动画
- 图标动画:动态图标
- 交互动画:按钮点击、页面转场
- 品牌动画: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. 动画性能问题
问题: 动画卡顿
解决方案:
- 启用硬件加速
- 避免在动画中创建对象
- 使用ViewPropertyAnimator
- 减少动画复杂度
3. 内存泄漏
问题: 动画导致内存泄漏
解决方案:
java
@Override
protected void onDestroy() {
super.onDestroy();
if (animator != null) {
animator.cancel();
animator.removeAllListeners();
animator = null;
}
}
4. 动画不执行
问题: 动画没有执行
解决方案:
- 检查View是否可见
- 检查动画是否已启动
- 检查属性名是否正确
- 检查View是否有getter/setter方法
面试题大全
一、动画基础(15题)
1. Android的动画类型有哪些?
答案: Android提供了三种动画类型:
- 补间动画(Tween Animation):也叫View动画,只能改变View的显示效果
- 帧动画(Frame Animation):逐帧播放图片序列
- 属性动画(Property Animation):可以改变对象的任意属性
2. 补间动画(Tween Animation)的特点是什么?
答案:
- 只能改变View的显示效果:不能改变View的实际属性
- 动画结束后View会恢复原状:动画只是视觉效果
- 性能较好:使用硬件加速
- 功能有限:只能实现平移、缩放、旋转、透明度变化
3. 帧动画(Frame Animation)的特点是什么?
答案:
- 逐帧播放:按顺序播放每一帧图片
- 资源占用较大:需要多张图片
- 适合简单动画:加载动画、图标动画
- 性能一般:需要加载多张图片
4. 属性动画(Property Animation)的特点是什么?
答案:
- 可以改变任意属性:不仅限于View,可以改变任何对象的属性
- 改变实际属性值:动画会真正改变对象的属性值
- 功能强大:支持复杂的动画效果
- 性能最好:使用硬件加速
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. 动画的性能优化有哪些?
答案:
- 使用硬件加速:在AndroidManifest.xml中启用
- 避免在动画中创建对象:提前创建并复用
- 使用ViewPropertyAnimator:性能更好
- 合理设置动画时长:一般300-500ms
- 及时取消动画:避免内存泄漏
- 避免过度绘制:使用clipRect减少绘制区域
9. 动画的最佳实践有哪些?
答案:
- 选择合适的动画类型:简单动画用补间动画,复杂动画用属性动画
- 合理设置动画时长:短动画100-300ms,中等动画300-500ms
- 使用合适的插值器:进入动画用加速,退出动画用减速
- 及时清理资源:在onDestroy中取消动画
- 避免内存泄漏:使用弱引用或及时移除监听器
10. Lottie动画的使用场景是什么?
答案:
- 加载动画:替代GIF加载动画
- 图标动画:动态图标
- 交互动画:按钮点击、页面转场
- 品牌动画:Logo动画
11. 补间动画的四种类型是什么?
答案:
- 平移动画(TranslateAnimation):让View在X轴或Y轴上移动
- 缩放动画(ScaleAnimation):让View放大或缩小
- 旋转动画(RotateAnimation):让View旋转
- 透明度动画(AlphaAnimation):改变View的透明度
12. 属性动画的核心类有哪些?
答案:
- ValueAnimator:在指定时间内平滑改变某个值
- ObjectAnimator:直接改变对象的属性值
- 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. 动画的性能优化有哪些?
答案:
- 使用硬件加速:在AndroidManifest.xml中启用
- 避免在动画中创建对象:提前创建并复用
- 使用ViewPropertyAnimator:性能更好
- 合理设置动画时长:一般300-500ms
- 及时取消动画:避免内存泄漏
- 避免过度绘制:使用clipRect减少绘制区域
22. 动画的内存优化有哪些?
答案:
- 及时取消动画:在onDestroy中取消
- 移除监听器:避免持有View引用
- 使用弱引用:避免内存泄漏
- 及时释放资源:动画结束后释放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. 动画的测试如何进行?
答案:
- 单元测试:测试动画的逻辑和计算
- UI测试:使用Espresso测试动画的显示效果
- 性能测试:使用性能分析工具测试动画性能
- 使用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. 动画的测试如何进行?
答案:
- 单元测试:测试动画的逻辑和计算
- UI测试:使用Espresso测试动画的显示效果
- 性能测试:使用性能分析工具测试动画性能
- 使用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. 动画的兼容性问题有哪些?
答案:
- 属性动画兼容性:Android 3.0以上才支持,低版本需要使用nineoldandroids库
- 硬件加速兼容性:某些设备不支持硬件加速
- 插值器兼容性:PathInterpolator需要Android 5.0以上
- 转场动画兼容性: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. 动画的调试方法有哪些?
答案:
- 使用开发者选项:开启"显示布局边界"和"GPU渲染模式分析"
- 使用Log监控:在动画回调中打印日志
- 使用Choreographer:监控每一帧的渲染时间
- 使用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道面试题及详细答案
核心要点:
- 补间动画只能改变显示效果,属性动画可以改变实际属性
- 属性动画功能最强大,推荐使用
- 使用硬件加速和ViewPropertyAnimator提高性能
- 及时清理资源,避免内存泄漏
补充知识点
动画的调试方法
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,流畅自然