前言
Android 动画也是 Android 系统中一个很重要的模块, 在平时开发中, 为了做出炫酷的效果, 动画可以说是必不可少的; 本文将总结 Android 中与动画相关的部分, 文中部分内容整理自文末参考链接, 权作笔记~
需要声明的是文章不会详细通过源码去讲解各种动画的实现细节, 因为相对来说, 动画的熟练使用更为重要, 所以本文只是提一下关键的动画源码部分
正文
一. 概述
Android 中动画分为三大类: View 动画, Transition (过渡动画), 属性动画; 下文也将从这三个方面进行总结和讲解
动画的本质实际上就是将作用对象的属性值在一段时间内缓慢的改变, 将每一个小的时间片段对应的属性值改变作用到对象并进行不断重绘, 造成肉眼看起来的的动画效果~
二. View动画
2.1 基本使用总结
View 动画分为四种, 如下表:
| 名称 | 标签 | 子类 | 效果 |
|---|---|---|---|
| 平移动画 | <translate> | TranslateAnimation | 移动 |
| 缩放动画 | <scale> | ScaleAnimation | 缩放 |
| 旋转动画 | <rotate> | RotateAnimation | 旋转 |
| 透明度 | <alpha> | AlphaAnimation | 透明度 |
注: 动画中还有一种叫 帧动画 , 这里也归为 View 动画中, 后文单独讲解
View 动画可以使用 xml 描述, 也可以使用代码描述(即使用上表中的四个子类); 使用 xml 描述的语法格式如下:
注: 位置为 res/anim/filename.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator="[true | false]"
android:fillAfter="[true | false]"
android:duration="int"
android:repeatMode="[reverse | restart]">
<alpha
android:fromAlpha="float"
android:toAlpha="float" />
<scale
android:fromXScale="float"
android:toXScale="float"
android:fromYScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float"/>
<translate
android:fromXDelta="float"
android:toXDelta="float"
android:fromYDelta="float"
android:toYDelta="float"/>
<rotate
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float"/>
<set>
...
</set>
</set>
解释:
-
<set> 代表 AnimationSet , 可以包含若干动画
-
android:shareInterpolator : 表示集合中的动画是否和集合共享同一个插值器; 如果集合不指定插值器, 那么子动画需要单独指定或者使用默认插值器
-
android:pivotX : 轴点 x 坐标
-
android:pivotY : 轴点 y 坐标
-
android:fillAfter : 动画结束之后, View 是否停留在结束位置, true 表示停留在结束位置, false 表示不停留
在代码中使用 View 动画:
Button button = findViewById(R.id.button);
Animatoin animation = AnimationUtils.loadAnimation(this, R.anim.animation_item);
button.startAnimation(animation);
2.2 自定义View动画
TranslateAnimation , ScaleAnimation , RotateAnimation , 和 AlphaAnimation 都继承自 Animation ; 如果要自定义 View 动画的话, 也需要继承 Animation , 并重写 Animation.initialize() 和 Animation.applyTransformation() 方法; initialize() 顾名思义就是进行一些初始化工作, 比如设置属性的初始值等, applyTransformation() 就是根据时间的流失量来计算出当前时间片段所对应的属性值, 并设置到对应的作用对象(对于 View 动画来说该对象就是 View ); 这里的设置往往是通过 Matrix 来作用的, 如下以 TranslateAnimation.applyTransformation() 为例
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime); // 通过时间流失量计算出x的改变量
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime); // 通过时间流失量计算出y的改变量
}
t.getMatrix().setTranslate(dx, dy); // 通过Matrix作用到对象
}
上述代码其实也可以当做自定义 View 动画的模板代码, 自定义 View 动画时, 可以使用 Camera 来配合计算改变量; 关于 Camera 和 Matrix 的使用, 可以参见 博客
2.3 帧动画
帧动画就是顺序播放一组预先定义好的图片, 系统提供了 AnimationDrawable 来使用帧动画
xml 定义如下:
注: 位置为 /res/drawable/filename.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/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
</animation-list>
使用帧动画时, 当图片较多或者较大时可能引起 OOM
三. Transition(过渡动画)
过渡动画其实是用于控制 ViewGroup 的 Item 的出场效果, 或者 Activity 之间的切换效果等
3.1 LayoutAnimation
xml 实现格式如下:
注: 位置为 /res/anim/anim_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="[normal | reverse | random]"
android:animation="@anim/anim_item"/>
解释:
-
android:delay : 子元素延迟多少时间执行动画; 比如子元素入场动画时间周期为 300ms , 那么 0.5 表示每个每个子元素都需要延迟 150ms 才能播放入场动画, 总体来说, 第一个子元素延迟 150ms 开始播放动画, 第二个子元素延迟 300ms 开始播放动画, 以此类推
-
android:animationOrder : normal 表示顺序显示, 即排在前面的子元素先开始动画; reverse 表示逆向显示; random 表示随机显示
当定义好 layoutAnimation 之后, 在布局文件中就可以通过 ViewGroup 的属性 android:layoutAnimation="@anim/anim_layout" 来指定入场动画了~
代码实现, 格式如下: 即通过 LayoutAnimationController 实现
ListView listView = findViewById(R.id.list);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);
3.2 Activity的切换效果
- 当启动一个 Activity 的时候, 可以按照如下方式为其添加自定义的切换效果:
Intent intent = new Intent(this, AnimActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
- 当 Activity 退出时, 可以如下添加切换效果:
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}
可以看出, 都是使用 overridePendingTransition() 来指定切换动画, 同时需要注意的是这个方法必须在 startActivity(intent) 或者 finish() 之后被调用才能生效
- 为 Fragment 添加切换动画: 可以通过 FragmentTransaction.setCustomAnimations() 来添加切换动画; 需要注意的是该动画必须是 View 动画, 不能用属性动画(因为属性动画在 API 11 引入, Fragment 也是 API 11 才引入的)
四. 属性动画
属性动画是在 Android 3.0 ( API 11 )开始引入的; 属性动画可以用 xml 实现, 也可以用代码实现, 但是一般都是用代码实现
4.1 基本使用
4.1.1 ViewPropertyAnimator
操作 View 属性值, 可以通过 View.animate() 来获取一个 ViewPropertyAnimator 对象, 然后就可以通过 ViewPropertyAnimator 来操作该对象的属性值了~ 可以操作的属性值参见下图(图片来源, 参见文末参考链接):
使用示例如 View.animate().setDuration(500).alpha(0.5);
4.1.2 ObjectAnimator
也是针对 View 的特定属性, 同时还要求该属性提供了对应的 set 和 get 方法, 基本使用如下; 因为 ObjectAnimator 是通过属性的 set 方法来不断改变属性值的, 所以 set 方法是一定需要的, 至于 get 方法只是用于获取动画开始的初始值的, 如果明确指定了初始值的话, 也可以提供 get 方法(如果没有提供 get 方法, 同时又没有指定初始值的话, 将 Crash ; 如果没有 set 方法, 不会 Crash , 只是没有效果而已)
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0, 65);
animator.start();
如果一个属性没有 set 方法的话, 解决方法有以下三种:
-
如果有权限的话, 自己给对象加上 set 和 get 方法
-
用一个类来包装原始对象, 间接为其提供 set 和 get 方法
-
采用 ValueAnimator , 监听动画过程, 自己实现属性改变
4.2 插值器
所谓插值器就是属性改变的速度, 系统提供了如下插值器:
| AccelerateDecelerateInterpolator | LinearInterpolator | AccelerateInterpolator |
| DecelerateInterpolator | AnticipateInterpolator | OvershootInterpolator |
| AnticipateOvershootInterpolator | BounceInterpolator | CycleInterpolator |
| PathInterpolator | ||
| FastOutLinearInInterpolator | FastOutSlowInInterpolator | LinearOutSlowInInterpolator |
其中 FastOutLinearInInterpolator , FastOutSlowInInterpolator , LinearOutSlowInInterpolator 是 Android 5.0(API 21) 引入的三个新的 Interpolator 模型, 并把它们加入了 support v4 包中
关于各种插值器的讲解, 可以参见 博客 , 该博客讲的比较详细, 此处不再赘述~
4.3 估值器
估值器即 TypeEvaluator , 作用是根据当前属性改变的百分比来计算改变后的属性值, 用于协助插值器实现非线性运动; 系统提供了如下估值器:
| IntEvaluator | IntArrayEvaluator | FloatEvaluator |
| FloatArrayEvaluator | ArgbEvaluator | PointFEvaluator |
| RectEvaluator |
如果要对其他类型做动画(非 int , float , Color ), 那么需要自定义类型估值算法, 即继承 TypeEvaluator 自己实现其 evaluate() 方法即可
估值器基本使用如下:
ObjectAnimator anim = ObjectAnimator.ofObject(view, "alpha", new FloatEvaluator(), 0, 1);
4.4 监听器
ViewPropertyAnimator 和 ObjectAnimator 设置监听器的方法如下表:
| ViewPropertyAnimator | setListener() | setUpdateListener() | withStartAction() | withEndAction() |
| ObjectAnimator | addListener() | addUpdateListener() | addPauseListener() |
注:
-
ViewPropertyAnimator 可以通过 setListener() 和 setUpdateListener() 来设置监听器, 移除监听器可以通过 setListener(null) 和 setUpdateListener(null) 来移除; ViewPropertyAnimator 独有的 withStartAction() 和 withEndAction() 方法, 可以设置 一次性 (动画结束后就自动弃掉了, 即一次有效)的动画开始或结束的监听
-
ObjectAnimator 则是用 addListener() 和 addUpdateListener() 来添加一个或多个监听器, 移除监听器则是通过 removeListener() 和 removeUpdateListener() 来指定移除对象; ObjectAnimator 支持使用 pause() 方法暂停, 所以它还多了一个 addPauseListener() 和 removePauseListener() 的支持
-
ViewPropertyAnimator.withStartAction/EndAction() 是 ViewPropertyAnimator 的独有方法, 它们和 set/addListener() 中回调的 onAnimationStart() 和 onAnimationEnd() 相比起来的不同主要有两点:
- withStartAction() 和 withEndAction() 是一次性的, 在动画执行结束后就自动弃掉了, 就算之后再重用 ViewPropertyAnimator 来做别的动画, 用它们设置的回调也不会再被调用; 而 set/addListener() 所设置的 AnimatorListener 是持续有效的, 当动画重复执行时, 回调总会被调用
- withEndAction() 设置的回调只有在动画正常结束时才会被调用, 而在动画被取消时不会被执行; 这点和 AnimatorListener.onAnimationEnd() 的行为是不一致的
监听器方法有:
| AnimatorListener | onAnimationStart() | onAnimationEnd() | onAnimationCancel() | onAnimationRepeat() |
注: 即使动画通过 cancle() 方法取消, onAnimationEnd() 也会被调用; 所以当动画被取消时, 如果设置了 AnimatorListener , 那么 onAnimationCancel() 和 onAnimationEnd() 都会被调用; onAnimationCancel() 会先于 onAnimationEnd() 被调用; 由于 ViewPropertyAnimator 不支持重复, 所以这个方法对 ViewPropertyAnimator 相当于无效
五. 动画注意事项
-
避免使用帧动画, 易造成 OOM
-
动画需要考虑暂停和取消; 属性动画中有一类无线循环的动画, 这类动画在 Activity 退出时要及时停止, 否则将导致 Activity 无法释放从而造成内存泄露; 通过验证后发现 View 动画无此问题
-
使用 View 动画之后可能会出现 View 无法隐藏的现象, 即 setVisibility(View.GONE) 失效了, 此时可以使用 view.clearAnimation() 来清除 View 动画即可
-
Android 3.0 以前系统, 不管是 View 动画还是属性动画, 都只是作用于 View 内容(因为 Android 3.0 以前, 属性动画底层其实也是通过 View 动画实现的), 新位置无法触发单击事件; 从 3.0 开始, 属性动画的单击事件触发位置为移动后的位置, 但是 View 动画仍然在原位置
六. 参考
-
«Android开发艺术探索»