Transition的相关分析与使用

Posted Alex_MaHao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Transition的相关分析与使用相关的知识,希望对你有一定的参考价值。

Transition:意“过渡”,从字面意思上理解,就是从一种状态到另一种状态的切换过程,同样,对于android中,其实现了UI状态的切换,其中包含了同一个Activity不同UI视图的变换,同时也能够实现不同Activity的切换动画,最出名的可能就是共享元素动画了。

官网提供的原理如下


从流程图上可以看出,有关键类如下:

  • Scene: 视图状态,定义开始和结束的状态。
  • Transtion: 动画,对于初始状态和结束状态的过渡效果的定义。会遍历对应Scene下的每一个View
  • TransitionManager:相应管理器,启动过渡效果。

简单的Transition动画

首先看一下实现的效果:

根据上面的原理图,我们可以按照如下思路实现效果。

  • 构建初始场景(Scene)和结束场景(Scene)。
  • 定义初始与结束场景的过渡动画效果(Transition)。
  • 使用TransitionMananger启动相应动画效果。

实现代码如下:

创建开始与结束场景

首先创建基本的布局文件activity_scene.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"
    android:orientation="vertical"
    android:padding="16dp">
    <Button
        android:onClick="begin"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:text="开始动画" />
    <!--这个View用来做动画的父布局-->
    <FrameLayout
        android:id="@+id/rootView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!--初始状态 -->
        <include layout="@layout/scene1"/>
    </FrameLayout>
</LinearLayout>

为了便于区分开始与结束状态的场景:将初始状态的场景抽出来为一单一的布局文件。
初始状态与结束状态的布局分别如下:
scene1.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="400dp">
    <ImageView
        android:id="@+id/image1"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:src="@drawable/image1" />
    <ImageView
        android:id="@+id/image3"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/image1"
        android:src="@drawable/image3" />
    <ImageView
        android:id="@+id/image2"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentRight="true"
        android:layout_below="@id/image1"
        android:src="@drawable/image2" />
</RelativeLayout>

结束状态场景scene2.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="400dp">
    <ImageView
        android:id="@+id/image3"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:src="@drawable/image3" />
    <ImageView
        android:id="@+id/image2"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/image3"
        android:src="@drawable/image2" />
    <ImageView
        android:id="@+id/image1"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentRight="true"
        android:layout_below="@id/image3"
        android:alpha="20"
        android:src="@drawable/image1" />
</RelativeLayout>

通过代码可以看到,不同点在于两次场景的子控件的布局文件不同。把三个ImageView调换了一下位置。

相应的布局文件创建完了,那么如何通过布局文件创建具体的Scene类呢?

由于初始状态已经默认加载到布局中,所以我们只需要关心结束状态的场景即可,使用如下方法

Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)即可通过布局加载场景。

  • sceneRoot:对应的布局需要加入到那个父控件下
  • layoutId:当前场景的布局id。
  • context: 上下文。

场景创建如下

Scene scene2 = Scene.getSceneForLayout(rootView, R.layout.scene2, this);

定义过渡动画

对于过渡动画效果,Android默认提供了一些基本的实现类

  • ChangeBounds: 布局位置改变的过渡效果
  • Visibility : 布局显示隐藏的相关过渡效果。

在这里,初始与结束场景只是改变了控件的位置,我们使用ChangeBounds即可。

启动动画

TransitionManager.go(scene2, new ChangeBounds());

分析

对于上面的变化效果:可能会有如下疑问:

  • 怎么确定那些控件需要变化。

  • 动画效果是怎么实现的。

  • 结束场景是什么时候加载到视图中的

对于Transition,他会比对开始场景与结束场景的不同,遍历场景下的每一个View,如何确定开始与结束场景是同一个View的判断条件如下:

private static final int[] DEFAULT_MATCH_ORDER = 
          MATCH_NAME,  // xml中 transitionName 属性,暂时不管
        MATCH_INSTANCE, // 同一个xml实例
        MATCH_ID, // 布局id
        MATCH_ITEM_ID, // ListView ,recycleView 下的itemId 
    ;

而对于上面的开始与结束场景,我们定义了对应id,所以Transition在比对两次场景下的view时,通过id确定前后场景下的同一个View

找到了前后场景的相同控件,Transition会记录前后场景下同一View的相对应属性,例如x,y等,然后根据该属性进行相对应的动画。

在通过TransitionManager启动动画时,会将初始场景remove,加载结束场景,并启动动画,因为动画是从初始场景开始的,所以显示出过渡效果。

添加Target

上面的例子中,是对整个布局的变化进行全局的动画处理,当然可以对某一个单一的控件进行动画操作。

changeBounds.addTarget(R.id.image2)

addTraget是一个重载的方法,其可以传入如下值:

  • int targetId: 资源id
  • String targetName: transitionName
  • Class targetType:
  • View target

编写xml动画

上面例子中的ChangeBoundsnew出来的,同样可以编写xml实现动画。

TransitionInflater.from(this).inflateTransition(R.transition.fade_transition);
<?xml version="1.0" encoding="utf-8"?>
<explode xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000">
    <targets>
        <target android:targetId="@id/img_transition"/>
    </targets>
</explode>

延迟动画

上面的例子:对于每一次过渡效果,都需要构造对应的场景,稍微麻烦一点。

能不能们监听某一状态,当该状态下的UI产生变化时,自动执行变化效果,例如View的显示隐藏,将自动的执行过渡效果。

TransitionManager.beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)

该方法将监听sceneRoot下的控件,当有变化时,将会执行对象的效果。

实现下面这个例子来了解beginDelayedTransition的使用:

首先看一下布局文件activity_transition_example.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <GridLayout
        android:id="@+id/root_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:columnCount="2">
        <View
            android:id="@+id/red_box"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#f00" />
        <View
            android:id="@+id/blue_box"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="10dp"
            android:background="#00f" />
        <View
            android:id="@+id/black_box"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginTop="10dp"
            android:background="#000" />
        <View
            android:id="@+id/yellow_box"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:background="#ff0" />
    </GridLayout>
</RelativeLayout>

定义了一个四个不同的颜色的方块,将通过改变visable属性,构造不同的场景。

看一下具体的代码实现,关键便是在点击效果的监听实现方法

    @Override
    public void onClick(View v) 
        // 启动延时动画
        TransitionManager.beginDelayedTransition(mRootView, new Explode());
        // 切换控件的状态
        toggleVisibility(mRedBoxView, mBlueBoxView, mBlackBoxView, mYellowBoxView);
    

注释很明白,唯一的疑惑可能就是 Explode,首先可以确定他是Explode的实现类。

在前面提到过Transition的其中一个子类Visibility,该类主要监听控件的显示和隐藏并做对应的动画,而Explode便是该类的其中一个子类,实现了类似爆炸效果的显示隐藏。还有两个子类分别是FadeSlide

自定义Transition

根据上面的例子,可以简单的理解,Transition就是监听控件的某一属性,并根据该属性的变化形成过渡动画。那么看下面的例子:


该过渡效果,主要监听颜色的变化根据颜色的变化生成过渡效果。

创建类ColorChangeTransition,继承Transition,并实现两个抽象方法:

 public static class ColorChangeTransition extends Transition 

        // key
        private static final String PROPNAME_BACKGROUND = "colorchangetransition:change_color:background";


        @Override
        public void captureStartValues(TransitionValues transitionValues) 
            // 开始场景的回调,计算初始状态
            if (transitionValues.view.getBackground() instanceof ColorDrawable) 
                // 计算颜色的值
                captureValues(transitionValues);
            
        

        // 获取色值,并保存
        private void captureValues(TransitionValues values) 
            View view = values.view;
            // 保存对应的状态
            values.values.put(PROPNAME_BACKGROUND, ((ColorDrawable) values.view.getBackground()).getColor());

        

        @Override
        public void captureEndValues(TransitionValues transitionValues) 
            // 结束场景的回调,计算结束状态
            if (transitionValues.view.getBackground() instanceof ColorDrawable) 
                captureValues(transitionValues);
            
        

           @Override
        public String[] getTransitionProperties() 
            // 返回我们自定义的属性key
            return new String[]
                    PROPNAME_BACKGROUND
            ;
        

因为我们实现的是背景颜色变换的过渡效果,所以我们只关注背景的变换。

继承Transition方法会实现两个抽象方法

  • captureStartValues: 开始场景状态的回调
  • captureEndValues : 结束场景状态的回调

根据之前的原理分析,我们只需要在两个回调中获取我们相应的值幷保存即可。因为开始与结束保存的值的方式都相同,所以统一方法处理。

同时,我们还需要重载一个方法,实现动画的创建。


 @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) 
            // 在开始与结束场景都存在的时
            if (null == startValues || null == endValues) 
                return null;
            
            // 动画的目标view
            final View view = endValues.view;
            // 初始状态和结束状态
            int startBackground = (Integer) startValues.values.get(PROPNAME_BACKGROUND);
            int endBackground = (Integer) endValues.values.get(PROPNAME_BACKGROUND);

            if (startBackground != endBackground) 
                // 创建动画
                ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
                        startBackground, endBackground);
                animator.setDuration(500);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) 
                        Object value = animation.getAnimatedValue();
                        if (null != value) 
                            view.setBackgroundColor((Integer) value);
                        
                    
                );
                return animator;
            
            return null;
        

基于上,即可实现。

总结:

  • 继承Transition,并实现抽象方法。
  • 保存开始与结束场景的相应状态值
  • 创建对应的动画。

源码已经上传到github,有需求请移步。https://github.com/AlexSmille/TransitionDemo

以上是关于Transition的相关分析与使用的主要内容,如果未能解决你的问题,请参考以下文章

什么时候使用transition?什么时候使用animation?

CSS transition和animation的区别

CSS animation 与 CSS transition 有何区别

CSS3 animaion 和 transition 比较

前端基础学习CSS过渡与动画

前端基础学习CSS过渡与动画