Android Transition Framework 实战演练

俗话说,“光说不练假把式,光练不说傻把式,既说又练全把式”,继 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使用

  1. TransitionManager.go(toScene, transition)

    go() 方式是 TransitionManager 的静态方式,这种是最简单粗暴的,toScene 直接定义了 end scene,那么 start scene 呢?当然指的就是 SceneRoot 当前的 Scene。例如,上文中所使用的

    TransitionManager.go(mScene1, mChangeBounds);
  2. 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() 的效果是一样的。

  3. 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 的原理的文章了。

源码地址

https://github.com/buxiliulian/TransitionFramewokDemo

发布了44 篇原创文章 · 获赞 30 · 访问量 400万+

猜你喜欢

转载自blog.csdn.net/zwlove5280/article/details/74571852