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

诞生于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




    

    

    


//scene2.xml




    

    

    


一、初识Transition—实现两个场景的变换_第1张图片
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的切换效果,下面会讲到,所以先不管它。
最后出来的效果是:


一、初识Transition—实现两个场景的变换_第2张图片

一、初识Transition—实现两个场景的变换_第3张图片
scene1和scene2的 Id 对比图

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


一、初识Transition—实现两个场景的变换_第4张图片
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

一、初识Transition—实现两个场景的变换_第5张图片
GIFchangebounds.gif

/**
 * This transition captures the layout bounds of target views before and after
 * the scene change and animates those changes during the transition.
 *
 * 

A ChangeBounds transition can be described in a resource file by using the * tag changeBounds, using its attributes of * {@link android.R.styleable#ChangeBounds} along with the other standard * attributes of {@link android.R.styleable#Transition}.

*/ public class ChangeBounds extends Transition { ... }
2.1.2 ChangeClipBounds :检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果

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

一、初识Transition—实现两个场景的变换_第6张图片
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.
 *
 * 

In combination with ChangeBounds, ChangeImageTransform allows ImageViews * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents * smoothly.

*/ 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)属性变化动画。

一、初识Transition—实现两个场景的变换_第7张图片
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 :渐入、滑动、爆炸




    
    


//或者直接new Fade()等等
一、初识Transition—实现两个场景的变换_第8张图片
fade_and_slide.gif

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

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

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

一、初识Transition—实现两个场景的变换_第9张图片
GIFbegindelayed.gif

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

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




    

    

    

    


// explode_fade_changebounds.xml



    
    

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),然后自动帮我们生成效果


一、初识Transition—实现两个场景的变换_第10张图片
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—实现两个场景的变换)