动画体系知识梳理(1) 转场动画 ContentTransition 理论篇
一、概述
在Android 5.0
当中,Google
基于Android 4.4
中的Transition
框架引入了转场动画,设计转场动画的目的,在于让Activity
之间或者Fragment
之间的切换更加自然,其根本原因在于界面间切换时的动画不再是以Activity
或者Fragment
的整个布局作为切换时动画的执行单元,而是将动画的执行单元细分到了View
。目前提供的转场动画分为两种:
Content Transition
:用于两个界面之间非共享的View
。Shared Element Transition
:用于两个界面之间需要共享的View
。
二、什么是Transition
2.1 Transition
的基本概念
在学习Content Transition
之前,我们先对转场动画所依赖的Transition
框架做一个简要的介绍,这个框架是围绕着两个概念**Scene
(场景)和Transition
(变换)**来建立的,在后面我们会多次提到它:
- 场景(
Scene
):表示UI
所对应的状态,一般来说,会有两个场景:起点场景和终点场景,在这两个场景当中,UI
有可能会有不同的状态。在上图当中,SceneA
和SceneB
就是两个不同的场景,ViewA
在两个场景中对应的状态分别为VISIBLE
和INVISIBLE
。 - 变换(
Transition
):用来定义两个场景之间切换的规则,当场景发生发生变换时,Transition
需要做的有两点: - 确定
View
在起点场景和终点场景的状态。 - 创建
View
从终点场景切换到终点场景所需的Animator
。
2.2 Transition
的简单例子
下面,我们通过一个简单的例子,对上面的概念有一个直观的感受:
public class ExampleActivity extends Activity implements View.OnClickListener {private ViewGroup mRootView;private View mRedBox, mGreenBox, mBlueBox, mBlackBox;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mRootView = (ViewGroup) findViewById(R.id.layout_root_view);mRootView.setOnClickListener(this);mRedBox = findViewById(R.id.red_box);mGreenBox = findViewById(R.id.green_box);mBlueBox = findViewById(R.id.blue_box);mBlackBox = findViewById(R.id.black_box);}@Overridepublic void onClick(View v) {TransitionManager.beginDelayedTransition(mRootView, new Fade());toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox);}private static void toggleVisibility(View... views) {for (View view : views) {boolean isVisible = view.getVisibility() == View.VISIBLE;view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);}}
}
复制代码
- 第一步:通过
beginDelayedTranstion
传入场景对应布局的根节点(mRootView
)以及场景变换的规则(Fade
),此时系统理解调用Transition
的captureStartValues
方法,来确定场景当中所有子View
的visibility
。 - 第二步:当
beginDeleyedTransition
返回后,我们将子View
设置为不可见。 - 第三步:在下一帧,系统调用
Transtion
的captureEndValues()
方法获取场景当中所有子View
的可见性。 - 第四步:这时候系统发现在起始场景中
View
是VISIBLE
的,而在终点场景中它变为了INVISIBLE
,那么Fade Transition
就会根据这些信息创建并返回AnimatorSet
,用它来将那些发生变化的View
的alpha
值渐变为0
,而不是直接设为不可见。 - 第五步:系统启动这个
Animator
,使得这些View
慢慢隐藏。
2.3 Transition
小结
我们可以总结出Transition
的两个特点:
Animator
对于开发者而言是抽象的,开发者设置View
的起始值和最终值,Transition
会根据这两者的差异,自动地创建切换的Animator
。- 可以随时通过替换
Transition
来改变切换的规则。
三、Content Transition
基本概念
3.1 旧的界面切换动画
回忆一下,在5.0
之前:
- 给
Activity
之间的切换添加动画,在启动Activity
的地方加上overridePendingTransition
- 给
Fragment
之间的切换添加动画,通过FragmentTransation
的setCustomAnimation
。
这两种方式都有一个共同的特点,那就是它们都是将Activity
所在的窗口或Fragment
所对应的布局作为切换动画的执行单元。
3.2 新的界面切换动画
在新的切换方式当中,可以将布局中的每个View
作为切换的执行单元,我们以Activity
之间的切换为例。
3.2.1 启动BActivity
在AActivity
启动中BActivity
,这时候就会涉及到四种Scene
和两种Transition
:
AActivity's Exit Transition
:它定义了AActivity
中的元素如何从VISIBLE
(起点场景)变为INVISIBLE
(终点场景)。BActivity's Enter Transition
:它定义了BActivity
中的元素如果从INVISIBLE
(起点场景)变为VISIBLE
(终点场景)。
3.2.1.1 确定需要执行Transition
的View
整个Transition
的第一步,就是先要确定当前界面中需要执行Transition
的动画切换单元,这一过程是通过对整个View
树进行递归调用得到的,而递归的逻辑在ViewGroup
当中:
public void captureTransitioningViews(List<View> transitioningViews) {if (getVisibility() != View.VISIBLE) {return;}if (isTransitionGroup()) {transitioningViews.add(this);} else {int count = getChildCount();for (int i = 0; i < count; i++) {View child = getChildAt(i);child.captureTransitioningViews(transitioningViews);}}
}
复制代码
而在View
中,该方法为:
public void captureTransitioningViews(List<View> transitioningViews) {if (getVisibility() == View.VISIBLE) {transitioningViews.add(this);}
}
复制代码
由此可见,所有需要变换的ViewGroup/View
都保存在transitioningViews
当中,关于这个集合的构成依据以下三点:
- 节点不可见,那么它以及它的所有子节点都不加入集合。
- 节点的
isTransitionGroup()
标志位为true
,那么把它和它的所有子节点当成一个变换单元加入到集合当中。 - 除了以上两种情况,那么
View
树的所有叶子节点都加入到集合当中。
其中isTransitionGroup()
的值我们可以通过setTransitionGroup(boolean flag)
来改变,如果在场景当中用到了WebView
,而我们希望将它作为一个整体进行变换,那么应当加上这个标志位。 除了系统默认的遍历,我们还可以通过Transition
的added
和excluded
来改变这个集合。
3.2.1.2 Exit Transition
的执行过程
下面,我们以AActivity
的Exit Transition
为例,描述一下它整个的执行过程:
- 第一步:系统遍历
AActivity
的View
树,并决定在exit transition
运行时需要变换的View
,把它们放在集合当中,也就是我们在3.2.1.1
中所说的transitionViews
。 - 第二步:
AActivity
的Exit Transition
获取集合中View
的起始状态,调用的是captureStartValues
方法。 - 第三步:将集合中的
View
设为INVISIBLE
。 - 第四步:在下一帧时,
Exit Transition
获取集合中View
的终点状态,调用的是captureEndValues
方法。 - 第五步:
Exit Transition
根据第二步中的起始状态和终点状态,创建一个Animator
,并执行这个Animator
,由于是从VISIBLE
变为INVISIBLE
,因此,是通过onDisappear
方法得到Animator
。
3.2.1.3 Enter Transition
的执行过程。
BActivity
的Enter Transition
和AActvity
的Exit Transition
类似,只不过第三步操作是从INVISIBLE
到VISIBLE
。
3.2.2 从BActivity
返回
而当我们从BActivity
返回到AActivity
,那么就会涉及到下面四种Scene
和两种Transition
:
BActivity's Return Transition
AActivity's Reenter Transition
其原理和上面是相同的,就不多介绍了。
3.2.3 小结
无论是AActivity
启动BActivity
,还是BActivity
返回到AActivity
,当View
的可见性不断切换的时候,系统能保证根据状态信息来创建所需的动画。很显然,所有的Content transition
对象都需要能够捕捉并记录View
的起始状态和终点状态,幸运的是,抽象类Visiblity
已经帮我们做了,我们只需要实现onAppear
和onDisappear
方法,在里面创建一个Animator
来定义进入和退出场景的View
的动画,系统默认提供了三种Transition
- Fade、Slide、Explode
,下面我们在分析Fade
源码的时候,会详细解释这一过程。
3.3 Content Transition
和Shared Element Transition
在上面的讨论当中,我们是从切换的角度来考虑的,而如果我们从Transition
的角度来看,那么每个Transition
又可以细分为两类:
content transitions
:定义了Activity
非共享View
进入和退出场景的方式。shared element transitions
:定义了Acitivity
共享View
进入和退出场景的方法。
3.4 例子
下面,我们以一个视频来解释一下上面谈到的四个Transition
:
在这个视频当中,我们将列表页称为AActivity
,详情页称为BActivity
,此时,对应于上面提到的四种Transition
:
AActivity's Exit Transition
为null
AActivity's Reenter Transition
为null
BActivity's Enter Transition
则分为三个部分:- 封面从小的圆形渐渐变成大的方形
- 播放图标的半径渐渐变大
- 底下的列表采用了自定义的
Slide-in
动画。 BActivity's Exit Transition
:- 上半部分采用了
Slide(TOP)
的方式,而下半部分采用Slide(BOTTOM)
的方式。
四、源码分析
系统默认自带了三种Transition
,Fade、Slide、Explode
,这一节,我们一起来分析一下它们的实现方式:
4.1 Fade
4.1.1 captureXXX
函数
首先,我们看一下它获取起点和终点属性的函数:
public void captureStartValues(TransitionValues transitionValues)
public void captureEndValues(TransitionValues transitionValues)
Fade
只重写了captureStartValues
,在这里面,它把View
当前的translationAlpha
值保存起来,这个值表示的是在Transition
开始之前View
的translationAlpha
的值:
@Overridepublic void captureStartValues(TransitionValues transitionValues) {super.captureStartValues(transitionValues);transitionValues.values.put(PROPNAME_TRANSITION_ALPHA, transitionValues.view.getTransitionAlpha());}
复制代码
4.1.2 onAppear
和onDisappear
在上面的分析当中,我们提到过,当View
的可见性从INVISIBLE
变为VISIBLE
时会调用Transition
中的Animator
来执行这一变换的过程,例如从AActivity
跳转到BActivity
,那么BActivity
中的View
就会调用onAppear
所返回的Animator
:
@Overridepublic Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {float startAlpha = getStartAlpha(startValues, 0);if (startAlpha == 1) {startAlpha = 0;}return createAnimation(view, startAlpha, 1);}
复制代码
这里首先会通过getStartAlpha
去获取起始的transitionAlpha
值,它是把之前保存在PROPNAME_TRANSITION_ALPHA
中的值取出来:
private static float getStartAlpha(TransitionValues startValues, float fallbackValue) {float startAlpha = fallbackValue;if (startValues != null) {Float startAlphaFloat = (Float) startValues.values.get(PROPNAME_TRANSITION_ALPHA);if (startAlphaFloat != null) {startAlpha = startAlphaFloat;}}return startAlpha;}
复制代码
下面,我们再回到onAppear
函数当中,看一下Animator
的创建过程:
private Animator createAnimation(final View view, float startAlpha, final float endAlpha) {if (startAlpha == endAlpha) {return null;}view.setTransitionAlpha(startAlpha);final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", endAlpha);final FadeAnimatorListener listener = new FadeAnimatorListener(view);anim.addListener(listener);addListener(new TransitionListenerAdapter() {@Overridepublic void onTransitionEnd(Transition transition) {view.setTransitionAlpha(1);}});return anim;}
复制代码
从上面可以看出,它返回的是一个ObjectAnimator
,这个Animator
会把View
的translationAlpha
从startAlpha
变为1
,这也就是一个渐渐显示的过程。 再看一下onDisappear
函数,它就是onAppear
的反向过程:
@Overridepublic Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,TransitionValues endValues) {float startAlpha = getStartAlpha(startValues, 1);return createAnimation(view, startAlpha, 0);}
复制代码
4.2 Slide
下面,我们来看一下另一种Transition
- Slide
的实现原理,和上面类似,我们先看一下captureXXX
方都做了什么:
4.2.1 captureXXX
@Overridepublic void captureStartValues(TransitionValues transitionValues) {super.captureStartValues(transitionValues);captureValues(transitionValues);}@Overridepublic void captureEndValues(TransitionValues transitionValues) {super.captureEndValues(transitionValues);captureValues(transitionValues);}
复制代码
对于起点和终点值的获取都是调用了下面这个函数,它保存的是View
在窗口中的位置:
private void captureValues(TransitionValues transitionValues) {View view = transitionValues.view;int[] position = new int[2];view.getLocationOnScreen(position);transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
}
复制代码
4.2.2 onAppear
和onDisappear
@Overridepublic Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {if (endValues == null) {return null;}int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);//终点值是确定的float endX = view.getTranslationX();float endY = view.getTranslationY();//起点值则需要根据所选的模式来确定float startX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);float startY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);//根据起点值、终点值、View所处窗口的位置,来得到一个`Animator`return TranslationAnimationCreator.createAnimation(view, endValues, position[0], position[1], startX, startY, endX, endY, sDecelerate, this);}
复制代码
这里面,最关键的是mSlideCalculator
,默认情况下为:
private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {@Overridepublic float getGoneY(ViewGroup sceneRoot, View view, float fraction) {return view.getTranslationY() + sceneRoot.getHeight() * fraction;}};
复制代码
用一张图解解释一下上面的坐标:
所以当我们采用这个Transition
的时候,就可以看到它从屏幕的底端滑上来。 而onDisappear
则也是一个反向的过程:
@Overridepublic Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {if (startValues == null) {return null;}int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);//这里的起始值和终点值正好是和onAppear相反的.float startX = view.getTranslationX();float startY = view.getTranslationY();float endX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);float endY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);return TranslationAnimationCreator.createAnimation(view, startValues, position[0], position[1], startX, startY, endX, endY, sAccelerate, this);}
复制代码
4.3 小结
通过分析Fade
和Slide
的源码,它们的主要思想就是:
- 在
capturexxx
方法中,把属性保存在TranslationValues
中,这里,一定要记得调用对应的super
方法让系统保存一些默认的状态。 - 在
onAppear
和onDisappear
中,根据起点和终点和终点的TranslationValues
,构造一个改变View
属性的Animator
,同时在动画结束之后,还原它的属性。
五、总结
这一篇我们分析了Content Transition
的设计思想和原理,下一篇文章我们将着重讨论如何通过代码来实现上面的效果。
六、参考文献
1.Getting Started with Activity & Fragment Transitions (part 1) 2.Content Transitions In-Depth (part 2) 3.Material-Animations
动画体系知识梳理(1) 转场动画 ContentTransition 理论篇相关推荐
- View 绘制体系知识梳理(7) getMeasuredWidth 和 getWidth 的区别
前言 前几天被问到了getMeasuredWidth和getWidth之间的区别,因此回来看了一下源码,又顺便复习了一遍measure/layout/draw的过程,有兴趣的同学可以看前面的几篇文章 ...
- Flutter Hero 实现径向变换动画 — 圆形变成矩形的转场动画
系列文章 Flutter 旋转动画 - RotationTransition Flutter 平移动画 - 4种实现方式 Flutter 淡入淡出与逐渐出现动画 Flutter 尺寸缩放.形状.颜色. ...
- php switch 函数,PHP丨PHP基础知识之条件语SWITCH判断「理论篇」
Switch在一些计算机语言中是保留字,其作用大多情况下是进行判断选择.以PHP来说,switch(开关语句)常和case break default一起使用 典型结构 switch($control ...
- Android官方开发文档Training系列课程中文版:动画视图之创建自定义转场动画
原文地址:http://android.xsoftlab.net/training/transitions/custom-transitions.html 自定义转场可以创建自定义动画.比如,可以定义 ...
- 《软件工程与实践》 |(九)软件工程新技术及体系 知识梳理
系列索引: <软件工程与实践>第三版 软件工程课程知识梳理 目录 系列索引: <软件工程与实践>第三版 软件工程课程知识梳理 本章重难点: 9.1 软件工程新技术 9.1.1 ...
- tab动画 vue_Vue项目轻松实现转场动画
在做混合开发的项目时,通常需要实现跟原生一样的转场动画,之前有个vue项目使用了vue路由钩子结合animate.css实现的,但没研究透所以在App里效果并不是很理想.后来发现了一款特别方便的插件v ...
- View 事件传递体系知识梳理(1) 事件分发机制
一.事件分发概述 1.1 事件分发的关键方法 对于ViewGroup来说,与事件分发相关的方法包括: public boolean dispatchTouchEvent(MotionEvent eve ...
- View 绘制体系知识梳理(4) 绘制过程之 Layout 详解
一.布局的起点 - performTraversals 和前面分析测量过程类似,整个布局的起点也是在ViewRootImpl的performTraversals当中: private void per ...
- php中while的用法,PHP丨PHP基础知识之流程控制WHILE循环「理论篇」
刚刚讲完FOR循环今天来讲讲他的兄弟WHILE循环!进入正题: while是计算机的一种基本循环模式.当满足条件时进入循环,进入循环后,当条件不满足时,跳出循环.while语句的一般表达式为:whil ...
最新文章
- 史上最全 | 数据分析技能详细拆解,一张图覆盖全流程知识细节和资源推荐(附下载)...
- LeetCode: 105. Construct Binary Tree from Preorder and Inorder Traversal
- Linux命令 swap:内存交换空间
- leetcode 62. 不同路径(dp)
- CS144 lab0 笔记
- SQLi LABS Less-3 联合注入+报错注入
- 开源游戏《一小时人生》GitHub仓库被删,CEO亲自道歉
- 2021-06-20 表单详解
- API 接口 并发测试 Jmeter Postman
- axure数据报表元件库_axure图表元件库 axure教程:如何制作axure组件库
- Win10优化大师Windows 10 Manager v3.4.6.0 官方安装绿色版
- PPT导出高分辨率dpi图片
- MySql经典面试题(含表)
- 磁盘管理以及文件系统管理
- 根据输入的电话号码查询联系人
- Docker的volumes的使用
- 支付宝小程序唤起独立签约
- sumproduct()学习
- JavaScript详细教程
- 签名文件及使用360加固保生成渠道包