Android动画

Drawable动画、View动画、属性动画,以及由属性动画封装出的Transition、Content Trasition、Shared Element Transition

Posted by candy1126xx on April 21, 2017

三种基本动画

  • Drawable Animation:指定每一帧的图片和播放时间,有序的进行播放而形成动画效果。
  • View Animation:当View回溯到DecorView被标记为PFLAG_INVALIDATED,以致其将被重绘时,在View.draw()中,将动画某一帧的计算结果赋值给RenderNode/Canvas,从而改变绘制结果,连贯起来就形成了动画。
  • Property Animation:在每次“Choreographer刷新“中,用差值器和估值器由时间比率计算出值,改变View的某项属性,从而形成动画效果。

差值器

差值器的作用是定义动画因子与时间比率(已过时长/总时长)的关系,根据时间比率算出动画因子供估值器使用。接口为TimeInterpolator。默认实现有:

  • AccelerateDecelerateInterpolator:其止时变化较慢,而中间变化较快。
  • AccelerateInterpolator:从慢到快。
  • AnticipateInterpolator:
  • AnticipateOvershootInterpolator:
  • BounceInterpolator:
  • CycleInterpolator:
  • DecelerateInterpolator:
  • LinearInterpolator:
  • OvershootInterpolator:

估值器

估值器接收动画因子、起止值,计算出当前值,供改变属性。接口为TypeEvaluator。默认实现有:

  • IntEvaluator:
  • FloatEvaluator:
  • ArgbEvaluator:

属性动画

重要的类

  • KeyFrame:描述动画的某一帧的“时间/值”。mFraction:某一帧的动画因子。mValue:某一帧的值。
  • mInterpolator:
  • KeyFrameSet:一组KeyFrame。
  • mEvaluator:计算关键帧之间帧的值所用的估值器。
  • mInterpolator:只用于2关键帧帧的情况。
  • PropertyValuesHolder:封装某属性及对应的KeyFrameSet。
  • mEvaluator:计算关键帧之间帧的值所用的估值器,与KeyFrameSet中的是同一对象。
  • ValueAnimator:只提供差值,需要手动修改对象的属性。
  • mValues(PropertyValuesHolder[]):该动画中要改变的所有属性的PropertyValuesHolder。
  • mValuesMap(HaskMap<String, PropertyValuesHolder>):该动画中要改变的所有属性及其对应的PropertyValuesHolder。
  • mInterpolator:
  • mPlayingState:
  • mPaused:
  • mResumed:
  • AnimationHandler:不是真正的Handler。它递归地调用Choreographer.postCallback()向Choreographer中CALLBACK_ANIMATION队列插入Runnable。它被放入ThreadLocal以避免竞态条件(线程单例)。在执行过程中多次使用“集合克隆法”,以防止执行过程中增减集合元素造成的错误。
  • mPendingAnimations:保存所有请求执行的动画。
  • mAnimations:保存所有正在执行的动画。
  • mDelayAnims:保存所有延迟执行的动画。
  • mReadAnims:保存所有延迟执行的、已到执行时间的动画。
  • mTmpAnimations:
  • mEndAnims:

可见,KeyFrame、KeyFrameSet、ValueAnimator中都有差值器的引用。其中,KeyFrame、KeyFrameSet是同一个差值器对象,而ValueAnimator中是不同的。1帧属性动画要使用2个差值器,1个估值器。

ValueAnimator

创建动画

ValueAnimator.onInt()/onFloat()…

根据传入的参数创建至少2个(传入参数个)KeyFrame。第一个KeyFrame的mFraction为0,mValue为第一个参数;之后第i个KeyFrame的mFraction为i/传入参数数量,mValue为第i个参数。因此这些KeyFrame是均匀分布在时间轴上的。然后以这组KeyFrame为参数构建KeyFrameSet。再以KeyFrameSet为参数构建PropertyValuesHolder。再以PropertyValuesHolder为元素填充ValueAnimator.mValues及ValueAnimator.mValuesMap。

执行动画

ValueAnimator.start()

  1. 检查调用线程是否是Looper的;不是则抛出错误。
  2. 创建AnimationHandler,并把ValueAnimator对象加入AnimationHandler.mPendingAnimations,并调用AnimationHandler.start()。
  3. 在AnimationHandler.start()中,调用Choreographer.postCallback()向Choreographer中CALLBACK_ANIMATION队列插入Runnable。
  4. 当“Choreographer刷新“发生时,就会执行Runnable执行体。在执行体中,
  5. 遍历AnimationHandler.mPendingAnimations,
    • 对立即执行的动画:调用ValueAnimator.startAnimation()。
    • 对延迟执行的动画:加入AnimationHandler.mDelayAnims。
  6. 遍历AnimationHandler.mDelayAnims,把所有到时间了该执行的动画加入AnimationHandler.mReadAnims。
  7. 遍历AnimationHandler.mReadAnims,把其中的动画从AnimationHandler.mDelayAnims中删除,并把AnimationHandler.mAnimations中的元素都加入AnimationHandler.mTmpAnimations,并执行每个元素的ValueAnimator.doAnimationFrame()。
  8. 如果ValueAnimator.doAnimationFrame()返回true,说明这个动画执行完了,把它加入AnimationHandler.mEndAnims。
  9. 清空AnimationHandler.mTmpAnimations。
  10. 遍历AnimationHandler.mEndAnims,调用每个元素的ValueAnimator.endAnimation()。
  11. 清空AnimationHandler.mEndAnims。
  12. 调用Choreographer.postCallback()向Choreographer中CALLBACK_COMMIT队列插入Runnable,这个Runnable的作用是使AnimationHandler.mAnimations中的动画的开始时间与“Choreographer刷新”对齐。
  13. 如果此时AnimationHandler.mAnimations或AnimationHandler.mDelayAnims不为空,即还有动画需要执行,就再次调用Choreographer.postCallback()向Choreographer中CALLBACK_ANIMATION队列插入Runnable。这与3形成递归。

由以上知,ValueAnimator执行动画的关键在于3个方法:

startAnimation():遍历ValueAnimator.mValues,调用每个PropertyValuesHolder的init()。在init()中,创建估值器。然后把ValueAnimator对象加入AnimationHandler.mAnimations。

doAnimationFrame():先处理Pause/Resume/Stop状态,需要将时间对齐。然后计算时间比率。

  • 时间比率 >= 1:处理“超时”/“反弹”等,转化为< 1的情况。
  • 时间比率 <= 1:根据时间比率、由差值器计算出动画因子,再由估值器计算出值。
  • 只有2个关键帧(准备阶段只传入了起止值):
  • 时间比率 <= 0:
  • 时间比率 == 1:
  • 0 < 时间比率 < 1:

endAnimation():从集合中删除该元素。动画状态改为STOPPED。

另外,注意到Pause/Stop的动画,并不是删除了CALLBACK_ANIMATION队列中的Runnable,而是在doAnimationFrame()中不作出反应。

ObjectAnimator

ObjectAnimator继承自ViewAnimator,具有其所有特性,并添加了自动修改属性值的功能。

  • 在创建阶段会把属性的Getter方法保存在mGetter(Method)、Setter方法保存在mSetter(Method)。
  • 在执行阶段会用Setter方法为属性赋值。

View动画

重要的类

  • Transformation:定义动画某一帧的变形信息。mMatrix(Matrix):变形矩阵。
  • mAlpha:透明度。
  • mTransformationType:变化类型。有4个值:TYPE_IDENTITY:没有变化。TYPE_ALPHA:透明度变化。TYPE_MATRIX:缩放、位移变化。TYPE_BOTH:透明度、缩放、位移变化。
  • mClipRect:
  • Animation:
  • mPreviousRegion:保存相对于该View左上角的原本的四边位置。
  • mListenerHandler:
  • mOnStart/mOnRepeat/mOnEnd:执行动画监听回调的Runnable。
  • mInterpolator:
  • View.mCurrentAnimation:绑定到该View的动画。
  • ViewGroup.mChildTransformation:供子View保存某帧动画的计算结果。所有子View共用1个,以节省空间。

创建动画

  1. 调用View.setAnimation()为View.mCurrentAnimation赋值。

执行动画

View.startAnimation()

  1. 把从该View回溯到DecorView标记为PFLAG_INVALIDATED;
  2. 在View绘制过程(onDraw())中:
  3. 如果View.mCurrentAnimation不为空,则调用View.applyLegacyAnimation()。在View.applyLegacyAnimation()中:
  4. 如果没有调用过Animation.initialized(),则调用Animation.initialized()重置所有参数,再调用Animation.initializeInvalidateRegion()记录下相对于该View左上角的原本的四边位置,再调用Animation.setListenerHandler(),把mAttatchInfo.mHandler赋值给Animation.mListenerHandler,mAttatchInfo.mHandler是向UI线程发信息的,所以动画监听回调都是在UI线程执行的。
  5. 由开始时间、动画时长、当前时间计算时间比率,再由差值器计算出动画因子,调用Animation.applyTransformation()。Animation子类重写applyTransformation(),以实现自己的动画逻辑。最终计算结果记录在ViewGroup.mChildTransformation。
  6. 调用软/硬件绘制方法用计算结果改变绘制结果。

View动画不影响触摸事件

一个按钮用View动画移动一段距离,要点击原位才有反应。因为View动画只改变绘制结果,不改变View的位置(mTop等),而触摸事件分发机制中,判断事件是否落到了该View上,使用的是View的位置。

Transition

重要的类

  • Scene:封装了控件树的一个子树,表示需要执行动画的部分。
  • mSceneRoot:执行动画的子树的根节点。
  • mLayoutId:layout文件的ID,在入场时将用它替换mSceneRoot的子节点。
  • mLayout:一个View树,在入场时将用它替换mSceneRoot的子节点。
  • mEnterAction/mExitAction(Runnable):定义入场/出场时,除动画外的其它动作。
  • Transition:比较两个Scene中相同控件的属性的不同,自动生成属性动画。
  • mSceneRoot:场景。
  • captureValues():计算动画参数。
  • playTransition():创建并执行属性动画。
  • TransitionManager:
  • mSceneTransition:保存“一个场景进出”的动画。
  • mScenePairTransition:保存“两个场景切换”的动画。
  • sRunningTransition:保存所有正在执行的场景动画及其对应的子树。线程单例。
  • mPendingTransition:保存所有正在执行的场景动画的子树。
  • AnimationInfo:
  • TransitionValues:

封装Scene

创建动画并执行

在TransitionManager.changeScene()中:

  1. 把Scene.mSceneRoot加入TransitionManager.mPendingTransition。
  2. 暂停所有Scene.mSceneRoot对应的动画。
  3. 执行Scene.mEnterAction。
  4. 为Scene.mSceneRoot添加ViewTreeObserver.OnPreDrawListener,在onPreDraw()回调中:
  5. 从TransitionManager.mPendingTransition中删除Scene.mSceneRoot。
  6. 把Transition对象加入TransitionManager.sRunningTransition。
  7. 为Transition对象添加监听:当动画执行完毕,从TransitionManager.sRunningTransition中删除Transition对象。
  8. 调用Transition.captureValues()。
  9. 恢复所有Scene.mSceneRoot对应的动画。
  10. 调用Transition.playTransition()。

定义Activity转场动画

定义主题的窗口动画

Activity.overridePendingTransition()

Content Transition

分为2步。先把A界面封装为StartScene1。然后所有控件的可见性设为INVISIBLE,并封装为EndScene1。用Transition1比较StartScene1和EndScene1的不同创建并执行属性动画。再把B界面所有控件的可见性设为INVISIBLE,封装为StartScene2。然后所有控件的可见性设为VISIBLE,并封装为EndScene2。用Transition2比较StartScene2和EndScene2的不同创建并执行属性动画。

getWindow().setExitTransition(); // 界面1跳转到界面2时,界面1的效果
getWindow().setEnterTransition(); // 界面1跳转到界面2时,界面2的效果
getWindow().setReturnTransition(); // 界面2跳回到界面1时,界面2的效果
getWindow().setReenterTransition(); // 界面2跳转到界面1时,界面1的效果
ActivityOptionsCompat.makeSceneTransitionAnimation(activity).toBundle(); // 把它作为startActivity()的第二个参数
Transition.excludeTarget(); // 指定某个控件不一起执行动画

Shared Element Transition

共享元素动画。可以和Content Transition一起使用。无法为每个共享元素单独设置动画效果。

getWindow().setSharedElementEnterTransition(); // 共享元素进入界面2时的动画
getWindow().setSharedElementReturnTransition(); // 共享元素返回界面1时的动画
ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pair...);
// 其中,pair是个数可变的Pair对象。Pair是可序列化的HashMap,以View为键,以String为值。
// 这里的String值,必须与界面2的对应的共享元素的transitionName一致。