一、初識(shí)Transition—實(shí)現(xiàn)兩個(gè)場(chǎng)景的變換

誕生于4.4的transition框架為在不同的UI狀態(tài)之間產(chǎn)生動(dòng)畫(huà)效果提供了非常方便的API。5.0中Activity和Fragment 轉(zhuǎn)場(chǎng)變換也是建立在Transitions框架的新特性之上的。

該框架主要基于兩個(gè)概念:scenes(場(chǎng)景)和transitions(變換)

1. Secene

Transition Framework 核心就是根據(jù)Scene的不同幫助開(kāi)發(fā)者們自動(dòng)生成動(dòng)畫(huà)。

官方文檔
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.

通俗的解釋就是這個(gè)類存儲(chǔ)著一個(gè)根view下的各種view的屬性。

創(chuàng)建Scene

創(chuàng)建一個(gè) Scene有兩種方法

// sceneRoot是Scene的 Container,也可以說(shuō)是它的根布局
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); //先把初始狀態(tài)設(shè)置為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);//先把初始狀態(tài)設(shè)置為scene1

sceneRoot 在動(dòng)畫(huà)開(kāi)始時(shí),會(huì)將sceneRoot中的所有子View都remove掉,然后在sceneRoot 中加載我們的end Scene。

所以,對(duì)于end Scene,如果是通過(guò)代碼new Scene(mSceneRoot, view)創(chuàng)建的Scene其實(shí)對(duì)于view是有要求的:view是沒(méi)有parentview的,不然在addview的時(shí)候會(huì)報(bào)錯(cuò)
驗(yàn)證代碼如下:

//測(cè)試該段代碼
        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的效果對(duì)比圖

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);//先把初始狀態(tài)設(shè)置為scene1
    }

    protected void clickChange(){
        TransitionManager.go(isScene1?scene2:scene1,getTransition());
        isScene1=!isScene1;
    }

    Transition getTransition() {
        return new ChangeBounds();
    }

getTransition方法提供的是兩個(gè)Scene的切換效果,下面會(huì)講到,所以先不管它。
最后出來(lái)的效果是:



scene1和scene2的 Id 對(duì)比圖

根據(jù)效果圖和Id對(duì)比圖可以發(fā)現(xiàn)切換時(shí)是相同id的View之間互相切換,那么如果兩個(gè)Scene之間View的Id不對(duì)等(id不相同 或者 一多一少)呢?


View的Id不對(duì)等

這個(gè)時(shí)候可以試著運(yùn)行一下就會(huì)發(fā)現(xiàn):仍然可以實(shí)現(xiàn)兩個(gè)場(chǎng)景的切換,但是切換過(guò)程沒(méi)有任何動(dòng)畫(huà)效果(即Transition沒(méi)有起作用),就只是簡(jiǎn)單的替換。


上面例子getTransition方法返回ChangeBounds(),其實(shí)就是一種Transition的實(shí)現(xiàn),下面來(lái)詳細(xì)了解一下:

2. transitions

當(dāng)一個(gè)Scene發(fā)生改變時(shí),transition主要負(fù)責(zé):

  1. 捕捉每個(gè)View在開(kāi)始場(chǎng)景和結(jié)束場(chǎng)景時(shí)的狀態(tài)。
  2. 根據(jù)兩個(gè)場(chǎng)景(開(kāi)始和結(jié)束)之間的區(qū)別創(chuàng)建一個(gè)Animator。

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

2.1.1 ChangeBounds :檢測(cè)view的位置邊界創(chuàng)建移動(dòng)和縮放動(dòng)畫(huà)

捕獲共享元素的layout bound,然后播放layout bound變化動(dòng)畫(huà)。ChangeBounds 是共享元素變換中用的最多的,因?yàn)榍昂髢蓚€(gè)activity中共享元素的大小和位置一般都是不同的。

根據(jù)始末位置畫(huà)出Path,再根據(jù)Path創(chuàng)造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 :檢測(cè)view的剪切區(qū)域的位置邊界,和ChangeBounds類似。不過(guò)ChangeBounds針對(duì)的是view而ChangeClipBounds針對(duì)的是view的剪切區(qū)域(setClipBound(Rect rect) 中的rect)。如果沒(méi)有設(shè)置則沒(méi)有動(dòng)畫(huà)效果

捕獲共享元素clip bounds,然后播放clip bounds變化動(dòng)畫(huà)。

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 :檢測(cè)ImageView(這里是專指ImageView)的尺寸,位置以及ScaleType,并創(chuàng)建相應(yīng)動(dòng)畫(huà)。

捕獲共享元素(ImageView)的transform matrices 屬性,然后播放ImageViewtransform matrices 屬性變化動(dòng)畫(huà)。與ChangeBounds相結(jié)合,這個(gè)變換可以讓ImageView在動(dòng)畫(huà)中高效實(shí)現(xiàn)大小,形狀或者ImageView.ScaleType

屬性平滑過(guò)度。

/**
 * 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 :滑動(dòng)的屬性發(fā)生了變化

/**
 * 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 :檢測(cè)view的scale和rotation創(chuàng)建縮放和旋轉(zhuǎn)動(dòng)畫(huà)

捕獲共享元素的縮放(scale)與旋轉(zhuǎn)(rotation)屬性 ,然后播放縮放(scale)與旋轉(zhuǎn)(rotation)屬性變化動(dòng)畫(huà)。

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時(shí),設(shè)置效果

2.2.2 根據(jù)設(shè)置的 transition文件 自動(dòng)生成Animator效果

在XML中或者在代碼中設(shè)置,舉個(gè)例子就是: res->transition文件下創(chuàng)建transition文件

//xxx.xml Fade、Slide、Explode :漸入、滑動(dòng)、爆炸

<?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

設(shè)置 transition文件 自動(dòng)生成Animator效果,通常在兩種情況下使用:

  1. 切換Activity/Fragment時(shí)設(shè)置場(chǎng)景切換效果(這個(gè)部分在后續(xù)的文章會(huì)講到)
  2. beginDelayedTransition()設(shè)置延時(shí)動(dòng)畫(huà)
beginDelayedTransition

為每一個(gè)結(jié)束關(guān)鍵幀都專門(mén)設(shè)置一個(gè)xml的Scene布局豈不是很麻煩?如圖所示,點(diǎn)擊每個(gè)圖片的切換效果都差不多,那是不是就要設(shè)置4個(gè)Scene,然后對(duì)應(yīng)的點(diǎn)擊某個(gè)View就跳轉(zhuǎn)到某個(gè)Scene呢?

GIFbegindelayed.gif

所以,就有了 : 延時(shí)動(dòng)畫(huà) :beginDelayedTransition()

例子:(四個(gè)ImageView,點(diǎn)擊每一個(gè),那個(gè)就會(huì)放大,其他三個(gè)就會(huì)消失)

<?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 是當(dāng)前的scene
        TransitionManager.beginDelayedTransition(rootView, TransitionInflater.from(this).inflateTransition(R.transition.explode_fade_changebounds));
        //next scene 此時(shí)通過(guò)代碼已改變了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狀態(tài)切換
     * @param ivs
     */
    private void changeVisibility(ImageView ... ivs) {
        for (View view:ivs){
            view.setVisibility(view.getVisibility()==View.VISIBLE?View.INVISIBLE:View.VISIBLE);
        }
    }
}

我們?cè)谶@做詳細(xì)的分析 :
假設(shè)最開(kāi)始每個(gè)view都是可見(jiàn)的:

  1. 當(dāng)點(diǎn)擊事件發(fā)生之后調(diào)用TransitionManager的beginDelayedTransition()方法,并且傳遞了mRootView和一個(gè)Fade對(duì)象最為參數(shù)。之后,framework會(huì)立即調(diào)用transition類的captureStartValues()方法為每個(gè)view保存其當(dāng)前的可見(jiàn)狀態(tài)(visibility)。
  2. 當(dāng)beginDelayedTransition返回之后,在上面的代碼中將每個(gè)view設(shè)置為不可見(jiàn)。
  3. 在接下來(lái)的顯示中framework會(huì)調(diào)用transition類的captureEndValues()方法,記錄每個(gè)view最新的可見(jiàn)狀態(tài)。
  4. 接著,framework調(diào)用transition的createAnimator()方法。transition會(huì)分析每個(gè)view的開(kāi)始和結(jié)束時(shí)的數(shù)據(jù)發(fā)現(xiàn)view在開(kāi)始時(shí)是可見(jiàn)的,結(jié)束時(shí)是不可見(jiàn)的。Fade(transition的子類)會(huì)利用這些信息創(chuàng)建一個(gè)用于把view的alpha屬性變?yōu)?的AnimatorSet,并且將此AnimatorSet對(duì)象返回。
  5. framework會(huì)運(yùn)行返回的Animator,導(dǎo)致所有的View都漸漸消失。

這樣就達(dá)到了:通過(guò)屬性的改變,就發(fā)生動(dòng)畫(huà)...達(dá)到了代碼的精簡(jiǎn)


3. 總結(jié)

  1. 創(chuàng)建兩個(gè)Scene(起始關(guān)鍵幀 和 結(jié)束關(guān)鍵幀)
  2. 利用系統(tǒng)內(nèi)置的或自定義的transitions創(chuàng)建Animator
  3. 開(kāi)啟動(dòng)畫(huà)

這樣就是實(shí)現(xiàn)了簡(jiǎn)單的一個(gè)動(dòng)畫(huà)效果,這個(gè)過(guò)程我們只關(guān)心 開(kāi)始狀態(tài)和結(jié)束狀態(tài),并為狀態(tài)的變化規(guī)定了變化規(guī)律(transitions),然后自動(dòng)幫我們生成效果


transitions_diagram.png

由此可見(jiàn) :transition框架的兩個(gè)主要優(yōu)點(diǎn)
第一、Transitions抽象和封裝了屬性動(dòng)畫(huà),Animator的概念對(duì)開(kāi)發(fā)者來(lái)說(shuō)是透明的,因此它極大的精簡(jiǎn)了代碼量。開(kāi)發(fā)者所做的所有事情只是改變一下view前后的狀態(tài)數(shù)據(jù),Transition就會(huì)自動(dòng)的根據(jù)狀態(tài)的區(qū)別去生成動(dòng)畫(huà)效果。第二、不同場(chǎng)景之間變換的動(dòng)畫(huà)效果可以簡(jiǎn)單的通過(guò)使用不同的Transition類來(lái)改變

基礎(chǔ)的介紹就先講到這里!!!
上面講到了基礎(chǔ)的 TransitionManager.go()beginDelayedTransition() 開(kāi)啟動(dòng)畫(huà)。其實(shí)還有一種開(kāi)啟方式更為常見(jiàn) :setEnterTransition()/setSharedElementEnterTransition() //當(dāng)然,這得看下回分解

Transition系列文章
一、初識(shí)Transition—實(shí)現(xiàn)兩個(gè)場(chǎng)景的變換
二、番外篇 Transition之ViewOverlay
三、定義 界面指定元素 或界面間共享元素 的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)基礎(chǔ)
四、Content Transition實(shí)現(xiàn)非共享元素轉(zhuǎn)場(chǎng)
五、SharedElementTransition之Activity間的轉(zhuǎn)場(chǎng)
六、SharedElementTransition之Fragment間的轉(zhuǎn)場(chǎng)
七、番外篇- 自定義Visibility
八、5.0以下實(shí)現(xiàn)共享轉(zhuǎn)場(chǎng)

本篇參考:
Activity和Fragment Transition介紹
Android 過(guò)渡(Transition)動(dòng)畫(huà)解析之基礎(chǔ)篇
animatedTransitionsLearn-master

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容