为View的切换添加过渡动画
动画效果不仅可以使得应用更加吸引人,更可以突出变化的内容,使得用户能够更好地理解应用的操作和运作方式。Android提供了Transitions Framework来为View层级之间的切换添加过渡效果,最低需要API level 19。下面通过一个例子说明如何 添加过渡动画。
例子以在Android上实现Master/Detail Flow为基础,最终实现效果如下:
Contents
1. 添加Transition
1.1. 添加transitionSet
transitionSet包含了一组动画效果。新建/res/transition-v21/文件夹,不在低于Android L的版本上显示动画。
在/res/transition-v21下新建detail_window_enter_transition.xml,设置用于显示进入MovieDetailActivity(Detail页面)的过渡效果:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together" android:duration="500"> <fade> <targets> <target android:excludeId="@android:id/statusBarBackground"/> <target android:excludeId="@android:id/navigationBarBackground"/> </targets> </fade> <slide android:slideEdge="top"> <targets> <target android:targetId="@id/app_bar" /> </targets> </slide> </transitionSet>
这里android:duration 为动画持续时间,单位为毫秒。<fade> 表示淡入效果,<targets> 用于指示动画作用的目标,这里用android:excludeId 排除了状态栏和底部虚拟按钮。<slide android:slideEdge=”top”> 表示滑入动画,滑入方向为上方,作用于app_bar 。
类似地,新建detail_window_return_transition.xml,设置退出MovieDetailActivity(Detail页面)的过渡效果:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together" android:duration="500"> <fade> <targets> <target android:excludeId="@android:id/statusBarBackground" /> <target android:excludeId="@android:id/navigationBarBackground" /> </targets> </fade> </transitionSet>
1.2. 设置Theme
为了启用过渡效果,需要在Activity的Theme中设置android:windowContentTransitions 为true ,并指明所使用的transitionSet。这里的例子使用了Master/Detail Flow,为MovieGridActivity(Master页面)设置主题AppTheme.Main ,为MovieDetailActivity(Detail页面)设置主题AppTheme.Detail 。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <application ... android:theme="@style/AppTheme"> <activity android:name=".ui.activity.MovieGridActivity" ... android:theme="@style/AppTheme.Main"> ... </activity> <activity android:name=".ui.activity.MovieDetailActivity" ... android:theme="@style/AppTheme.Detail"> ... </activity> </application> </manifest>
在/res/values-v21/styles.xml中为过渡前后的两个Activity打开android:windowContentTransitions ,并为MovieDetailActivity(Detail页面)的主题AppTheme.Detail 指定transitionSet:
<resources> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style> <style name="AppTheme.Main" parent="@style/AppTheme.NoActionBar"> <item name="android:windowContentTransitions">true</item> </style> <style name="AppTheme.Detail" parent="@style/AppTheme.NoActionBar"> <item name="android:windowContentTransitions">true</item> <item name="android:windowEnterTransition">@transition/detail_window_enter_transition</item> <item name="android:windowReturnTransition">@transition/detail_window_return_transition</item> </style> </resources>
在/res/values/styles.xml中依然需要定义AppTheme.Main 和AppTheme.Detail ,在Android L之前版本上不显示过渡动画:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="AppTheme.Main" parent="@style/AppTheme.NoActionBar"/> <style name="AppTheme.Detail" parent="@style/AppTheme.NoActionBar"/>
1.3. 启动Activity
最后,使用ActivityOptionsCompat通过ActivityCompat来启动Activity,过渡动画就会在进入Detail Activity时播放。
Intent intent = new Intent(this, MovieDetailActivity.class) .putExtra(MovieDetailActivity.MOVIE_INFO, movie); ActivityOptionsCompat activityOptions = ActivityOptionsCompat .makeSceneTransitionAnimation(this); ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
如果切换前后的两个界面具有相同的元素,那么可以使用Shared Element Transition为相同元素添加过渡效果。在这个例子中,MovieGridActivity(Master页面)和MovieDetailActivity(Detail页面)都具有电影海报这个元素,下面为电影海报添加Shared Element Transition。
2.1. 指定transitionName
首先参考1.2.中的步骤,在Activity的Theme中设置android:windowContentTransitions 为true 。
然后需要为Master Activity和Detail Activity中用于显示电影海报的ImageView添加transitionName,将这两个ImageView关联起来。首先定义transitionName的字符串:
<string name="detail_poster_transition_name" translatable="false">TN_DetailPoster</string>
MovieGridActivity(Master页面)的电影海报位于/res/layout/item_movie.xml:
<LinearLayout ...> <ImageView android:id="@+id/movie_poster" ... android:transitionName="@string/detail_poster_transition_name" ... /> ... </LinearLayout>
MovieDetailActivity(Detail页面)的电影海报位于/res/layout/fragment_movie_detail.xml:
<LinearLayout ... > <LinearLayout ... > <ImageView android:id="@+id/detail_poster" ... android:transitionName="@string/detail_poster_transition_name" ... /> ... </LinearLayout> ... </LinearLayout>
2.2. 启动进入和返回动画
依旧用ActivityOptionsCompat通过ActivityCompat来启动Activity,这时需要在ActivityOptionsCompat中指明过度前后所共享的View(显示电影海报的ImageView),以及共享元素的名称(即之前设置的transitionName)。
Intent intent = new Intent(this, MovieDetailActivity.class) .putExtra(MovieDetailActivity.MOVIE_INFO, movie); ActivityOptionsCompat activityOptions = ActivityOptionsCompat .makeSceneTransitionAnimation(this, new Pair<View, String>( vh.mPoster, getString(R.string.detail_poster_transition_name))); ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
然后还需要设置从MovieDetailActivity(Detail页面)返回MovieGridActivity(Master页面)的动画,在MovieDetailActivity中设置按下返回按钮时,调用supportFinishAfterTransition() :
@Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { supportFinishAfterTransition(); return true; } return super.onOptionsItemSelected(item); }
3. 延迟过渡动画
有时候在进行切换时,切换的目标元素还未载入完成,由此到会导致动画的不连贯。所以需要通过supportPostponeEnterTransition() 在MovieDetailActivity刚创建时,延迟动画效果,此时就算在MovieGridActivity(Master页面)的电影列表中点击了电影,画面会暂停,不会触发过渡动画:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_movie_detail); ... if (savedInstanceState == null) { ... supportPostponeEnterTransition(); ... } }
在这个例子中,MovieDetailFragment中的数据由MovieGridActivity传递,耗时的操作在于MovieDetailActivity中下载背景图片。应当在图片下载并显示完成后,无论成功与否,都启动动画效果,防止卡死:
private void updateTitle() { ... ImageView backdrop = (ImageView) findViewById(R.id.detail_backdrop); String url = ImageUtility.getBackdropImageUrl(mMovie.getBackdropPath()); Picasso.with(this).load(url).into(backdrop, new com.squareup.picasso.Callback() { @Override public void onError() { supportStartPostponedEnterTransition(); } @Override public void onSuccess() { supportStartPostponedEnterTransition(); } }); }
4. 完整代码
完整代码可以参考这里。