在上篇笔记中对于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的Transitionmarkdown
<!-- 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>复制代码
运行效果以下:oop
上图动画有两个问题:
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; } } );复制代码