前言
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()
为例
@Overrideprotected 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高级架构
链接:https://www.jianshu.com/p/d951aa9ae073