“Motion provides meaning. Objects are presented to the user without breaking the continuity of experience even as they transform and reorganize. Motion in the world of material design is used to describe spatial relationships, functionality, and intention with beauty and fluidity.”
上文是谷歌關於material design 交互的解釋,他是material design 設計規范下一個重要部分。本文主要是介紹轉場動畫的一些基本使用,以及三方組件material-components-android 對於轉場動畫一些拓展,轉場動畫本質上是谷歌在轉場規范下的屬性動畫,他提供了一些默認的轉場動畫效果,當然你也可以自己手動對轉場動畫進行定制。相關的類在 package android.transition 下,Android官方Api。(演示api並非androidx api)
1. TransitionManager
這個類主要提供了兩種方式讓用戶去控制轉場,一種方式是對一個單獨的View的可見狀態,大小做一個轉場,在通常的情況下我們可以對xml中的viewgroup設置android:animateLayoutChanges="true",這個時候如果子view從不可見<->可見會有默認的轉場動畫效果。下面展示一個view從不可見到可見的一個轉場效果。
findViewById(R.id.tv_vis_click).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TransitionManager.beginDelayedTransition(llAnimationRoot); targetView.setVisibility(targetView.getVisibility() == View.VISIBLE ? View.GONE:View.VISIBLE); } });
這樣一個簡單的可見到不可見的轉場就完成了,在默認的情況有個淡入淡出的效果。如果你需要自己定制這個轉場的持續時間,轉場效果時,beginDelayedTransition允許你傳入自己定制transition,這里不做過多拓展。
上面談到了view的可見不可見轉場,除此之外我們還可以設置對於大小變換的轉場。
findViewById(R.id.tv_size_click).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TransitionManager.beginDelayedTransition(llAnimationRoot); if (targetView.getVisibility() == View.GONE){ targetView.setVisibility(View.VISIBLE); } //changeSize if (!isLarge){ changeSize(700); isLarge = true; }else { changeSize(300); isLarge = false; } } });
合理有效的轉場動畫,能極大的提升用戶的體驗,你的app也不會再被人說成像H5一樣僵硬了。TransitionManager還支持對場景的轉換,比如場景A中有個view在左上角,場景B有個View在右上角,從A->B之間的轉換,當然AB場景也不限於一個View。使用的時候需要注意,場景過渡的View需要做到Id相同。
layout_scene1.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_share" android:layout_gravity="bottom" android:layout_width="wrap_content" android:background="@color/colorAccent" android:layout_height="wrap_content" android:text="轉場Exampe" android:textColor="#FFFFFF" android:padding="20dp" /> </LinearLayout>
layout_scene2.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_share" android:layout_width="wrap_content" android:background="@color/colorAccent" android:layout_height="wrap_content" android:text="轉場Exampe" android:textColor="#FFFFFF" android:padding="20dp" /> </LinearLayout>
通過layout聲明了兩個場景,當然你還可以通過自定義View方式聲明,這里不做過多介紹
final Scene scene1 = Scene.getSceneForLayout(llAnimationRoot, R.layout.layout_screen1, this); final Scene scene2 = Scene.getSceneForLayout(llAnimationRoot, R.layout.layout_screen2, this); findViewById(R.id.tv_scree_click).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (is2Scene2){ ChangeBounds changeBounds = new ChangeBounds(); TransitionManager.go(scene1,changeBounds); is2Scene2 = false; }else { ChangeBounds changeBounds = new ChangeBounds(); TransitionManager.go(scene2,changeBounds); is2Scene2 = true; } } });
如果你還想了解更多關於TransitionManager的使用和用法,建議移步這里,作者對TransitionManager進行了有效的自定義,查看項目你能學到更多關於TransitionManager的知識。
2. 頁面之間的轉場
Android頁面之間的轉場有個相對限制的條件,只能在ActivityA和ActivityB之間,或者在FragmentA和FragmentB之間。不允許在ActivityA和FragmentB之間進行。github上有相對更加全面的轉場動畫例子,例如Material-Animations。以下會對頁面轉場做個簡單說明,同時介紹material-components-android關於轉場部分的使用。
在Activity之間轉場
在 api 21 之前android提供了過渡動畫方法overridePendingTransition 在activity啟動和推出的時候指定動畫進行頁面轉換。下面我們寫個簡單的例子感受一下,讓ActivityB從右到左進入,從左到右推出。你只需要在啟動和退出的時候設置一下
Intent intent = new Intent(); intent.setClass(MainActivity.this,TansitionToActivity.class); startActivity(intent); overridePendingTransition(R.anim.slide_right_in,R.anim.slide_left_out);
@Override public void finish() { super.finish(); overridePendingTransition(R.anim.slide_left_in,R.anim.slide_right_out); }
在android 5.0之后轉場動畫被提出,ActivityA->ActivityB可以使用transition相關api進行轉場。我們需要對ActivityA 和 ActivityB 設置對應的轉場動畫配置,例如進入動畫和退出動畫,持續時間等等。
private void setupTransitionAnimations() { TransitionSet transitionSet = new TransitionSet(); transitionSet.setDuration(300); //一起動畫 transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER); Slide slideTransition = new Slide(); slideTransition.setSlideEdge(Gravity.RIGHT); transitionSet.addTransition(slideTransition); Fade fadeTransition = new Fade(); transitionSet.addTransition(fadeTransition); //排除狀態欄 transitionSet.excludeTarget(android.R.id.statusBarBackground, true); //是否同時執行 getWindow().setAllowEnterTransitionOverlap(false); getWindow().setAllowReturnTransitionOverlap(false); //進入 getWindow().setEnterTransition(slideTransition); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupTransitionAnimations(); setContentView(R.layout.activity_tansition_to); }
調用啟動新activity方法,傳入bundle
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, null).toBundle(); Intent intent = new Intent(); intent.setClass(MainActivity.this,TansitionToActivity.class); startActivity(intent,bundle);
如何你想手動關閉頁面調用了 finish() 方法,發現轉場動畫不起效了,那是因為 finish() 方法並沒有對轉場動畫做兼容,你需要調用 onBackPress() 方法或者 finishAfterTransition() 方法,這兩個方法對轉場做了一定的兼容處理。
注意ActivityOptions提供的過渡方式並非一種,由於篇幅有限,建議查看這里。
在轉場動畫中,最讓人驚艷的元素轉場借由谷歌官方的一張圖說明
從這張圖可以看出小機器人從周圍兩邊進行放大,而后變成了Activity2當中的大機器人。要實現這個效果,我們需要在Activity2設置入場動畫,同時指定Activity1中可以被作為元素位移的View。
private void setupBondsAnimation(){
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setDuration(300);
//排除狀態欄
changeBounds.excludeTarget(android.R.id.statusBarBackground, true);
changeBounds.addTarget(android.R.id.content);
//是否同時執行
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
//進入
getWindow().setEnterTransition(changeBounds);
}
上面的代碼,指定了過渡范圍在rootview。在設置布局之后還需要和Activity1當中過渡的View增加關聯,這里transitionName我使用了當前的類名方便做區分。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupBondsAnimation(); setContentView(R.layout.activity_tansition_to); findViewById(R.id.tv_share2).setTransitionName(TansitionToActivity.class.getName()); }
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, targetView,TansitionToActivity.class.getName()).toBundle(); Intent intent = new Intent(); intent.setClass(MainActivity.this,TansitionToActivity.class); startActivity(intent,bundle);
為了讓轉場更加靈活,更加流暢。谷歌對Material Design現有的設計做了一定的補充(點我查看更多)。並且重新自定義了一套轉場動畫,和原來的使用方式相同,在動畫設置上使用了自定義的模塊。下面介紹幾種使用,更加詳情的使用示例移步這里。
Container Transform
對比android自帶的元素轉場,Container Transform 在實際使用上更加流暢。本質上他自定義了元素轉場的入場和出場動畫,和顯示計算。在Activity1當中
簡單配置一下。
private void setupContainerTransformInject() { //當Activiy2頁面退出,轉場動畫捕捉 setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback()); //動畫串行執行 getWindow().setSharedElementsUseOverlay(false); }
設置Activity2的入場和出場動畫,Activity2轉場元素指定在rootview。
private void setupContainerTransformPrepare(){ findViewById(android.R.id.content).setTransitionName(TansitionToActivity.class.getName()); setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback()); MaterialContainerTransform materialContainerTransformEnter = new MaterialContainerTransform(); materialContainerTransformEnter.addTarget(android.R.id.content); materialContainerTransformEnter.setDuration(1000); getWindow().setSharedElementEnterTransition(materialContainerTransformEnter); MaterialContainerTransform materialContainerTransformReturn = new MaterialContainerTransform(); materialContainerTransformReturn.addTarget(android.R.id.content); materialContainerTransformReturn.setDuration(1000); getWindow().setSharedElementReturnTransition(materialContainerTransformReturn); }
上面兩個方法和之間使用的一樣需要配置在Activity設置布局之前。之后就是跳轉一下。
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, targetView, TansitionToActivity.class.getName()).toBundle(); Intent intent = new Intent(); intent.setClass(MainActivity.this, TansitionToActivity.class); startActivity(intent, bundle);
官方文檔的Shared Axis,Fade Through在最新釋放的版本當中配合在activity當中使用是無效的。但是在fragment之間的轉換卻是正常的。emmmmmm官方的例子也沒有關於Shared Axis,Fade Through之間做轉場的示例代碼。這個確實要吐槽一下。
在Fragment之間轉場
我們在agment的replace,show等操作的時候可以對於fragment的顯示做一個場景切換,使用的類型和activity一樣。
頁面轉換
我們可以使用系統自帶的效果,例如fade等,做界面轉場,只需要在在執行切換界面的代碼前設置入場或者出場的動畫就行了。需要注意的一點Transition對象一次只能指定一個fragment,不可指定多個fragment,指定多個fragment也只有一個會生效!
Fade fadea = new Fade(Visibility.MODE_IN); fadea.setDuration(1000); final FragmentA fragmentA = FragmentA.newInstance(); fragmentA.setEnterTransition(fadea);
final FragmentB fragmentB = FragmentB.newInstance(); Fade fadeb = new Fade(Visibility.MODE_IN); fadeb.setDuration(1000); fragmentB.setEnterTransition(fadeb);
FragmentUtils.add(getSupportFragmentManager(),fragmentA,R.id.fl_content,false); FragmentUtils.add(getSupportFragmentManager(),fragmentB,R.id.fl_content,true);
findViewById(R.id.tv_switch).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isSwitch){ FragmentUtils.hide(fragmentA); FragmentUtils.show(fragmentB); isSwitch = false; }else { FragmentUtils.hide(fragmentB); FragmentUtils.show(fragmentA); isSwitch = true; } } });
使用Fade Through進行轉換
使用方式一樣Transition替換成了Fade Through
MaterialFadeThrough materialFadeThroughA = MaterialFadeThrough.create(); materialFadeThroughA.setDuration(1000); final FragmentA fragmentA = FragmentA.newInstance(); fragmentA.setEnterTransition(materialFadeThroughA);
final FragmentB fragmentB = FragmentB.newInstance(); MaterialFadeThrough materialFadeThroughB = MaterialFadeThrough.create(); materialFadeThroughB.setDuration(1000); fragmentB.setEnterTransition(materialFadeThroughB);
FragmentUtils.add(getSupportFragmentManager(),fragmentA,R.id.fl_content,false); FragmentUtils.add(getSupportFragmentManager(),fragmentB,R.id.fl_content,true);
使用Shared Axis進行轉換,它提供了三個方向 X Y Z,頁面的所有元素都會從這三個方向進入或者是退出。
通過MaterialSharedAxis 的create方法生成Transition 。create方法接受兩個參數,一個是方向,一個是進入或者出去的動畫方向。
//MaterialSharedAxis.X
//MaterialSharedAxis.Y
//MaterialSharedAxis.Z
MaterialSharedAxis.create(MaterialSharedAxis.Z, true)
true表示是進入的動畫,false表示是退出的動畫。用法和上面一樣 將實現的Transition替換就可。