本文介绍了Android 5.0 新特性页面过渡共享元素的使用。
原文链接:Shared Element Activity Transition
概述
不同activity或fragment之间传统的过渡涉及到整个View层级相互独立的进入和退出过渡动画。例如,渐入过渡,滑动过渡,或新引入的爆炸过渡。
默认的Activity过渡:
然而,大多数情况下页面之间有共用元素,并且在页面切换时有能力提供这些共享元素分别强调连续性的过渡作为用户操作App。
这个过渡特性使得人们更能专注在内容上和新页面的表现上。
Activity共享元素过渡
注意共享元素过渡需要Android 5.0(API 21)及以上并且将会忽略低版本。使用API 21指定特性之前确保在运行时检查版本。
1.开启窗口内容过渡
在styles.xml文件中开启窗口内容过渡:
1 | <!-- Base application theme. --> |
2.指定共用过渡名称
在使用共享元素的布局中指定共用过渡名称。使用android:transitionName
属性。
例如在MainActivity.xml
:1
2
3
4
5
6
7
8
9
10<android.support.v7.widget.CardView
...>
<ImageView
android:id="@+id/ivProfile"
android:transitionName="profile"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="160dp" />
...
</android.support.v7.widget.CardView>
在DetailActivity.xml
:1
2
3
4
5
6
7
8
9
10<LinearLayout
...>
<ImageView
android:id="@+id/ivProfile"
android:transitionName="profile"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="380dp" />
...
</LinearLayout>
注意android:id
是可以不一样的或所在布局层级位置也是可以不同的。
3.开启Activity
通过从源处指定这些共享元素和view的bundle开启目标Activity。1
2
3
4
5
6Intent intent = new Intent(this, DetailsActivity.class);
// Pass data object in the bundle and populate details activity.
intent.putExtra(DetailsActivity.EXTRA_CONTACT, contact);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(this, (View)ivProfile, "profile");
startActivity(intent, options.toBundle());
这样就可以了。指定源view和过渡名称即使在源层级有多个相同过渡名称的view,它基本上可以选择正确的view开启动画。
当你销毁第二个Activity时为了反转情景过渡动画,调用Activity.supportFinishAfterTransition()
而不是 Activity.finish()
。还有,你需要重写ToolBar/ ActionBar中home按钮的行为:1
2
3
4
5
6
7
8
9
10@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
supportFinishAfterTransition();
return true;
}
return super.onOptionsItemSelected(item);
}
4.多个共享元素
有时你可能想要从源view层级动画多个元素。这可以通过在源view和目标view使用不同的过渡名称实现。
1 | Intent intent = new Intent(context, DetailsActivity.class); |
注意:默认会导入android.util.Pair,但我们应该选择android.support.v4.util.Pair来代替。
注意不要使用过多的共享元素。它会使场景有一个粘性直到动画从一个屏幕到另一个屏幕(可能会包含多个共享元素),过多的共享元素会导致用户分心产生糟糕的体验。
5.自定义共享元素过渡
在Android L中,共享元素过渡默认结合了 ChangeBounds, ChangeTransform, ChangeImageTransform, 和 ChangeClipBounds。这可以适用于大多数典型的场景。然而,你可能会自定义这个行为或者甚至定义你自己的自定义过渡。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- enable window content transitions -->
<item name="android:windowContentTransitions">true</item>
<!-- specify enter and exit transitions -->
<!-- options are: explode, slide, fade -->
<item name="android:windowEnterTransition">@transition/change_image_transform</item>
<item name="android:windowExitTransition">@transition/change_image_transform</item>
<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
@transition/change_image_transform</item>
</style>
change_image_transform过渡在这个示例中是这么定义的:1
2
3
4<!-- res/transition/change_image_transform.xml -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeImageTransform/>
</transitionSet>
在运行时开启窗口内容过渡,调用 Window.requestFeature()
方法:1
2
3
4
5
6// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// set an enter transition
getWindow().setEnterTransition(new Explode());
// set an exit transition
getWindow().setExitTransition(new Explode());
去除窗口内容过渡
有时候你想去掉状态栏,ActionBar和导航栏的动画序列的使用,尤其是如果共享元素绘制在它们的上面时(查看Google的这篇文章了解更多细节)。你可以去除这些元素通过添加<targets>
标签和指定去除元素的ID:1
2
3
4
5
6
7
8
9
10
11
12<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:slideEdge="right"
android:duration="1000">
<targets>
<!-- if using a custom Toolbar container, specify the ID of the AppBarLayout -->
<target android:excludeId="@id/app_bar_layout" />
<target android:excludeId="@android:id/statusBarBackground"/>
<target android:excludeId="@android:id/navigationBarBackground"/>
</targets>
</slide>
查看官方指南定义自定义动画获取更多信息。
6. 共享元素依赖于异步加载数据
如果共享元素需要通过AsyncTask, a Loader, 或其它类似的进行数据加载在被调用的Activity决定它们最终的表现,数据被分发返回到主线程之前框架可以开始过渡。
为了解决这个问题,Activity Transitions API提供了一个方法暂时推迟过渡直到我们确切地知道共享元素已经被适当的渲染和放置。
为了从开始时暂时阻止共享元素过渡,调用postponeEnterTransition()
(API >= 21)或supportPostponeEnterTransition()
(API < 21)在你被调用的Activity中的onCreate()方法中。然后,当你知道所有共享元素已经被适当的摆放和大小时,调用startPostponedEnterTransition()
(API >= 21)或supportStartPostponedEnterTransition()
(API < 21)恢复过渡。
你会找到一个常见的处理模式是在OnPreDrawListener中开启延迟过渡,它将会在共享元素被渲染和放置后调用。1
2
3
4
5
6
7
8
9
10
11
12
13// ... load remote image with Glide/Picasso here
supportPostponeEnterTransition();
ivBackdrop.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
ivBackdrop.getViewTreeObserver().removeOnPreDrawListener(this);
supportStartPostponedEnterTransition();
return true;
}
}
);
Fragment共享元素过渡
利用共享元素过渡也可以在fragment中使用和上面介绍的对Activity相同的方式显示。
注意共享元素过渡需要Android5.0(API 21)及以上并且将会忽略低版本。使用API 21指定特性之前确保在运行时检查版本。
指定共用过渡名称
在两个Fragment的布局中给共享元素指定共用过渡名称。使用android:transitionName
属性并把view放置在FirstFragment和SecondFragment中:1
2
3
4
5
6
7
8
9
10<android.support.v7.widget.CardView
...>
<ImageView
android:id="@+id/ivProfile"
android:transitionName="profile"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="160dp" />
...
</android.support.v7.widget.CardView>
定义过渡
在res/transition文件夹中添加一个名为change_image_transform.xml的过渡:1
2
3
4<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeImageTransform />
</transitionSet>
使用FragmentTransaction过渡动画
在Activity中,我们可以触发过渡作为FragmentTransaction的一部分:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// Get access to or create instances to each fragment
FirstFragment fragmentOne = ...;
SecondFragment fragmentTwo = ...;
// Check that the device is running lollipop
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Inflate transitions to apply
Transition changeTransform = TransitionInflater.from(this).
inflateTransition(R.transition.change_image_transform);
Transition explodeTransform = TransitionInflater.from(this).
inflateTransition(android.R.transition.explode);
// Setup exit transition on first fragment
fragmentOne.setSharedElementReturnTransition(changeTransform);
fragmentOne.setExitTransition(explodeTransform);
// Setup enter transition on second fragment
fragmentTwo.setSharedElementEnterTransition(changeTransform);
fragmentTwo.setEnterTransition(explodeTransform);
// Find the shared element (in Fragment A)
ImageView ivProfile = (ImageView) findViewById(R.id.ivProfile);
// Add second fragment by replacing first
FragmentTransaction ft = getFragmentManager().beginTransaction()
.replace(R.id.container, fragmentTwo)
.addToBackStack("transaction")
.addSharedElement(ivProfile, "profile");
// Apply the transaction
ft.commit();
}
else {
// Code to run on older devices
}
注意在fragment退出时我们需要使用setSharedElementReturnTransition和setExitTransition方法,在进入fragment时调用setSharedElementEnterTransition和setEnterTransition方法,最后我们需要找到共享元素实例然后调用addSharedElement(view, transitionName)
作为构建FragmentTransaction的一部分。除此之外关于fragment共享元素的过渡的资源有:
- Fragment Transitions Detailed Tutorial
- Android Authority article covering the basics
- Medium article on fragment shared element transitions
- Useful stackoverflow post for more details
- Sample repo with working code
- More useful sample code
你可以应用这些过渡到fragment上和应用到Activity一样方便。
参考
- https://developer.android.com/training/material/animations.html
- http://java.dzone.com/articles/material-design-activity
- https://www.youtube.com/watch?v=97SWYiRtF0Y&t=1403
- https://medium.com/@bherbst/fragment-transitions-with-shared-elements-7c7d71d31cbb#.x8pops1ap