俗话说,“光说不练假把式,光练不说傻把式,既说又练全把式”,继 Android Transition Framework 源码分析 的源码分析后,这篇文章会在应用层面上详解 Transition Framework。 老规矩,看看下要实现的效果图。
概要
Transition Framework 有两个重要的概念,Scene( 场景 ) 和 Transition( 转换 )。从 上篇文章 可以看出,如果想要实现场景转换,需要一个 start scene(起始场景)和 end scene(结束场景),然后利用 Transition 和 TransitionManager 来生成动画。 盗用安卓官网一副图来说明。
Scene
创建Scene
创建 Scene 有2中方式,一种是传入 XML layout 文件,例如,上篇文章例子中是使用的
mScene1 = Scene.getSceneForLayout(sceneRoot, R.layout.scene1, this);
而另外一种是传入 View,例如,本文后面例子中将使用的
mScene1 = new Scene(sceneRoot, findViewById(R.id.scene1));
Scene作用
从上文可以,Scene 的作用其实很少,主要就是用来保存在创建 Scene 的参数。另外,你还可以给 Scene 设置进入或者退出的动作
mScene1.setEnterAction(new Runnable() {
@Override
public void run() {
}
});
从上文分析可知,这个 Runnable 对象是与动画一起运行的。
而安卓官网上说,Scene 还用来保存层级视图的信息,其实这都是保存在 Transition 中。
如果没有提供 Transition,默认用的是 AutoTransition。
Transition
创建 Transition
系统给我们内置了好多个 Transition 的实现类,稍微查看了 android.transition 包,着实吓到宝宝了
这里面并不是全部都是 Transition 实现类,不过大部分是。 实现类也分为2类,一类是直接实现 Transition 类的,一类是实现 Visibility 类的,而 Visibility 是 Transition 类的。
Transition 创建方式与属性动画一样,有2种,一种是通过 XML 文件,一种是通过 new 创建。
例如,用 XML 文件创建
位置 : res/transition/fade_transition.xml
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
然后在 Activity 中加载
Transition mFadeTransition =
TransitionInflater.from(this).
inflateTransition(R.transition.fade_transition);
例如,直接用 new 创建
Transition transition = new ChangeBounds();
Transition 作用
从上篇文章分析可知,Transition 不仅用来保存层级视图信息,而且还用来创建并运行动画。
TransitionManager
TransitionManager作用
TransitionManager 保存 Scene 和 Transition,并协调它们执行 Transition Animation,从上文分析可知 TransitionManager 要做到这一点,是要经历三步,一是保存视图信息,而是场景切换,三是创建并运行动画。
TransitionManager使用
TransitionManager.go(toScene, transition)
go() 方式是 TransitionManager 的静态方式,这种是最简单粗暴的,toScene 直接定义了 end scene,那么 start scene 呢?当然指的就是 SceneRoot 当前的 Scene。例如,上文中所使用的
TransitionManager.go(mScene1, mChangeBounds);
TransitionManager.beginDelayedTransition(sceneRoot, transition)
beginDelayedTransition() 也是 TransitionManager 的静态方法,这个用法有点委婉,可以看到这里面并没有 Scene 参数,那么 start scene,end scene 如何定义呢? 假如我现在要在 ViewGroup 中,add view 或者 remove view,或者修改 view 的 LayoutParams,然后生成转化动画。
如果要利用 Scene 来实现,还要用一个 XML layout 创建end Scene ( start scene 一般就是当前 SceneRoot 决定的),而 end scene 就是 add view 或 remove view 或 修改 view 的 LayoutParams 后决定的 Layout 生成的。然后,从上文分析可知,在第二步的时候是需要先移除 SceneRoot 下的所有 children,在把 end scene 的 layout 加载进 SceneRoot。而上面描述的 add view 或者 remove view 或者 修改 view 的 LayoutParams,不就是帮我们完成了这个任务吗,所以,所以什么,所以来看安卓官网的代码代码
// Create a new TextView and set some View properties mLabelText = new TextView(); mLabelText.setText("Label").setId("1"); // Get the root view and create a transition mRootView = (ViewGroup) findViewById(R.id.mainLayout); mFade = new Fade(IN); // Start recording changes to the view hierarchy TransitionManager.beginDelayedTransition(mRootView, mFade); // Add the new TextView to the view hierarchy mRootView.addView(mLabelText);
这段代码与我们重新创建一个 XML layout ,然后用 TransitionManager.go() 的效果是一样的。
mTransitionManager.setTransition() 和 mTransitionManager.transitionTo()
这两个方式用来更特么的委婉,由于这2个方式不是静态方法,所以要先创建 TransitionManager 对象,然后调用
mTransitionManager = new TransitionManager(); mTransitionManager.setTransition(mScene1, mScene2, transition1); mTransitionManager.setTransition(mScene2, mScene1, transition2);
接下来,我们需要为 SceneRoot 指定 current scene
mScene1.enter();
从上一篇文章分析可知,这里进行的是场景切换,并设置了 current scene,核心源码如下
public void enter() { // ... setCurrentScene(mSceneRoot, this); } static void setCurrentScene(View view, Scene scene) { view.setTag(R.id.transition_current_scene, scene); }
最后一步就是要调用 mTransitinoManager.transitionTo(toScene)
mTransitinoManager.transitionTo(toScene2);
而 transitionTo() 就要用到之前设定的 current scene,核心代码如下
static Scene getCurrentScene(View view) { return (Scene) view.getTag(R.id.transition_current_scene); }
例子
结束了枯燥乏味的理论知识,现在来实现下本文开头给出的效果图。
首先给出 Activity 的主布局,activity_main.xml 布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/scene_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/scene1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/bottom_container"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:background="@color/colorPrimary"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp"
android:transitionName="@string/transition_bottom">
<TextView
android:id="@+id/album_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lose Yourself"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:id="@+id/album_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Eminem"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
<ImageView
android:id="@+id/album_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/bottom_container"
android:scaleType="centerCrop"
android:src="@drawable/emi"
android:transitionName="@string/transition_image"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/album_image"
android:layout_alignEnd="@id/album_image"
android:layout_marginBottom="-30dp"
android:layout_marginEnd="15dp"
android:onClick="onFabClick"
android:src="@drawable/ic_play_animatable"
android:transitionName="@string/transition_fab"
app:fabSize="normal"/>
</RelativeLayout>
</RelativeLayout>
这个布局的效果如下
注意,在接下来的转换动画中,SceneRoot 在这个布局中指的是 id 为 scene_root 的 RelativeLayout。 同时,布局中定义了三个 transitionName,也就是要转换的 View。
然后看看 end scene 的布局 scene2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/album_image"
android:layout_width="match_parent"
android:layout_height="220dp"
android:scaleType="centerCrop"
android:src="@drawable/emi"
android:transitionName="@string/transition_image"/>
<LinearLayout
android:id="@+id/bottom_container"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_below="@id/album_image"
android:background="@color/colorPrimary"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp"
android:transitionName="@string/transition_bottom">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lose Yourself"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Eminem"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/album_image"
android:layout_alignEnd="@id/album_image"
android:layout_marginBottom="-30dp"
android:layout_marginEnd="15dp"
android:onClick="onFabClick"
android:src="@drawable/ic_pause_animatable"
android:transitionName="@string/transition_fab"
app:fabSize="normal"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/tracks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/bottom_container"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:transitionName="@string/transition_rv"/>
</RelativeLayout>
效果如下
同样需要注意的就是,给需要转换的三个 view 要给出相同的 transitionName。
然后,在 Activity 中创建 Scene
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewGroup sceneRoot = (ViewGroup) findViewById(R.id.scene_root);
mScene1 = new Scene(sceneRoot, findViewById(R.id.scene1));
mCurrentScene = mScene1;
mScene1.setEnterAction(new Runnable() {
@Override
public void run() {
startFabAnimation();
}
});
mScene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scene2, this);
mScene2.setEnterAction(new Runnable() {
@Override
public void run() {
RecyclerView tracks = (RecyclerView) findViewById(R.id.tracks);
tracks.addItemDecoration(new DividerItemDecoration(MainActivity.this, DividerItemDecoration.VERTICAL));
tracks.setLayoutManager(new LinearLayoutManager(MainActivity.this));
tracks.setAdapter(new TracksAdapter());
startFabAnimation();
}
});
}
private void startFabAnimation() {
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
AnimatedVectorDrawable drawable = (AnimatedVectorDrawable) fab.getDrawable();
drawable.start();
}
这里,我们给 scene1,scene2 设置了 action,前面我们已经说过,这个 Runnable 对象 action 是与动画一起执行的。
现在,我们定义场景切换的时机就是点击 FloatingActionButton 的时候
public void onFabClick(View view) {
if (mCurrentScene == mScene1) {
changeScene(mScene2);
} else if (mCurrentScene == mScene2) {
changeScene(mScene1);
}
}
private void changeScene(Scene scene) {
Transition transition = new ChangeBounds();
TransitionSet set = new TransitionSet();
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
Slide slide = new Slide(Gravity.BOTTOM);
slide.addTarget(R.id.tracks);
set.addTransition(slide);
set.addTransition(transition);
mCurrentScene = scene;
TransitionManager.go(scene, set);
}
到这里,你就可以实现文章开头给出的效果图了。
FloatingActionButton 的动画是用的矢量图动画,如果不明白如何创建和使用矢量图动画,请看我 Android:获取并制作矢量图动画
总结
本文是在应用层面上对 Transition Framework 进行剖析和实战,全面讲解了 Transition Framework 的 Scene,Transition,TransitionManager 的作用以及使用方法,其中 Transition 的实现类并没有给出太多分析,也没给出例子,也没有给出如何自定义 Transition 类的例子,这就靠我们自己在平时去实践。
总之,在这篇文章后,可以对 Transition Framework 研究告一段落了,接下来就写关于 Activity/Fragment Transition 和 Shared Elements Transition 的原理的文章了。