属性动画(android.animation)
插值器(Interpolator)
Interpolator 是可以使用 XML 来定义和解析的,它的作用是根据输入产生一个 [0, 1]之间的值
1 | public interface TimeInterpolator { |
Interpolator 有各种子类,最简单的是线性的 LinearInterpolator ,各种子类的公式如下
- $LinearInterpolator : a(t) = t $
- $AccelerateInterpolator : a (t) = t*t $
- $DecelerateInterpolator : a (t) =1 - (1-t)*(1-t) $
- $AccelerateDecelerateInterpolator : a (t) =\frac{cos((t+1)*\pi }{2} + 0.5 $
- $AnticipateInterpolator : a(t) = t t ((tension + 1) * t - tension)$ 越来越快
- $BounceInterpolator :$分段函数,呈弹跳效果
- $CycleInterpolator : a (t) = sin(2\picyclest) $ \将时间映射成正弦曲线,呈振动式效果,这一点实际非常有用,如果时间给500ms,那么就是半周期,数值复0,适合与View的animate方法联合使用。*
- $LookupTableInterpolator : $通过查表来产生结果值
插值器在属性动画中用于时间轴的变换。
估值器(TypeEvaluator)
TypeEvaluator 类用来根据起始值和时间坐标计算中间值。
1 | public interface TypeEvaluator<T> { |
典型的估值器实现如 IntEvaluator,RectEvaluator,ArgbEvaluator 等均是线性实现。
通过自定义估值器可以实现属性动画。例如自定义一个改变控件高度的属性动画
1 | //1.创建估值器,注意要改的属性是 LayoutParams.height。 |
使用估计器比添加 AnimatorUpdateListener 接口要简洁一些。
Property 与 PropertyValuesHolder
Property`类表示属性,所以它要表示键值对,对于键,必须是 String 类型,其值表示属性名;对于值,需要一个对象(Class:T) 来提供,数据类型为V,使用 get/set 方法可以从对象中读取和设置属性值。
1 | public abstract class Property<T, V> { |
以 View 中的”alpha”属性为例,View 对象提供 alpha 属性,其值的格式是 float。
1 | public static final Property<View, Float> ALPHA = new FloatProperty<View>("alpha") { |
PropertyValuesHolder 是对属性 Property 的包装,合成了Property ,属性名称以及属性值集合等,并通过反射(Method)来修改属性值。
1 | public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) { |
传入的多个属性值被转为 Keyframe 集合,且设置了属性名,值类型被确定为 int,通过 Property 对象可以应用属性值。只是缺少一个对象来提供和接受 int 类型的值。
- PropertyValuesHolder 还可以用 Object 对象来构造。
1 | public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, |
这里对象将被转换为 ObjectKeyframe,只不过属性值由Object对象提供。
PropertyValuesHolder 的重要职责是完成真正的步进计算
1 | void calculateValue(float fraction) { |
值动画(ValueAnimator)和对象动画(ObjectAnimator)
ValueAnimator 的主要域如下
1 | long mStartTime; |
ValueAnimator 在构造时即是通过 PropertyValuesHolder 来完成的。
1 | public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) { |
使用 ofInt 方法构造的 PropertyValuesHolder 属性名为空。对于 ARGB 颜色而言需要 Evaluator。
ObjectAnimator 比 ValueAnimator 就多了一个对象,可以使用反射来提供和接收数值。······················
1 | private WeakReference<Object> mTarget; |
当属性动画启动后的处理如下
1 | PropertyValuesHolder[] mValues; |
状态列表动画(StateListAnimator)
StateListAnimator 这种动画可以使得 View 在状态切换时启动属性动画,你可以只有使用
1 | <com.lxt.toast.text.FadeText android:stateListAnimator="@animator/test"/> |
也可以用代码
1 | setStateListAnimator(AnimatorInflater.loadStateListAnimator(getContext(), R.animator.test)); |
所用的 StateListAnimator 可以用XML来定义,标签是 selector。
1 | <selector xmlns:android="http://schemas.android.com/apk/res/android"> |
AnimatedVectorDrawableCompat
VectorDrawable 在XML解析时会将 path 字符串解析成 VFullPath 对象,包括 PathDataNode 数组和一些绘制信息,由此构建节点树,绘制时先将节点树绘制在缓存 Bitmap 上。
这里谈谈它的动画效果类 AnimatedVectorDrawableCompat,如果要兼容低版本,在XML中使用 app:srcCompat
来引用。
1.首先定义VectorDrawable,重点是给 path 标记 name
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
2.定义属性动画,注意 trimPathStart 和 trimPathEnd 这两个属性
1 |
|
3.合成动画Drawable
1 | <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_menu_camera"> <target android:name="star" //path 的 name android:animation="@animator/alpha"/> // 使用的属性动画 <target android:name="end" android:animation="@animator/alpha"/> |
最后调用动画
1 | (AnimatedVectorDrawableCompat) fab.getDrawable().start(); |
AnimatorSet
AnimatorSet 可以多个属性动画设置播放顺序,如顺序播放,并行播放以及延迟播放,其原理是形成这些动画之间的依赖关系。
例如顺序播放的实现如下
1 | public void playSequentially(Animator... items) { |
play 方法并不是播放,而是将 Animator 转为 Node 类,Node 定义了其父节点,兄弟节点和子节点集合,以便形成依赖关系
1 | private static class Node implements Cloneable { |
before 方法会在儿子节点集合中添加代表下一个动画的 Node 节点。
并行播放的实现如下
1 | public void playTogether(Animator... items) { |
with 方法会在兄弟节点集合中添加代表下一个动画的 Node 节点。
至于 after 方法自然就是添加到父节点集合中去了,此外 after 方法还能够设置延迟,这是通过插入一个时长为 delay 的空动画来实现的。
1 | public Builder after(long delay) { |
在构建有向图的时候会使用DFS算法初始化依赖树,使用Build的这几个方法能够随心所欲的构建起动画播放顺序。
显露动画(RevealAnimator)
ViewAnimationUtils 这个工具类可以用来创建显露动画,这是经过优化的一种动画,可以产生波纹效果。它是一种属性动画,其扩展应用很广泛,这个留在实践部分讲。创建方法如下
1 | public static Animator createCircularReveal(View view, |
注意波纹效果仅在View区域内。
补间动画(android.view.animation)
系统定义的 Animation 确实只有四种,但完全可以通过自定义来扩展。自定义 Animation 需要重写下列方法,如直接改写控件尺寸
1 |
|
系统定义的 Animation 如 AlphaAnimation 是通过 Transformation 类来完成变换的,不过其功能有限,这里通过引入 View 直接修改参数。
各种动画的参数值得注意,如 使用百分比。
布局动画(LayoutAnimationController)
布局动画为 ViewGroup 所独有,它能够使得各个View依次按照延迟播放动画,其构建方法如下
1 | public LayoutAnimationController(Animation animation, float delay); |
其参数在 View 的布局参数中,AnimationParameters
中包含动画的延迟,默认按照 View 的序号生成。
实际通过延迟发送也能达到这种效果。
1 | for (int i = 0; i < size; i++) { |
属性动画的常规使用套路
某些控件的绘制依赖于一个参数 process(float) 的变化,对其使用属性动画是一个常规套路,能取得什么效果主要取决于 开发者的想象力。AVLoadingIndicatorView和MkLoader库就是典型代表。
- 以 ClassicSpinner 的实现为例,基本效果是画8个圆,动画效果是分别播放一个属性动画,但设置延迟,造成透明度不同。
1 | ValueAnimator fadeAnimator = ValueAnimator.ofInt(126, 255, 126); |
这里将圆形抽象成Circle类,以便于使用动画。
FishSpinner
的绘制是5个圆,依次旋转一定角度。动画效果为360度的旋转,视觉效果即通过延迟来达到。
更复杂的使用:分段绘制
将几个 process 控制的绘制过程拼接起来,可以达到更炫的效果。GADownloading和JJSearchViewAnim是分段绘制的典型。
暴露动画的应用
CircularAnim 是一个扩展暴露动画的典型例子,要点在于计算中心点位置和始终半径。
注意中心点位置是相对于 view 的坐标位置,可以在view区域之外。这样如果要以另一个View B为动画的中心,就需要计算B的中心点相对与View左上点的位置,并要确保动画半径容纳原 View。
1 | int[] mCL = new int[2]; |
如果要实现全屏效果,可以在 DecorView 上添加一个 ImageView 来完成动画,ImageView 可以任意设置图片或颜色效果,要注意在动画结束后删除这个 ImageView 。
至于图中的效果,是通过隐藏一个ProgressBar来完成的,将TextView的收缩半径设置为ProgressBar的一半高度,动画完成后隐藏 TextView,显示 ProgressBar 即可。
更复杂的暴露动画效果:RippleLayout
暴露动画实际也是属性动画,将它和其它属性动画结合能产生一些视觉效果。
这里图片上的效果可以分成几部分
1.点击活动A的按钮,开始触发波纹效果,动画结束之后启动活动B
2.活动B的布局分为上下两层,分别执行TransitionY动画。
与之类似,点击返回键,将反向播放内容布局动画和波纹动画。
入屏动画
这是一个属性动画的封装库,能够流式的使用属性动画,但无法取消以前的动画,只能用作入屏动画。
1 | PropertyAction fabAction = PropertyAction.newPropertyAction(fab).scaleX(0).scaleY(0).duration(750).build(); |