在Android 5.0
当中,Google
基于Android 4.4
中的Transition
框架引入了转场动画,设计转场动画的目的,在于让Activity
之间或者Fragment
之间的切换更加天然,其根本缘由在于界面间切换时的动画再也不是以Activity
或者Fragment
的整个布局做为切换时动画的执行单元,而是将动画的执行单元细分到了View
。目前提供的转场动画分为两种:html
Content Transition
:用于两个界面之间非共享的View
。Shared Element Transition
:用于两个界面之间须要共享的View
。Transition
Transition
的基本概念在学习Content Transition
以前,咱们先对转场动画所依赖的Transition
框架作一个简要的介绍,这个框架是围绕着两个概念**Scene
(场景)和Transition
(变换)**来创建的,在后面咱们会屡次提到它: android
Scene
):表示UI
所对应的状态,通常来讲,会有两个场景:起点场景和终点场景,在这两个场景当中,UI
有可能会有不一样的状态。在上图当中,SceneA
和SceneB
就是两个不一样的场景,ViewA
在两个场景中对应的状态分别为VISIBLE
和INVISIBLE
。Transition
):用来定义两个场景之间切换的规则,当场景发生发生变换时,Transition
须要作的有两点:View
在起点场景和终点场景的状态。View
从终点场景切换到终点场景所需的Animator
。Transition
的简单例子下面,咱们经过一个简单的例子,对上面的概念有一个直观的感觉:git
public class ExampleActivity extends Activity implements View.OnClickListener {
private ViewGroup mRootView;
private View mRedBox, mGreenBox, mBlueBox, mBlackBox;
@Override
protected 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);
}
@Override
public 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
慢慢隐藏。Transition
小结咱们能够总结出Transition
的两个特色:github
Animator
对于开发者而言是抽象的,开发者设置View
的起始值和最终值,Transition
会根据这二者的差别,自动地建立切换的Animator
。Transition
来改变切换的规则。Content Transition
基本概念回忆一下,在5.0
以前:bash
Activity
之间的切换添加动画,在启动Activity
的地方加上overridePendingTransition
Fragment
之间的切换添加动画,经过FragmentTransation
的setCustomAnimation
。这两种方式都有一个共同的特色,那就是它们都是将Activity
所在的窗口或Fragment
所对应的布局做为切换动画的执行单元。app
在新的切换方式当中,能够将布局中的每一个View
做为切换的执行单元,咱们以Activity
之间的切换为例。框架
BActivity
在AActivity
启动中BActivity
,这时候就会涉及到四种Scene
和两种Transition
: ide
AActivity's Exit Transition
:它定义了AActivity
中的元素如何从VISIBLE
(起点场景)变为INVISIBLE
(终点场景)。BActivity's Enter Transition
:它定义了BActivity
中的元素若是从INVISIBLE
(起点场景)变为VISIBLE
(终点场景)。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
来改变这个集合。
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
。Enter Transition
的执行过程。BActivity
的Enter Transition
和AActvity
的Exit Transition
相似,只不过第三步操做是从INVISIBLE
到VISIBLE
。
BActivity
返回而当咱们从BActivity
返回到AActivity
,那么就会涉及到下面四种Scene
和两种Transition
:
BActivity's Return Transition
AActivity's Reenter Transition
其原理和上面是相同的,就很少介绍了。
不管是AActivity
启动BActivity
,仍是BActivity
返回到AActivity
,当View
的可见性不断切换的时候,系统能保证根据状态信息来建立所需的动画。很显然,全部的Content transition
对象都须要可以捕捉并记录View
的起始状态和终点状态,幸运的是,抽象类Visiblity
已经帮咱们作了,咱们只须要实现onAppear
和onDisappear
方法,在里面建立一个Animator
来定义进入和退出场景的View
的动画,系统默认提供了三种Transition
- Fade、Slide、Explode
,下面咱们在分析Fade
源码的时候,会详细解释这一过程。
Content Transition
和Shared Element Transition
在上面的讨论当中,咱们是从切换的角度来考虑的,而若是咱们从Transition
的角度来看,那么每一个Transition
又能够细分为两类:
content transitions
:定义了Activity
非共享View
进入和退出场景的方式。shared element transitions
:定义了Acitivity
共享View
进入和退出场景的方法。下面,咱们以一个视频来解释一下上面谈到的四个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
,这一节,咱们一块儿来分析一下它们的实现方式:
Fade
captureXXX
函数首先,咱们看一下它获取起点和终点属性的函数:
public void captureStartValues(TransitionValues transitionValues)
public void captureEndValues(TransitionValues transitionValues)
Fade
只重写了captureStartValues
,在这里面,它把View
当前的translationAlpha
值保存起来,这个值表示的是在Transition
开始以前View
的translationAlpha
的值:
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
transitionValues.values.put(PROPNAME_TRANSITION_ALPHA, transitionValues.view.getTransitionAlpha());
}
复制代码
onAppear
和onDisappear
在上面的分析当中,咱们提到过,当View
的可见性从INVISIBLE
变为VISIBLE
时会调用Transition
中的Animator
来执行这一变换的过程,例如从AActivity
跳转到BActivity
,那么BActivity
中的View
就会调用onAppear
所返回的Animator
:
@Override
public 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() {
@Override
public void onTransitionEnd(Transition transition) {
view.setTransitionAlpha(1);
}
});
return anim;
}
复制代码
从上面能够看出,它返回的是一个ObjectAnimator
,这个Animator
会把View
的translationAlpha
从startAlpha
变为1
,这也就是一个渐渐显示的过程。 再看一下onDisappear
函数,它就是onAppear
的反向过程:
@Override
public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
TransitionValues endValues) {
float startAlpha = getStartAlpha(startValues, 1);
return createAnimation(view, startAlpha, 0);
}
复制代码
Slide
下面,咱们来看一下另外一种Transition
- Slide
的实现原理,和上面相似,咱们先看一下captureXXX
方都作了什么:
captureXXX
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
captureValues(transitionValues);
}
@Override
public 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);
}
复制代码
onAppear
和onDisappear
@Override
public 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() {
@Override
public float getGoneY(ViewGroup sceneRoot, View view, float fraction) {
return view.getTranslationY() + sceneRoot.getHeight() * fraction;
}
};
复制代码
用一张图解解释一下上面的坐标:
Transition
的时候,就能够看到它从屏幕的底端滑上来。 而
onDisappear
则也是一个反向的过程:
@Override
public 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);
}
复制代码
经过分析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