Android动画合集之属性动画-又见
Android 动画合集之属性动画 – 又见
属性动画(Property Animation)是 Android 3.0(API 11)引入的强大动画机制,允许开发者通过动态修改对象的属性值(如 View 的位置、透明度、自定义对象的字段等)实现平滑动画效果。前文已介绍属性动画的基础概念、核心类(如 ValueAnimator
、ObjectAnimator
、AnimatorSet
、ViewPropertyAnimator
)和基本用法(《属性动画 – 初见》)。作为 Android 动画合集的延续,本文(《属性动画 – 又见》)将深入探讨属性动画的高级用法,包括多属性动画、自定义插值器(Interpolator)、类型评估器(TypeEvaluator)、关键帧(Keyframes)、动画监听器、以及与 Canvas 和 Paint 的结合,为开发者提供更复杂的动画实现方案。帧动画和补间动画已在前文覆盖,后续可探讨过渡动画或 Lottie。
属性动画的高级特性
属性动画的灵活性使其适用于复杂场景,以下是高级特性的核心点:
- 多属性动画:通过
PropertyValuesHolder
或AnimatorSet
实现多个属性同时或顺序变化。 - 自定义插值器:控制动画的时间-值曲线,实现非标准动画效果(如弹簧、抛物线)。
- 类型评估器:支持非数值类型的动画(如颜色、点坐标)。
- 关键帧:定义动画中的特定时间点和值,创建非线性动画路径。
- 动画监听器:监控动画状态(如开始、结束、取消),实现交互逻辑。
- 结合 Canvas:在自定义 View 中结合属性动画和 Canvas 绘制,实现动态图形效果。
1. 多属性动画
多属性动画允许同时对对象的多个属性(如透明度、平移、旋转)进行动画操作,主要通过 PropertyValuesHolder
或 AnimatorSet
实现。
使用 PropertyValuesHolder
PropertyValuesHolder
用于在单个 ObjectAnimator
中控制多个属性动画。
- 代码示例:
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
public class MultiPropertyAnimationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_property_animation);
ImageView imageView = findViewById(R.id.image_view);
// 定义多个属性
PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);
PropertyValuesHolder translateXHolder = PropertyValuesHolder.ofFloat("translationX", 0f, 200f);
PropertyValuesHolder rotateHolder = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
// 创建 ObjectAnimator
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alphaHolder, translateXHolder, rotateHolder);
animator.setDuration(1000);
// 按钮触发
findViewById(R.id.start_button).setOnClickListener(v -> animator.start());
}
}
- 说明:
ofPropertyValuesHolder
将多个PropertyValuesHolder
组合,同步执行。- 每个属性独立设置值范围(如
alpha
从 0 到 1,translationX
从 0 到 200)。 - 比多个
ObjectAnimator
更高效,减少对象创建。
使用 AnimatorSet
AnimatorSet
允许控制动画的播放顺序(如顺序、同时、延迟)。
- 代码示例:
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
public class AnimatorSetActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_property_animation);
ImageView imageView = findViewById(R.id.image_view);
// 创建动画
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f);
ObjectAnimator translate = ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
// 组合动画:alpha 和 translate 同时,rotate 在后
AnimatorSet set = new AnimatorSet();
set.play(alpha).with(translate).before(rotate);
set.setDuration(1000);
// 按钮触发
findViewById(R.id.start_button).setOnClickListener(v -> set.start());
}
}
- 说明:
play().with()
:alpha 和 translate 同时播放。play().before()
:rotate 在前两者完成后播放。- 可通过
setStartDelay(long)
添加延迟。
2. 自定义插值器
插值器(Interpolator)控制动画的时间-值曲线,决定动画的节奏。Android 提供内置插值器(如 LinearInterpolator
、OvershootInterpolator
),但复杂动画可能需要自定义插值器。
- 实现自定义插值器:
import android.view.animation.Interpolator;
public class BounceInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
// 模拟弹跳效果:input 是时间进度 (0-1)
return (float) (1 - Math.pow(1 - input, 2)); // 抛物线弹跳
}
}
- 使用示例:
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "translationY", 0f, 200f);
animator.setInterpolator(new BounceInterpolator());
animator.setDuration(1000);
animator.start();
- 说明:
getInterpolation(float input)
:输入时间进度 (0-1),输出值进度 (0-1)。- 可实现弹簧、抛物线、随机抖动等效果。
- API 21+ 推荐实现
TimeInterpolator
接口。
3. 自定义 TypeEvaluator
TypeEvaluator
定义如何在动画值之间插值,支持非数值类型(如颜色、点、自定义对象)。
- 实现自定义 TypeEvaluator(颜色渐变):
import android.animation.TypeEvaluator;
import android.graphics.Color;
public class ColorEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startR = Color.red(startValue), startG = Color.green(startValue), startB = Color.blue(startValue);
int endR = Color.red(endValue), endG = Color.green(endValue), endB = Color.blue(endValue);
int r = (int) (startR + fraction * (endR - startR));
int g = (int) (startG + fraction * (endG - startG));
int b = (int) (startB + fraction * (endB - startB));
return Color.rgb(r, g, b);
}
}
- 使用示例:
ValueAnimator animator = ValueAnimator.ofObject(new ColorEvaluator(), Color.RED, Color.BLUE);
animator.setDuration(1000);
animator.addUpdateListener(animation -> {
int color = (int) animation.getAnimatedValue();
imageView.setBackgroundColor(color);
});
animator.start();
- 说明:
evaluate
:根据 fraction(0-1)计算起始值到结束值的中间值。- 适用于颜色、PointF、自定义对象等复杂类型。
4. 关键帧(Keyframes)
关键帧(Keyframe
)允许在动画中定义特定时间点的值,实现非线性动画路径。
- 代码示例:
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
public class KeyframeAnimationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_property_animation);
ImageView imageView = findViewById(R.id.image_view);
// 定义关键帧:0% 在 0,50% 在 300,100% 回 0
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(0.5f, 300f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, holder);
animator.setDuration(1000);
findViewById(R.id.start_button).setOnClickListener(v -> animator.start());
}
}
- 说明:
Keyframe.ofFloat(fraction, value)
:定义时间点(fraction,0-1)和值。- 适合复杂路径动画(如先右移再返回)。
5. 动画监听器
AnimatorListener
监控动画状态(如开始、结束、取消),用于触发后续逻辑。
- 代码示例:
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f);
animator.setDuration(1000);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("Animation", "Started");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("Animation", "Ended");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("Animation", "Cancelled");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.d("Animation", "Repeated");
}
});
animator.start();
- 说明:
- 使用
AnimatorListenerAdapter
可只实现部分方法。 - 常用于状态切换或触发后续动画。
6. 结合 Canvas 和 Paint
属性动画可以与 Canvas
和 Paint
结合,在自定义 View 中实现动态绘制效果。
- 代码示例(动态渐变矩形):
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import android.animation.ValueAnimator;
import android.os.Bundle;
public class CanvasAnimationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_canvas_animation);
CustomView customView = findViewById(R.id.custom_view);
ValueAnimator animator = ValueAnimator.ofFloat(0f, 360f);
animator.setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(animation -> {
customView.setRotationAngle((float) animation.getAnimatedValue());
customView.invalidate(); // 重绘
});
findViewById(R.id.start_button).setOnClickListener(v -> animator.start());
}
}
class CustomView extends View {
private Paint paint;
private float rotationAngle = 0f;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
}
public void setRotationAngle(float angle) {
rotationAngle = angle;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 使用 Shader 实现渐变
LinearGradient gradient = new LinearGradient(0, 0, 100, 100, Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
paint.setShader(gradient);
canvas.rotate(rotationAngle, getWidth() / 2f, getHeight() / 2f);
canvas.drawRect(50, 50, 150, 150, paint);
}
}
- 布局 XML(res/layout/activity_canvas_animation.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.example.CustomView
android:id="@+id/custom_view"
android:layout_width="200dp"
android:layout_height="200dp" />
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Animation" />
</LinearLayout>
- 说明:
ValueAnimator
驱动rotationAngle
变化。Canvas.rotate
应用旋转,结合Paint
的Shader
实现渐变矩形。invalidate()
触发重绘。
注意事项
- 性能:复杂动画或频繁重绘可能影响性能,建议优化动画范围或使用 ViewPropertyAnimator。
- 兼容性:属性动画需 API 11+,低版本可用
NineOldAndroids
(已废弃,建议迁移到 Compose)。 - 属性支持:
ObjectAnimator
要求属性有 getter/setter 方法,自定义对象需提供公开 setter。 - 生命周期:在 Activity/Fragment 暂停时(如
onPause
)调用cancel()
释放资源。 - 调试:使用
AnimatorListener
监控状态,Log 输出值变化或使用 Visual Inspector 检查效果。
扩展:Jetpack Compose 中的等效实现
Compose 使用声明式动画,高级功能通过 updateTransition
或 Animatable
实现:
- 示例(多属性 + 自定义插值器):
import androidx.compose.animation.core.*
import androidx.compose.foundation.Image
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@Composable
fun AdvancedAnimation() {
var enabled by remember { mutableStateOf(false) }
val transition = updateTransition(targetState = enabled, label = "Animation")
val alpha by transition.animateFloat(
transitionSpec = { tween(durationMillis = 1000, easing = FastOutSlowInEasing) },
label = "alpha"
) { if (it) 1f else 0f }
val translationX by transition.animateFloat(
transitionSpec = { tween(durationMillis = 1000, easing = FastOutSlowInEasing) },
label = "translationX"
) { if (it) 200f else 0f }
val rotation by transition.animateFloat(
transitionSpec = { tween(durationMillis = 1000, easing = FastOutSlowInEasing) },
label = "rotation"
) { if (it) 360f else 0f }
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "Advanced Animation",
modifier = Modifier
.size(100.dp)
.graphicsLayer(alpha = alpha, translationX = translationX, rotationZ = rotation)
.clickable { enabled = !enabled }
)
}
- 说明:
updateTransition
管理多个属性动画。tween
支持自定义 easing(如FastOutSlowInEasing
)。- Compose 内置支持关键帧(
keyframes
)和自定义插值器。
示例:复杂交互动画
一个按钮点击时的弹跳效果:
Button button = findViewById(R.id.start_button);
button.setOnClickListener(v -> {
AnimatorSet set = new AnimatorSet();
ObjectAnimator scaleUpX = ObjectAnimator.ofFloat(v, "scaleX", 1f, 1.2f);
ObjectAnimator scaleUpY = ObjectAnimator.ofFloat(v, "scaleY", 1f, 1.2f);
ObjectAnimator scaleDownX = ObjectAnimator.ofFloat(v, "scaleX", 1.2f, 1f);
ObjectAnimator scaleDownY = ObjectAnimator.ofFloat(v, "scaleY", 1.2f, 1f);
set.play(scaleUpX).with(scaleUpY).before(scaleDownX).with(scaleDownY);
set.setDuration(200);
set.setInterpolator(new OvershootInterpolator());
set.start();
});
如果需要更深入的内容(如复杂关键帧、自定义 TypeEvaluator 的高级用法、与 Lottie 对比、Canvas 复杂动画),或继续探讨 Android 动画合集的其他部分(如过渡动画、Activity 切换动画),请告诉我!