美文网首页程序员技术栈安卓集中营安卓UI
一、初识Transition—实现两个场景的变换

一、初识Transition—实现两个场景的变换

作者: HungerDeng | 来源:发表于2018-10-11 17:51 被阅读116次

    诞生于4.4的transition框架为在不同的UI状态之间产生动画效果提供了非常方便的API。5.0中Activity和Fragment 转场变换也是建立在Transitions框架的新特性之上的。

    该框架主要基于两个概念:scenes(场景)和transitions(变换)

    1. Secene

    Transition Framework 核心就是根据Scene的不同帮助开发者们自动生成动画

    官方文档
    A scene represents the collection of values that various properties in the View hierarchy will have when the scene is applied. A Scene can be configured to automatically run a Transition when it is applied, which will animate the various property changes that take place during the scene change.

    通俗的解释就是这个类存储着一个根view下的各种view的属性。

    创建Scene

    创建一个 Scene有两种方法

    // sceneRoot是Scene的 Container,也可以说是它的根布局
    1. Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) ;
    2. new Scene(ViewGroup sceneRoot, View layout);
    

    例子如下:

        protected Scene scene1;
        protected Scene scene2;
    
        protected void initScene(@IdRes int sceneRoot, @LayoutRes int scene1_layout, @LayoutRes int scene2_layout) {
            ViewGroup sceneRoot= (ViewGroup) findViewById(rootView);
            scene1= Scene.getSceneForLayout(sceneRoot,scene1_layout,this);
            scene2=Scene.getSceneForLayout(sceneRoot,scene2_layout,this);
            TransitionManager.go(scene1); //先把初始状态设置为scene1
        }
    

    或者

        protected Scene scene1;
        protected Scene scene2;
    
            ViewGroup sceneRoot=findViewById(R.id.rootView);
    
            View view1= LayoutInflater.from(this).inflate(R.layout.changeclipbounds_scene,null);
            View view2= LayoutInflater.from(this).inflate(R.layout.changeclipbounds_scene,null);
            ImageView iv1=view1.findViewById(R.id.imageView);
            ImageView iv2=view2.findViewById(R.id.imageView);
            iv1.setClipBounds(new Rect(0,0,100,100));
            iv2.setClipBounds(new Rect(100,100,200,200));
    
            scene1=new Scene(sceneRoot,view1);
            scene2=new Scene(sceneRoot,view2);
            TransitionManager.go(scene1);//先把初始状态设置为scene1
    

    sceneRoot 在动画开始时,会将sceneRoot中的所有子View都remove掉,然后在sceneRoot 中加载我们的end Scene。

    所以,对于end Scene,如果是通过代码new Scene(mSceneRoot, view)创建的Scene其实对于view是有要求的:view是没有parentview的,不然在addview的时候会报错
    验证代码如下:

    //测试该段代码
            LinearLayout container=new LinearLayout(appContext);
            View view= LayoutInflater.from(appContext).inflate(R.layout.layout_temp,null);
            FrameLayout frameLayout=new FrameLayout(appContext);
            frameLayout.addView(view);
            container.addView(view);
    //log
    java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
    at android.view.ViewGroup.addViewInner(ViewGroup.java:4917)
    at android.view.ViewGroup.addView(ViewGroup.java:4748)
    

    例子

    //scene1.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/bangtang"
            tools:layout_editor_absoluteX="128dp"
            tools:layout_editor_absoluteY="58dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="58dp"
            app:layout_constraintEnd_toEndOf="parent" />
    
        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/shengdanlaoren"
            tools:layout_editor_absoluteX="49dp"
            tools:layout_editor_absoluteY="226dp"
            app:layout_constraintTop_toTopOf="@+id/imageView3"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="49dp" />
    
        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/xueren"
            tools:layout_editor_absoluteX="210dp"
            tools:layout_editor_absoluteY="226dp"
            android:layout_marginTop="40dp"
            app:layout_constraintTop_toBottomOf="@+id/imageView1"
            android:layout_marginEnd="46dp"
            app:layout_constraintEnd_toEndOf="parent" />
    </android.support.constraint.ConstraintLayout>
    
    //scene2.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    
        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/shengdanlaoren"
            tools:layout_editor_absoluteX="128dp"
            tools:layout_editor_absoluteY="58dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="58dp"
            app:layout_constraintEnd_toEndOf="parent" />
    
        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/xueren"
            tools:layout_editor_absoluteX="49dp"
            tools:layout_editor_absoluteY="226dp"
            app:layout_constraintTop_toTopOf="@+id/imageView1"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="49dp" />
    
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/bangtang"
            tools:layout_editor_absoluteX="210dp"
            tools:layout_editor_absoluteY="226dp"
            android:layout_marginTop="40dp"
            app:layout_constraintTop_toBottomOf="@+id/imageView2"
            android:layout_marginEnd="46dp"
            app:layout_constraintEnd_toEndOf="parent" />
    
    </android.support.constraint.ConstraintLayout>
    
    scene1和scene2的效果对比图

    Activity代码:

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_base_scene);
            Button btn=findViewById(R.id.btn);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    clickChange();
                }
            });
                    
            ViewGroup sceneRoot= (ViewGroup) findViewById(rootView);
            scene1= Scene.getSceneForLayout(sceneRoot,scene1_layout,this);
            scene2=Scene.getSceneForLayout(sceneRoot,scene2_layout,this);
            TransitionManager.go(scene1);//先把初始状态设置为scene1
        }
    
        protected void clickChange(){
            TransitionManager.go(isScene1?scene2:scene1,getTransition());
            isScene1=!isScene1;
        }
    
        Transition getTransition() {
            return new ChangeBounds();
        }
    

    getTransition方法提供的是两个Scene的切换效果,下面会讲到,所以先不管它。
    最后出来的效果是:



    scene1和scene2的 Id 对比图

    根据效果图和Id对比图可以发现切换时是相同id的View之间互相切换,那么如果两个Scene之间View的Id不对等(id不相同 或者 一多一少)呢?


    View的Id不对等

    这个时候可以试着运行一下就会发现:仍然可以实现两个场景的切换,但是切换过程没有任何动画效果(即Transition没有起作用),就只是简单的替换。


    上面例子getTransition方法返回ChangeBounds(),其实就是一种Transition的实现,下面来详细了解一下:

    2. transitions

    当一个Scene发生改变时,transition主要负责:

    1. 捕捉每个View在开始场景和结束场景时的状态。
    2. 根据两个场景(开始和结束)之间的区别创建一个Animator

    2.1 API 21之后,框架层提供的Transitions:

    2.1.1 ChangeBounds :检测view的位置边界创建移动和缩放动画

    捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。

    根据始末位置画出Path,再根据Path创造Animator

    GIFchangebounds.gif
    /**
     * This transition captures the layout bounds of target views before and after
     * the scene change and animates those changes during the transition.
     *
     * <p>A ChangeBounds transition can be described in a resource file by using the
     * tag <code>changeBounds</code>, using its attributes of
     * {@link android.R.styleable#ChangeBounds} along with the other standard
     * attributes of {@link android.R.styleable#Transition}.</p>
     */
    public class ChangeBounds extends Transition {
        ...
    }
    
    2.1.2 ChangeClipBounds :检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果

    捕获共享元素clip bounds,然后播放clip bounds变化动画。

    GIFchangeClipBounds.gif
    /**
     * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the
     * scene change and animates those changes during the transition.
     */
    public class ChangeClipBounds extends Transition {
          
        ...
        @Override
        public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
             ...
            Rect start = (Rect) startValues.values.get(PROPNAME_CLIP);
            Rect end = (Rect) endValues.values.get(PROPNAME_CLIP);
            boolean endIsNull = end == null;
            ...
            endValues.view.setClipBounds(start);
            RectEvaluator evaluator = new RectEvaluator(new Rect());
            ObjectAnimator animator =
                    ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end);
            if (endIsNull) {
                final View endView = endValues.view;
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        endView.setClipBounds(null);
                    }
                });
            }
            return animator;
        }
    }
    
    2.1.3 ChangeImageTransform :检测ImageView(这里是专指ImageView)的尺寸,位置以及ScaleType,并创建相应动画。

    捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小,形状或者ImageView.ScaleType

    属性平滑过度。

    /**
     * This Transition captures an ImageView's matrix before and after the
     * scene change and animates it during the transition.
     *
     * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews
     * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents
     * smoothly.</p>
     */
    public class ChangeImageTransform extends Transition {
    
        ...
        /**
         * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
         * {@link android.widget.ImageView.ScaleType}.
         *
         * @param sceneRoot   The root of the transition hierarchy.
         * @param startValues The values for a specific target in the start scene.
         * @param endValues   The values for the target in the end scene.
         * @return An Animator to move an ImageView or null if the View is not an ImageView,
         * the Drawable changed, the View is not VISIBLE, or there was no change.
         */
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
                TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
            Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
            Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
            if (startBounds == null || endBounds == null) {
                return null;
            }
            Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
            Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
            boolean matricesEqual = (startMatrix == null && endMatrix == null) ||
                    (startMatrix != null && startMatrix.equals(endMatrix));
            if (startBounds.equals(endBounds) && matricesEqual) {
                return null;
            }
            ImageView imageView = (ImageView) endValues.view;
            Drawable drawable = imageView.getDrawable();
            int drawableWidth = drawable.getIntrinsicWidth();
            int drawableHeight = drawable.getIntrinsicHeight();
            ObjectAnimator animator;
            if (drawableWidth == 0 || drawableHeight == 0) {
                animator = createNullAnimator(imageView);
            } else {
                if (startMatrix == null) {
                    startMatrix = Matrix.IDENTITY_MATRIX;
                }
                if (endMatrix == null) {
                    endMatrix = Matrix.IDENTITY_MATRIX;
                }
                ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix);
                animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
            }
            return animator;
        }
        ...
    }
    
    2.1.4 ChangeScroll :滑动的属性发生了变化
    
    /**
     * This transition captures the scroll properties of targets before and after
     * the scene change and animates any changes.
     */
    public class ChangeScroll extends Transition {
    
         ...
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
                TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
            final View view = endValues.view;
            int startX = (Integer) startValues.values.get(PROPNAME_SCROLL_X);
            int endX = (Integer) endValues.values.get(PROPNAME_SCROLL_X);
            int startY = (Integer) startValues.values.get(PROPNAME_SCROLL_Y);
            int endY = (Integer) endValues.values.get(PROPNAME_SCROLL_Y);
            Animator scrollXAnimator = null;
            Animator scrollYAnimator = null;
            if (startX != endX) {
                view.setScrollX(startX);
                scrollXAnimator = ObjectAnimator.ofInt(view, "scrollX", startX, endX);
            }
            if (startY != endY) {
                view.setScrollY(startY);
                scrollYAnimator = ObjectAnimator.ofInt(view, "scrollY", startY, endY);
            }
            return TransitionUtils.mergeAnimators(scrollXAnimator, scrollYAnimator);
        }
    }
    
    2.1.5 ChangeTransform :检测view的scale和rotation创建缩放和旋转动画

    捕获共享元素的缩放(scale)与旋转(rotation)属性 ,然后播放缩放(scale)与旋转(rotation)属性变化动画。

    GIFchangetransform.gif
    /**
     * This Transition captures scale and rotation for Views before and after the
     * scene change and animates those changes during the transition.
     *
     * A change in parent is handled as well by capturing the transforms from
     * the parent before and after the scene change and animating those during the
     * transition.
     */
    public class ChangeTransform extends Transition {
    
        ...
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
                TransitionValues endValues) {
            if (startValues == null || endValues == null ||
                    !startValues.values.containsKey(PROPNAME_PARENT) ||
                    !endValues.values.containsKey(PROPNAME_PARENT)) {
                return null;
            }
            ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
            ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
            boolean handleParentChange = mReparent && !parentsMatch(startParent, endParent);
            Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_INTERMEDIATE_MATRIX);
            if (startMatrix != null) {
                startValues.values.put(PROPNAME_MATRIX, startMatrix);
            }
            Matrix startParentMatrix = (Matrix)
                    startValues.values.get(PROPNAME_INTERMEDIATE_PARENT_MATRIX);
            if (startParentMatrix != null) {
                startValues.values.put(PROPNAME_PARENT_MATRIX, startParentMatrix);
            }
            // First handle the parent change:
            if (handleParentChange) {
                setMatricesForParent(startValues, endValues);
            }
            // Next handle the normal matrix transform:
            ObjectAnimator transformAnimator = createTransformAnimator(startValues, endValues,
                    handleParentChange);
            if (handleParentChange && transformAnimator != null && mUseOverlay) {
                createGhostView(sceneRoot, startValues, endValues);
            }
            return transformAnimator;
        }
    
        ...
        /**
         * PathAnimatorMatrix allows the translations and the rest of the matrix to be set
         * separately. This allows the PathMotion to affect the translations while scale
         * and rotation are evaluated separately.
         */
        private static class PathAnimatorMatrix {
          ...
        }
    }
    

    2.2 如何使用Transition

    2.2.1 TransitionManager.go(Scene scene, Transition transition)

    直接在切换Scene时,设置效果

    2.2.2 根据设置的 transition文件 自动生成Animator效果

    在XML中或者在代码中设置,举个例子就是: res->transition文件下创建transition文件

    //xxx.xml Fade、Slide、Explode :渐入、滑动、爆炸
    
    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds/>
        <explode/>
        <fade/>
    </transitionSet>
    
    //或者直接new Fade()等等
    
    fade_and_slide.gif

    设置 transition文件 自动生成Animator效果,通常在两种情况下使用:

    1. 切换Activity/Fragment时设置场景切换效果(这个部分在后续的文章会讲到)
    2. beginDelayedTransition()设置延时动画
    beginDelayedTransition

    为每一个结束关键帧都专门设置一个xml的Scene布局岂不是很麻烦?如图所示,点击每个图片的切换效果都差不多,那是不是就要设置4个Scene,然后对应的点击某个View就跳转到某个Scene呢?

    GIFbegindelayed.gif
    所以,就有了 : 延时动画 :beginDelayedTransition()

    例子:(四个ImageView,点击每一个,那个就会放大,其他三个就会消失)

    <?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/rootView"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_marginStart="72dp"
            android:layout_marginTop="71dp"
            app:srcCompat="@drawable/bangtang"
            android:layout_alignParentTop="true"
            android:layout_alignParentStart="true" />
    
        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="120dp"
            android:layout_height="120dp"
            app:srcCompat="@drawable/shengdanshu"
            android:layout_alignTop="@+id/imageView1"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="30dp" />
    
        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="120dp"
            android:layout_height="120dp"
            app:srcCompat="@drawable/xueren"
            android:layout_alignTop="@+id/imageView4"
            android:layout_alignStart="@+id/imageView1" />
    
        <ImageView
            android:id="@+id/imageView4"
            android:layout_width="120dp"
            android:layout_height="120dp"
            app:srcCompat="@drawable/xunlu"
            android:layout_marginTop="24dp"
            android:layout_below="@+id/imageView2"
            android:layout_alignStart="@+id/imageView2" />
    </RelativeLayout>
    
    // explode_fade_changebounds.xml
    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds/>
        <explode/>
        <fade/>
    </transitionSet>
    
    public class BeginDelayedActivity extends AppCompatActivity implements View.OnClickListener{
    
        ImageView iv1,iv2,iv3,iv4;
        ViewGroup rootView;
    
        boolean isBig=false;
        int primarySize;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_begin_delayed);
            rootView=findViewById(R.id.rootView);
            iv1=findViewById(R.id.imageView1);
            iv2=findViewById(R.id.imageView2);
            iv3=findViewById(R.id.imageView3);
            iv4=findViewById(R.id.imageView4);
            primarySize=iv1.getLayoutParams().width;
            iv1.setOnClickListener(this);
            iv2.setOnClickListener(this);
            iv3.setOnClickListener(this);
            iv4.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            //start scene 是当前的scene
            TransitionManager.beginDelayedTransition(rootView, TransitionInflater.from(this).inflateTransition(R.transition.explode_fade_changebounds));
            //next scene 此时通过代码已改变了scene statue
            changeScene(v);
        }
    
        private void changeScene(View v) {
            changeSize(v);
            changeVisibility(iv1,iv2,iv3,iv4);
            v.setVisibility(View.VISIBLE);
        }
    
    
        private void changeSize(View v) {
    
            isBig=!isBig;
            ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
            if(isBig){
                layoutParams.width=(int)(1.5*primarySize);
                layoutParams.height=(int)(1.5*primarySize);
            }else {
                layoutParams.width=primarySize;
                layoutParams.height=primarySize;
            }
            v.setLayoutParams(layoutParams);
        }
    
        /**
         * VISIBLE和INVISIBLE状态切换
         * @param ivs
         */
        private void changeVisibility(ImageView ... ivs) {
            for (View view:ivs){
                view.setVisibility(view.getVisibility()==View.VISIBLE?View.INVISIBLE:View.VISIBLE);
            }
        }
    }
    

    我们在这做详细的分析 :
    假设最开始每个view都是可见的:

    1. 当点击事件发生之后调用TransitionManager的beginDelayedTransition()方法,并且传递了mRootView和一个Fade对象最为参数。之后,framework会立即调用transition类的captureStartValues()方法为每个view保存其当前的可见状态(visibility)。
    2. 当beginDelayedTransition返回之后,在上面的代码中将每个view设置为不可见。
    3. 在接下来的显示中framework会调用transition类的captureEndValues()方法,记录每个view最新的可见状态。
    4. 接着,framework调用transition的createAnimator()方法。transition会分析每个view的开始和结束时的数据发现view在开始时是可见的,结束时是不可见的。Fade(transition的子类)会利用这些信息创建一个用于把view的alpha属性变为0的AnimatorSet,并且将此AnimatorSet对象返回。
    5. framework会运行返回的Animator,导致所有的View都渐渐消失。

    这样就达到了:通过属性的改变,就发生动画...达到了代码的精简


    3. 总结

    1. 创建两个Scene(起始关键帧 和 结束关键帧)
    2. 利用系统内置的或自定义的transitions创建Animator
    3. 开启动画

    这样就是实现了简单的一个动画效果,这个过程我们只关心 开始状态和结束状态,并为状态的变化规定了变化规律(transitions),然后自动帮我们生成效果


    transitions_diagram.png

    由此可见 :transition框架的两个主要优点
    第一、Transitions抽象和封装了属性动画,Animator的概念对开发者来说是透明的,因此它极大的精简了代码量。开发者所做的所有事情只是改变一下view前后的状态数据,Transition就会自动的根据状态的区别去生成动画效果。第二、不同场景之间变换的动画效果可以简单的通过使用不同的Transition类来改变

    基础的介绍就先讲到这里!!!
    上面讲到了基础的 TransitionManager.go()beginDelayedTransition() 开启动画。其实还有一种开启方式更为常见 :setEnterTransition()/setSharedElementEnterTransition() //当然,这得看下回分解

    Transition系列文章
    一、初识Transition—实现两个场景的变换
    二、番外篇 Transition之ViewOverlay
    三、定义 界面指定元素 或界面间共享元素 的转场动画基础
    四、Content Transition实现非共享元素转场
    五、SharedElementTransition之Activity间的转场
    六、SharedElementTransition之Fragment间的转场
    七、番外篇- 自定义Visibility
    八、5.0以下实现共享转场

    本篇参考:
    Activity和Fragment Transition介绍
    Android 过渡(Transition)动画解析之基础篇
    animatedTransitionsLearn-master

    相关文章

      网友评论

        本文标题:一、初识Transition—实现两个场景的变换

        本文链接:https://www.haomeiwen.com/subject/igymaftx.html