在上篇笔记中对于Transition的框架和经常使用的API使用进行了分析,Transition最经常使用的是在界面过渡方面,本文继续学习Transition在界面过渡上的使用。在界面过渡上,Transition分为不带共享元素的Content Transition和带共享元素的ShareElement Transition。html
先看下content transition的一个例子,在Google Play Games上的应用:android
在通过学习后咱们也能够设计出相似的效果,首先须要了解在界面过渡中涉及到的一些重要方法,从ActivtyA调用startActivity方法唤起ActivityB,到ActivityB按返回键返回ActivityA涉及到与Transition有关的方法git
所以,只要咱们在对应的方法中设置了Transition就能够了。在默认没有设置对应Transition的状况下,Material-theme应用的exitTransition为null,enterTransition为Fade,若是reenterTransition和returnTransition未设定,则分别对应exitTransition和enterTransition。github
在style中添加android:windowContentTransitions
属性启用窗口内容转换(Material-theme应用默认为true),指定该Activity的Transitionbash
<!-- 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>
</style>复制代码
也能够在代码中指定app
// 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());复制代码
而后启动Acticity框架
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());复制代码
这里在代码中指定ActivityA的exitTransition:ide
private void setupTransition() {
Slide slide = new Slide(Gravity.LEFT);
slide.setDuration(1000);
slide.setInterpolator(new FastOutSlowInInterpolator());
getWindow().setExitTransition(slide);
}复制代码
使用xml方式指定ActivityB的enterTransition:wordpress
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<slide
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:slideEdge="bottom">
<targets>
<target android:targetId="@id/content_container"/>
</targets>
</slide>
<slide
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:slideEdge="top">
<targets>
<target android:targetId="@id/image_container"/>
</targets>
</slide>
</transitionSet>复制代码
运行效果以下:布局
上图动画有两个问题:
1.ActivityA的exitTransition还没彻底走完ActivityB的enterTransition就执行了,ActivityB的returnTransition还没彻底走完ActivityA的reenterTransition就执行了;
2.状态栏和导航栏的动画不太协调;
问题1是由于默认状况下enter/return transition会比exit/reenter transition彻底结束前稍微快一点运行,若是想让前者彻底运行完后者再进来,能够在代码中调用Window
的
setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)复制代码
或者在xml中设置
<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>复制代码
运行以下:
再看下问题2,默认状况下状态栏和标题栏也会参与动画(若是有导航栏也会,测试机默认木有导航栏),当咱们把xxxoverlap属性设为false后就看得比较明显了,若是不想让它们参与动画经过excludeTarget()
将其排除,在代码中:
private void setupTransition() {
Slide slide = new Slide(Gravity.LEFT);
slide.setDuration(1000);
slide.setInterpolator(new FastOutSlowInInterpolator());
slide.excludeTarget(android.R.id.statusBarBackground, true);
slide.excludeTarget(android.R.id.navigationBarBackground, true);
slide.excludeTarget(R.id.appbar,true);
getWindow().setExitTransition(slide);
}复制代码
或者在xml中:
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:slideEdge="left"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="1000">
<targets>
<!-- if using a custom Toolbar container, specify the ID of the AppBarLayout -->
<target android:excludeId="@id/appbar" />
<target android:excludeId="@android:id/statusBarBackground"/>
<target android:excludeId="@android:id/navigationBarBackground"/>
</targets>
</slide>复制代码
效果以下:
ActivityA startActivity()
1.肯定须要执行exit Transition的target View
2.Transition的captureStartValues()获取target View Visibility的值(此时为VISIBLE)
3.将target View Visibility的值设为INVISIBLE
4.Transition的captureEndValues()获取target View Visibility的值(此时为INVISIBLE)
5.Transition的createAnimator()根据先后Visibility的属性值变化建立动画
ActivityB Activity 开始
1.肯定须要执行enter Transition的target View
2.Transition的captureStartValues()获取获取target View Visibility的,初始化为INVISIBLE
3.将target View Visibility的值设为VISIBLE
4.Transition的captureEndValues()获取target View Visibility的值(此时为VISIBLE)
5.Transition的createAnimator()根据先后Visibility的属性值变化建立动画
shareElement Transition的例子
shareElement Transition指的是共享元素从activity/fragment到其余activity/fragment时的动画
有了上面的分析看名字应该也猜得出方法对应的功能了,若是没有设置exit/enter shared element transitions,默认为 @android:transition/move
,上面的Content Transition是根据Visibility的变化建立动画,而shareElement Transition是根据大小,位置,和外观的变化建立动画,如chanageBounds、changeTransform、ChangeClipBounds、 ChangeImageTransform等,具体API使用和效果能够参考上篇。指定shareElement Transition能够经过代码形式:
getWindow().setSharedElementEnterTransition();
getWindow().setSharedElementExitTransition();
getWindow().setSharedElementReturnTransition();
getWindow().setSharedElementReenterTransition();复制代码
也能够经过xml形式:
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
@transition/change_image_transform</item>
</style>复制代码
而后启动Acticity
Intent 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添加android:transitionName
且保持一致,若是要共享的View有点多,能够经过Pair,Pair<View,String> 存储着共享View和View的名称,使用以下
Intent intent = new Intent(context, DetailsActivity.class);
intent.putExtra(DetailsActivity.EXTRA_CONTACT, contact);
Pair<View, String> p1 = Pair.create((View)ivProfile, "profile");
Pair<View, String> p2 = Pair.create(vPalette, "palette");
Pair<View, String> p3 = Pair.create((View)tvName, "text");
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(this, p1, p2, p3);
startActivity(intent, options.toBundle());复制代码
在ActivityB的theme中添加SharedElementEnterTransition
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform
</item>复制代码
change_image_transform.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<changeImageTransform
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</transitionSet>复制代码
执行效果:
从图上看,好像图片是从一个ActivityA"传递"到另外一个ActivityB,实际上真正负责绘制都发生在ActivityB上:
1.ActivityA调用startActivity()后ActivityB处于透明状态
2.Transition收集ActivityA中共享View的初识状态,并传递给ActivityB
3.Transition收集ActivityB中共享View的最终状态
4.Transition根据状态改变建立动画
5.Transition隐藏ActivityA,随着ActivityB的共享View运动到指定位置,ActivityB的背景在ActivityA上淡入,并随着动画完成而彻底可见。
咱们能够经过修改Activity背景淡入淡出时间来验证,在ActivityB中加入
getWindow().setTransitionBackgroundFadeDuration(2000);复制代码
为了更直观,把ActivityA的exitTransition先注释掉,运行效果:
能够看到,ActivityB确实像盖在ActivityA上,这里用到了 ViewOverlay,原理简单来讲就是在其余View上draw,共享View利用该技术能够实现画在其余View上。咱们能够经过Window
的setSharedElementsUseOverlay(false)
来关闭该功能,不过这样一来会使最终结果和你预想的不一致,默认该值为true。
上面分析Transition会获取共享视图先后的状态值来建立动画,若是咱们的图片是网上下载的,那么颇有可能图片的准确大小须要下载下来才能肯定,Activity Transitions API提供了一对方法暂时推迟过渡,直到咱们确切地知道共享元素已经被适当的渲染和放置。在onCreate中调用postponeEnterTransition()(API >= 21)或者supportPostponeEnterTransition()(API < 21)延迟过渡;当图片的状态肯定后,调用startPostponedEnterTransition()(API >= 21)或supportStartPostponedEnterTransition()(API < 21)恢复过渡,常见处理:
// ... 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;
}
}
);复制代码