今天我们来聊聊Android L(5.0)
引入的新特性:页面内容过渡动画和页面共享动画,这两个特性都是基于我们前面已经说过的Transition
动画,如果你对Transition
动画不太属性,请先看我前面的两篇文章。在5.0之前,我们从一个Activity A
进入到另外一个Activity B
时,如果需要设置一个动画,我们通常可以通过overridePendingTransition(int enterAnim, int exitAnim)
来设置一个B的进入动画和A的退出动画,但是这个方式的缺点也很明显,我们设置的动画只能针对页面中所有元素。
在5.0以后为了提升Android
在页面切换时更好的用户体验,页面间过渡动画应运而生,更加强大灵活的API
为开发者提供了更多页面过渡的动画选择。本文的主要内容有:什么是内容过渡动画以及如何使用;什么是共享元素动画以及如何使用;两种动画的原理;最后说了两种共享元素常用场景的实现。
在开始所有内容之前,我首先要请大家树立一个基本原则:页面过渡动画是基于Transition
动画来实现的!,文中的所有demo都可以从这里下载。ok,我们进入主题吧。
基本概念
为了简化描述,我们首先定义一些标记,在Activity A
打开Activity B
这个场景中:
-
Activity A
:我们称它为calling Activity
,简称calling
-
Activity B
:我们称为called Activity
,以后简称called
-
calling->called
:用来描述calling
打开called
-
called->>calling
:用来描述called
返回到calling
在页面过渡中,我们有两种动画可以设置:1)页面内容过渡动画,指定整个页面的变化(类似于之前的overridePendingTransition
);2)共享元素过渡动画,我们可以指定calling
中的一些View
平滑过渡到called
页面中,下面我们分开讨论这两个东西。
页面内容过渡动画
在内容过渡动画中,我们可以指定四种动画,分别为exit
(离开) enter
(进入) return
(返回) reenter
(重新进入),分别对应于下图中的四种状态:
可以看到 exit
和 reenter
作用在calling Activity
上,enter
和return
作用在called Activity
上。
和以前一样,我们还是先来一个简单的例子,再次安利一下,本系列文章中的代码都可以从这里直接下载
有人跟我反馈说文章中的
gif
不知道从哪里开始的,因为它是一直在重复,额从这个例子开始,我会在页面开始加一个begin
的黑色页面,同时右下角有时间轴
我们前面定义过,calling
表示第一个页面,called
表示第二个页面。
calling->called
之后,calling
执行了一个淡入淡出的动画,什么?不知道Fade
!?,那你先去读一下我的这篇文章-什么是过渡动画。比如,我们在calling
中可以直接这样指定:
Fade fade = new Fade();
getWindow().setExitTransition(fade);` //指定退出动画
实际上,这里一共有四个动画:
-
calling exit
: 一个淡出效果,通过Fade
实现 -
called enter
:一个从上往下的Slide
效果 -
called return
:从左往右消失 -
calling reenter
:从底部往上出现
上面例子中,我们直接指定了四种动画效果。这四种效果是两两一对的:
exit
对应reenter
,enter
对应return
。如果我们只指定了exit
或者enter
,那么与这两个对应的另外一个动画将默认使用关联动画的反向执行。此外,还需要注意一点的是,上例中能够这么看清楚这四个动画的原因是我将动画叠加执行去掉了,你可以通过setAllowEnterTransitionOverlap
改变这个设置。
**注意,为了启动页面过渡效果,calling
需要将startActivity(intent)
改成调用startActivity(Intent intent, Bundle options)
,后面的options可以通过ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
来获取;
在called
中我们需要将调用finish()
改成调用finishAfterTransition()
**
我们前面说了,这个页面过渡基于Transition
来做的,那Transition
动画无非就是两点呗:1)记录动画前后的视图树的状态变化;2)定义两个视图树之间的过渡效果。第二条我们前面已经分析过了,现在来看看系统是如何来记录两个场景的不同状态的。
其实对于整个页面的进入和退出,系统采取的办法也很简单,以退出为例,在将calling
真正隐藏之前,系统直接调用TransitionManager.beginDelayedTransition(decorView,exitTransition);
,然后再调用decorView.setVisibility(INVISIBLE);
,这样退出动画就自然执行了。
是不是很简单??
这里其实说明了如果我们自己定义一个过渡动画,那么必须要处理
View
的可见性变化,因为只有处理了可见性变化才能触发动画,如果不明白这里,可以去查看我前面的文章。
共享元素动画
怎么描述共享元素动画呢?所谓共享就是calling
中的一个view
和called
中的一个view
可以做一个平滑的过渡,下面动图中的红色的小圆圈就是一个共享元素,在实际应用中,大家可以看一下微信朋友圈图片打开的动画就是一个典型的共享元素动画。
如果仔细的朋友可以看到,我们不仅仅将view
在两个页面间共享,而且在共享之前,我们有颜色变化的动画,这里也涉及了四个动画,和之前内容过渡一一对应。
它们分别是
-
calling share exit
: 颜色由红色变成绿色 -
called share enter
:大小由小变大 -
called share return
:大小由大变小 -
calling share reenter
:颜色由绿色变成红色
我们来看一下如何来让两个页面之间的View
做一个过渡:
- 在两个Activity的布局中为需要进行过渡变化的View设置
android:transitionName
,android:transitionName
必须成对出现。 -
startActivity
时,我们通过startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this,view,transitionName).toBundle());
关联两个Activity中间的View。
我在前面曾经让你想想看,我们startActivity
的第二个参数的作用是啥,因为共享元素也是基于过渡动画来做的,而我们需要过渡的View
又处在两个不同的Activity
中,但是一个动画肯定不能在两个Activity
中间无缝过渡,那系统是怎么做到的呢?
其实着仅仅是一个障眼法而已,比如上面例子中,我们将一个View
从calling
移动到了called
,但是实际上这个动画仅仅是在called
中执行,而我们startActivity
第二个参数的Bundle
实际上是将calling
中的共享元素的相关信息传递给called
,called
在进入时,会先吧这些共享元素的信息取出来,然后在直接操作当前视图树中相关联的View
的属性和calling
中元素一样,记录一下状态,再恢复成called
本来的布局,记录一下状态,然后就可以愉快地开始动画了,真相就是这个样子。
除了Activity
,其实Android
还提供了Fragment
切换时进行页面过渡动画的相关API
,和Android
完全一致,这里就不去说明了。
Material Design
引入了很多UI的新技能,其实本人不太喜欢专研UI方面的东西,但是有时候产品需求压下来,还是得调研,多了解一些新的知识总归是好的。
多谢大家关注这个Transition
动画系列,到这里基本上就已经完全结束了,如果你觉得自己有一些收获,请点击底下的LIKE
吧!
2016-08-07补充:
在分享元素实战应用中有两个比较重要的场景,官方文档对这个场景的解决方案并不明确,在这里补充一下。
补充一 更新共享元素对应关系
很多情况下,我们需要共享元素的页面满足下面的条件:
calling
是一个列表页面,called
是一个详情页,我们在列表中选择一项打开,然后会有一个元素共享过渡到详情页,而我们在详情页是可以左右滑动切换元素的,如下图所示:
这个系统自带的相册APP,可以看到我们进入大图预览时,点击的是图片4,然后我们左右滑动,切换图片之后返回列表页,这个时候过渡动画直接返回到了图片1,那这是怎么做到的呢?
其实这里无非在过渡动画返回时,需要告诉系统,我的绑定关系改变了,我们可以在calling
设置一个SharedElementCallback
,然后在回调的onMapSharedElements
更新一下对应关系,大概的逻辑代码如下:
setExitSharedElementCallback(new SharedElementCallback() {
@Override
public void onMapSharedElements(List names, Map sharedElements) {
super.onMapSharedElements(names, sharedElements);
sharedElements.put("avator",getItemByPosition(curentPos));
}
});
我们在celled
中左右滑动修改了列表Index
之后,需要通过某种方式告知calling
,你可以通过startActivityForResult()
也可以通过EventBus
之类的第三方库,但是需要注意的是,onMapSharedElements
在exit
和reenter
时都会触发!
延迟共享元素
想想这种场景吧,called
中的共享元素在进入时,并没有到达最终的位置(比如这个数据需要从网络获取数据之后,才展示它真正的大小位置),此时如果过渡动画如果过早的执行,那么过渡动画获取的called
中共享view
的状态并不正确,所以我们有时候希望called
中的共享元素完全layout
完毕之后再执行,幸好API
提供了支持。
在called
的onCreate
中我们可以这样处理:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 延迟共享动画的执行
postponeEnterTransition();
}
然后在共享元素的最终布局确定后,你可以执行startPostponedEnterTransition
来启动共享元素动画,我们一般可以通过下面的工具方法来启动延迟动画:
private void scheduleStartPostponedTransition(final View sharedElement) {
sharedElement.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
//启动动画
sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
}
但是使用延迟共享动画有两点需要注意:
*1. 调用postponeEnterTransition
必须要在适当的时候调用startPostponedEnterTransition
,否则Activity
的过渡就会卡到这里,不能执行下去
2.基于1,我们在这两个方法间实际上也不能间隔太久(前面说的等待网络返回只是举个例子,不要在实际上使用等待网络返回的共享元素),如果间隔太大,那页面的过渡会直到调用startPostponedEnterTransition
才能进行页面切换!