引言
在Android应用开发中,动画是提升用户体验的重要手段。从早期Android版本提供的补间动画(Tween Animation)和帧动画(Frame Animation),到Android 3.0引入的属性动画(Property Animation),动画框架不断演进,赋予开发者更强大、更灵活的动画控制能力。本文将深入探讨属性动画的原理,并与传统动画进行对比,然后逐步讲解如何自定义插值器与估值器,实现动画的暂停、恢复、反转与组合,最后介绍物理动画(Spring、Fling)的实现,并整合实战案例,构建一个完整的交互动画效果库。
1. 属性动画 vs 补间动画 vs 帧动画
1.1 帧动画(Frame Animation)
帧动画是最简单的动画形式,它通过快速播放一系列预先定义的图片(drawable资源)来产生动画效果。通常使用AnimationDrawable实现。
- 优点:实现简单,适合复杂逐帧变化(如GIF)。
- 缺点:图片资源占用内存大,无法动态改变对象属性,仅支持View背景变化。
1.2 补间动画(Tween Animation)
补间动画通过对View进行旋转、平移、缩放、透明度变换来实现动画效果。使用Animation及其子类(TranslateAnimation、RotateAnimation等)。
- 优点:使用方便,代码量少。
- 缺点:只改变View的绘制位置,而不改变View的真实属性(如点击事件位置仍停留在原地);仅支持View,且只能作用于少数固定属性。
1.3 属性动画(Property Animation)
属性动画是Android 3.0引入的新框架,它通过不断改变对象的属性值来实现动画。核心类为ValueAnimator和ObjectAnimator。属性动画直接修改对象的实际属性(如translationX、alpha等),因此对象的行为会随之改变(例如按钮移动后点击事件在新位置生效)。
优点:
- 作用于任何对象的任何属性(只要提供get/set方法)。
- 可以自定义插值器(Interpolator)和估值器(TypeEvaluator),实现复杂动画效果。
- 支持动画组合(AnimatorSet)和动画监听。
- 性能更好(基于属性值变化,而非重绘整个View)。
对比总结:
| 类型 | 操作对象 | 改变属性 | 内存占用 | 灵活性 |
|---|---|---|---|---|
| 帧动画 | Drawable资源 | 否 | 高 | 低 |
| 补间动画 | View | 否 | 中 | 中 |
| 属性动画 | 任意对象属性 | 是 | 低 | 高 |
2. 属性动画核心原理
属性动画的工作原理可以概括为:在一个时间间隔内,通过插值器计算当前动画进度对应的属性值,然后不断将该值设置给目标对象,触发界面重绘。
核心组件:
- TimeInterpolator:根据时间流逝的百分比计算出当前属性值变化的百分比(可理解为动画速率曲线)。
- TypeEvaluator:根据当前属性值变化的百分比计算出具体属性值(如颜色、位置等)。
- ValueAnimator:负责动画的计时、循环、刷新等核心逻辑。
- ObjectAnimator:继承自ValueAnimator,直接对目标对象的属性进行赋值。
简单示例:
java
ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
view.setTranslationX(value);
}
});
animator.start();
3. 自定义TypeEvaluator和TimeInterpolator
3.1 自定义TypeEvaluator
TypeEvaluator用于将动画进度(0~1)转换为具体的属性值。系统内置了IntEvaluator、FloatEvaluator、ArgbEvaluator等。当我们需要对非基本类型(如Point、自定义颜色渐变规则)进行动画时,就需要自定义。
示例:自定义PointEvaluator(实现点从起点到终点的移动)
java
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
float x = startValue.x + fraction * (endValue.x - startValue.x);
float y = startValue.y + fraction * (endValue.y - startValue.y);
return new Point((int)x, (int)y);
}
}
// 使用
ObjectAnimator animator = ObjectAnimator.ofObject(view, "position",
new PointEvaluator(), startPoint, endPoint);
animator.setDuration(2000);
animator.start();
3.2 自定义TimeInterpolator
插值器决定了动画的速率变化。系统提供了LinearInterpolator、AccelerateDecelerateInterpolator等。我们可以通过实现TimeInterpolator接口来自定义。
示例:自定义一个反弹插值器(简单模拟)
java
public class BounceInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
// 使用正弦函数模拟反弹效果
return (float) (Math.abs(Math.sin(6 * input * Math.PI)) * (1 - input));
}
}
// 使用
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", 0, -500);
animator.setInterpolator(new BounceInterpolator());
animator.setDuration(2000);
animator.start();
4. 动画的暂停、恢复、反转与组合
4.1 暂停与恢复
使用Animator的pause()和resume()方法(API 19+)。如果需兼容低版本,可以使用AnimatorListener手动记录状态。
java
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
animator.setDuration(2000);
// 暂停
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
animator.pause();
}
// 恢复
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
animator.resume();
}
4.2 反转
调用reverse()方法可以让动画反向执行。如果动画正在运行,调用reverse()会立即反转。
java
animator.reverse();
4.3 组合动画
使用AnimatorSet可以组合多个动画,支持顺序(playSequentially)或同时(playTogether)执行,以及灵活的依赖关系(after、before、with)。
java
ObjectAnimator moveX = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f);
ObjectAnimator moveY = ObjectAnimator.ofFloat(view, "translationY", 0f, 500f);
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
AnimatorSet set = new AnimatorSet();
set.play(moveX).with(moveY).before(fadeOut);
set.setDuration(1000);
set.start();
5. 物理动画(Spring、Fling)的实现
物理动画基于物理学模型(如弹簧、惯性)来模拟真实世界的运动。Android在Support Library(现AndroidX)中提供了DynamicAnimation及其子类:SpringAnimation和FlingAnimation。
5.1 SpringAnimation(弹簧动画)
弹簧动画模拟弹簧的振荡效果,需要设置刚度(SpringForce stiffness)和阻尼比(damping ratio)。
java
// 创建一个弹簧动画,使view的scaleX从当前值变化到2.0f
SpringAnimation springAnim = new SpringAnimation(view, SpringAnimation.SCALE_X, 2f);
SpringForce springForce = new SpringForce(2f)
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY);
springAnim.setSpring(springForce);
springAnim.start();
5.2 FlingAnimation(惯性动画)
惯性动画模拟抛掷动作,需要设置初始速度。常用于滑动列表的惯性滚动。
java
FlingAnimation flingAnim = new FlingAnimation(view, DynamicAnimation.TRANSLATION_X);
flingAnim.setStartVelocity(500f) // 像素/秒
.setFriction(1.1f) // 摩擦力,默认为1
.setMinValue(0)
.setMaxValue(1000)
.start();
物理动画同样支持添加更新监听器和状态监听器,可以实时获取当前值。
6. 实战:实现一个完整的交互动画效果库
现在我们将综合以上知识点,构建一个小型动画效果库,包含:
属性动画基础封装
自定义插值器与估值器
动画控制(暂停/恢复/反转)
物理动画示例
6.1 项目结构
text
animlib/
├── AnimUtils.java // 工具类,快速创建常用动画
├── evaluator/ // 自定义估值器
│ ├── PointEvaluator.java
│ └── HsvEvaluator.java // HSV颜色空间估值器
├── interpolator/ // 自定义插值器
│ ├── BounceInterpolator.java
│ └── OvershootInterpolator.java
└── physics/ // 物理动画封装
├── SpringAnimHelper.java
└── FlingAnimHelper.java
6.2 AnimUtils:快速创建动画
java
public class AnimUtils {
public static ObjectAnimator createFadeIn(View view, long duration) {
return ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).setDuration(duration);
}
public static ObjectAnimator createFadeOut(View view, long duration) {
return ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).setDuration(duration);
}
public static ObjectAnimator createMoveX(View view, float from, float to, long duration) {
return ObjectAnimator.ofFloat(view, "translationX", from, to).setDuration(duration);
}
public static AnimatorSet createShake(View view) {
ObjectAnimator shake1 = ObjectAnimator.ofFloat(view, "translationX", 0, 15, -15, 10, -10, 5, -5, 0);
shake1.setDuration(800);
shake1.setInterpolator(new AccelerateDecelerateInterpolator());
return new AnimatorSet().play(shake1);
}
}
6.3 HsvEvaluator:HSV颜色渐变
HSV颜色空间更适合平滑的色彩过渡。
java
public class HsvEvaluator implements TypeEvaluator<Integer> {
float[] startHsv = new float[3];
float[] endHsv = new float[3];
float[] outHsv = new float[3];
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
Color.colorToHSV(startValue, startHsv);
Color.colorToHSV(endValue, endHsv);
// 计算插值后的HSV
outHsv[0] = startHsv[0] + fraction * (endHsv[0] - startHsv[0]);
outHsv[1] = startHsv[1] + fraction * (endHsv[1] - startHsv[1]);
outHsv[2] = startHsv[2] + fraction * (endHsv[2] - startHsv[2]);
return Color.HSVToColor(outHsv);
}
}
6.4 使用自定义估值器改变背景颜色
java
ValueAnimator colorAnim = ValueAnimator.ofObject(new HsvEvaluator(), Color.RED, Color.BLUE);
colorAnim.setDuration(3000);
colorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setBackgroundColor((Integer) animation.getAnimatedValue());
}
});
colorAnim.start();
6.5 动画控制面板示例
创建一个包含开始、暂停、恢复、反转按钮的界面,控制一个属性动画。
java
public class AnimControlActivity extends AppCompatActivity {
private ObjectAnimator animator;
private View targetView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anim_control);
targetView = findViewById(R.id.target_view);
animator = ObjectAnimator.ofFloat(targetView, "rotation", 0f, 360f);
animator.setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
findViewById(R.id.btn_start).setOnClickListener(v -> animator.start());
findViewById(R.id.btn_pause).setOnClickListener(v -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
animator.pause();
}
});
findViewById(R.id.btn_resume).setOnClickListener(v -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
animator.resume();
}
});
findViewById(R.id.btn_reverse).setOnClickListener(v -> animator.reverse());
}
}
6.6 物理动画辅助类
java
public class SpringAnimHelper {
public static void createSpring(View view, DynamicAnimation.ViewProperty property,
float finalPosition, float stiffness, float damping) {
SpringAnimation anim = new SpringAnimation(view, property, finalPosition);
anim.getSpring().setStiffness(stiffness).setDampingRatio(damping);
anim.start();
}
}
7. 总结
本文从属性动画的原理出发,对比了传统动画与属性动画的优劣,详细讲解了自定义TypeEvaluator和TimeInterpolator的方法,演示了动画的暂停、恢复、反转与组合,并引入了物理动画(Spring、Fling)的实现。最后通过实战案例,将所学知识整合成一个简易的动画库,帮助读者深入理解并灵活运用Android属性动画。
属性动画的强大之处在于其灵活性和扩展性,掌握它可以让你的应用交互更加生动自然。希望本文能对你在Android动画开发之路上有所帮助。