ExpectAnim 开源项目使用教程:声明式动画编程新范式
还在为 Android 动画的复杂实现而头疼吗?ExpectAnim 提供了一个革命性的解决方案——通过声明式语法描述动画期望,让复杂的多视图动画变得简单直观。本文将带你全面掌握这个强大的动画库。
什么是 ExpectAnim?
ExpectAnim 是一个创新的 Android 动画库,采用声明式编程范式(Declarative Programming Paradigm)。与传统命令式动画不同,你只需描述视图的最终状态期望,库会自动计算并执行所有中间动画步骤。
核心优势
- 声明式语法:描述"期望什么"而非"如何实现"
- 多视图协同:自动处理视图间的依赖关系
- 流畅性能:基于 Android 原生动画系统
- 丰富特性:支持位置、缩放、旋转、透明度等全方位动画
快速开始
添加依赖
在项目的 build.gradle 中添加依赖:
dependencies {
implementation 'com.github.florent37:expectanim:1.0.8'
}
基础使用示例
new ExpectAnim()
.expect(avatarView)
.toBe(
bottomOfParent().withMarginDp(16),
leftOfParent().withMarginDp(16),
width(40).toDp().keepRatio()
)
.toAnimation()
.setDuration(1500)
.start();
核心概念详解
1. 动画期望(Expectations)
ExpectAnim 提供了丰富的期望类型,可以分为以下几类:
位置相关期望
// 相对其他视图
toRightOf(otherView).withMarginDp(16)
toLeftOf(otherView)
aboveOf(otherView)
belowOf(otherView)
// 相对父容器
topOfParent().withMarginDp(20)
bottomOfParent()
leftOfParent()
rightOfParent()
// 居中相关
centerInParent(true, true) // 水平和垂直居中
centerHorizontalInParent()
centerVerticalInParent()
sameCenterAs(otherView, true, false) // 水平中心对齐
// 屏幕外
outOfScreen(Gravity.BOTTOM) // 移出屏幕底部
变换相关期望
// 缩放
scale(0.5f, 0.5f) // 缩放50%
width(100).toDp() // 设置宽度
height(200) // 设置高度
sameScaleAs(otherView)
sameWidthAs(otherView)
// 旋转
rotated(45f) // 旋转45度
flippedHorizontally() // 水平翻转
flippedVertically() // 垂直翻转
withCameraDistance(1000f) // 3D透视效果
// 透明度
alpha(0.5f) // 半透明
visible() // 完全显示
invisible() // 完全隐藏
自定义属性期望
toHaveTextColor(Color.RED) // 文字颜色动画
toHaveBackgroundAlpha(0.5f) // 背景透明度
2. 链式调用与多视图动画
ExpectAnim 的强大之处在于可以同时处理多个视图的复杂动画:
new ExpectAnim()
.expect(avatar)
.toBe(
bottomOfParent().withMarginDp(36),
leftOfParent().withMarginDp(16),
width(40).toDp().keepRatio()
)
.expect(username)
.toBe(
toRightOf(avatar).withMarginDp(16),
sameCenterVerticalAs(avatar),
toHaveTextColor(Color.WHITE)
)
.expect(button)
.toBe(
rightOfParent().withMarginDp(20),
bottomOfParent().withMarginDp(12),
toHaveBackgroundAlpha(0f)
)
.toAnimation()
.setDuration(1500)
.start();
高级特性
1. 滚动联动动画
ExpectAnim 完美支持与 ScrollView 的联动,创建流畅的视差效果:
private ExpectAnim expectAnimMove;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroll);
expectAnimMove = new ExpectAnim()
.expect(headerImage)
.toBe(
scale(0.7f, 0.7f),
topOfParent().withMarginDp(10)
)
.expect(title)
.toBe(
alpha(0.8f),
sameCenterVerticalAs(headerImage)
)
.toAnimation();
scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
int oldScrollX, int oldScrollY) {
final float percent = (scrollY * 1f) / v.getMaxScrollAmount();
expectAnimMove.setPercent(percent);
}
});
}
2. 动画序列(Concat)
使用 ExpectAnim.concat() 创建连续的动画序列:
ExpectAnim.concat(
new ExpectAnim()
.expect(view1)
.toBe(
withCameraDistance(500f),
flippedHorizontally()
)
.toAnimation()
.setDuration(1000),
new ExpectAnim()
.expect(view2)
.toBe(
withCameraDistance(1000f),
flippedVertically()
)
.toAnimation()
.setDuration(500)
).start();
3. 即时应用与重置
// 立即应用动画最终状态(无动画效果)
new ExpectAnim()
.expect(view)
.toBe(outOfScreen(Gravity.BOTTOM))
.toAnimation()
.setNow();
// 重置到初始状态
expectAnim.reset();
实战案例
案例1:用户资料卡片动画
public class ProfileActivity extends AppCompatActivity {
@BindView(R.id.avatar) View avatar;
@BindView(R.id.name) View name;
@BindView(R.id.bio) View bio;
@BindView(R.id.follow_btn) View followBtn;
private ExpectAnim profileAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
ButterKnife.bind(this);
// 初始化状态
new ExpectAnim()
.expect(avatar).toBe(scale(0.8f, 0.8f), alpha(0f))
.expect(name).toBe(alpha(0f), translatedX(100f))
.expect(bio).toBe(alpha(0f), translatedY(50f))
.expect(followBtn).toBe(alpha(0f), scale(0.5f, 0.5f))
.toAnimation()
.setNow();
profileAnim = new ExpectAnim()
.expect(avatar)
.toBe(
atItsOriginalScale(),
alpha(1f),
centerHorizontalInParent()
)
.expect(name)
.toBe(
atItsOriginalPosition(),
alpha(1f),
belowOf(avatar).withMarginDp(16)
)
.expect(bio)
.toBe(
atItsOriginalPosition(),
alpha(1f),
belowOf(name).withMarginDp(8)
)
.expect(followBtn)
.toBe(
atItsOriginalScale(),
alpha(1f),
belowOf(bio).withMarginDp(24),
centerHorizontalInParent()
)
.toAnimation()
.setDuration(1200)
.setInterpolator(new OvershootInterpolator());
}
@OnClick(R.id.show_profile)
public void showProfile() {
profileAnim.start();
}
}
案例2:购物车添加动画
public class ProductActivity extends AppCompatActivity {
@BindView(R.id.product_image) View productImage;
@BindView(R.id.cart_icon) View cartIcon;
public void onAddToCartClicked() {
// 创建商品飞到购物车的动画
new ExpectAnim()
.expect(productImage)
.toBe(
sameCenterAs(cartIcon, true, true),
scale(0.3f, 0.3f),
alpha(0f)
)
.toAnimation()
.setDuration(800)
.addEndListener(new AnimationEndListener() {
@Override
public void onAnimationEnd(ExpectAnim expectAnim) {
// 动画结束后恢复原状
productImage.setAlpha(1f);
productImage.setScaleX(1f);
productImage.setScaleY(1f);
productImage.setTranslationX(0);
productImage.setTranslationY(0);
// 显示添加成功提示
showAddSuccess();
}
})
.start();
}
}
性能优化建议
1. 避免过度使用
// 不推荐:每个小变化都创建新动画
view.setOnClickListener(v -> {
new ExpectAnim().expect(v).toBe(scale(1.1f, 1.1f)).toAnimation().start();
});
// 推荐:复用动画实例
private ExpectAnim scaleAnim;
void initAnimations() {
scaleAnim = new ExpectAnim()
.expect(button)
.toBe(scale(1.1f, 1.1f))
.toAnimation()
.setDuration(200);
}
void onButtonClick() {
scaleAnim.reset();
scaleAnim.start();
}
2. 合理设置持续时间
// 根据动画复杂度设置合适的持续时间
new ExpectAnim()
.expect(view1).toBe(moveToPosition())
.expect(view2).toBe(scaleChange())
.expect(view3).toBe(rotation())
.toAnimation()
.setDuration(800) // 复杂动画适当延长
.start();
3. 使用合适的插值器
new ExpectAnim()
.expect(view)
.toBe(/* expectations */)
.toAnimation()
.setInterpolator(new AccelerateDecelerateInterpolator()) // 平滑加速减速
.setDuration(600)
.start();
常见问题解答
Q1: 如何处理视图依赖关系?
ExpectAnim 自动处理视图间的依赖关系,你只需按逻辑顺序声明期望即可。
Q2: 支持自定义属性动画吗?
是的,可以通过扩展 CustomAnimExpectation 类来实现自定义属性动画。
Q3: 如何监听动画状态?
expectAnim.addStartListener(expectAnim -> {
// 动画开始
});
expectAnim.addEndListener(expectAnim -> {
// 动画结束
});
Q4: 支持 Proguard 混淆吗?
在 proguard-rules.pro 中添加:
-keep class com.github.florent37.expectanim.*{ *; }
-dontwarn com.github.florent37.expectanim.**
总结
ExpectAnim 通过声明式编程范式彻底改变了 Android 动画的开发方式:
- 极简语法:用描述代替命令,代码更清晰
- 强大功能:支持复杂的多视图协同动画
- 优秀性能:基于原生动画系统,流畅稳定
- 丰富生态:提供全面的期望类型和扩展能力
无论你是要创建简单的视图过渡,还是复杂的交互动画,ExpectAnim 都能提供优雅的解决方案。开始使用这个强大的库,让你的应用动画变得更加出色!
提示:在实际项目中,建议先从简单的动画开始,逐步掌握复杂的多视图动画技巧。合理规划动画逻辑,避免过度动画影响用户体验。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



