Transition的使用分析

Transition

概念

Transition中,有一些新的概念,例如Scene, Transition, TransitionSet, TransitionManager. 除了Scene, 其他的概念其实和简单的View动画差不多。

Scene

Scene的构造

Scene是一个新的概念,它被翻译成了“场景”,这个描述很准确,但是比较难以理解。下面通过源码分析

    /**
     * Constructs a Scene which, when entered, will remove any
     * children from the sceneRoot container and add the layout
     * object as a new child of that container.
     *
     * @param sceneRoot The root of the hierarchy in which scene changes
     * and transitions will take place.
     * @param layout The view hierarchy of this scene, added as a child
     * of sceneRoot when this scene is entered.
     */
    public Scene(ViewGroup sceneRoot, View layout) {
        mSceneRoot = sceneRoot;
        mLayout = layout;
    }

上面是Scene的一个构造器。注释:构造一个场景,当进入这个场景时,将从场景的sceneRoot中移除其他所有的children, 并添加这个layout作为这个sceneRoot的新child .

sceneRoot的解释: 发生场景改变和Transition所在的层次的根 .

layout的解释:这个场景指定的view, 当进入这个场景时,被添加到sceneRoot的view, 作为sceneRoot的child .

下面是Scene的另一个构造器

    /**
     * Constructs a Scene which, when entered, will remove any
     * children from the sceneRoot container and will inflate and add
     * the hierarchy specified by the layoutId resource file.
     *
     * <p>This method is hidden because layoutId-based scenes should be
     * created by the caching factory method {@link Scene#getCurrentScene(View)}.</p>
     *
     * @param sceneRoot The root of the hierarchy in which scene changes
     * and transitions will take place.
     * @param layoutId The id of a resource file that defines the view
     * hierarchy of this scene.
     * @param context The context used in the process of inflating
     * the layout resource.
     */
    private Scene(ViewGroup sceneRoot, int layoutId, Context context) {
        mContext = context;
        mSceneRoot = sceneRoot;
        mLayoutId = layoutId;
    }

注释:构造一个场景,当进入这个场景时,将从场景的sceneRoot中移除其他所有的children, 并inflate和添加由layoutId指定的资源文件的view层。 这是一个私有的构造器,因为基于layoutId构造的场景,使用了缓存,即可以进行多次复用。

下面是会调用上面的私有构造器的一个静态方法

    /**
     * Returns a Scene described by the resource file associated with the given
     * <code>layoutId</code> parameter. If such a Scene has already been created for
     * the given <code>sceneRoot</code>, that same Scene will be returned.
     * This caching of layoutId-based scenes enables sharing of common scenes
     * between those created in code and those referenced by {@link TransitionManager}
     * XML resource files.
     *
     * @param sceneRoot The root of the hierarchy in which scene changes
     * and transitions will take place.
     * @param layoutId The id of a standard layout resource file.
     * @param context The context used in the process of inflating
     * the layout resource.
     * @return The scene for the given root and layout id
     */
    public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) {
        SparseArray<Scene> scenes = (SparseArray<Scene>) sceneRoot.getTag(
                com.android.internal.R.id.scene_layoutid_cache);
        if (scenes == null) {
            scenes = new SparseArray<Scene>();
            sceneRoot.setTagInternal(com.android.internal.R.id.scene_layoutid_cache, scenes);
        }
        Scene scene = scenes.get(layoutId);
        if (scene != null) {
            return scene;
        } else {
            scene = new Scene(sceneRoot, layoutId, context);
            scenes.put(layoutId, scene);
            return scene;
        }
    }

注释:返回一个由layoutId指定的资源文件描述的Scene. 如果这个场景已经被sceneRoot创建过了,将返回同样的场景(复用). 进入layoutId的场景缓存,使得那些在代码中创建和在xml文件中引用的相同的场景能够共享(不太明白这为什么能够共享代码中创建的和在xml文件中描述的) .

代码解释:使用了View.setTag的方式缓存场景列表,当需要根据layoutId创建一个场景时,如果发现sceneRoot中已经缓存的场景列表中包含了该场景,那么直接返回,否则创建一个新的场景。

Scene的分析

下面是Scene.enter()方法

    /**
     * Enters this scene, which entails changing all values that
     * are specified by this scene. These may be values associated
     * with a layout view group or layout resource file which will
     * now be added to the scene root, or it may be values changed by
     * an {@link #setEnterAction(Runnable)} enter action}, or a
     * combination of the these. No transition will be run when the
     * scene is entered. To get transition behavior in scene changes,
     * use one of the methods in {@link TransitionManager} instead.
     */
    public void enter() {

        // Apply layout change, if any
        if (mLayoutId > 0 || mLayout != null) {
            // empty out parent container before adding to it
            getSceneRoot().removeAllViews();

            if (mLayoutId > 0) {
                LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
            } else {
                mSceneRoot.addView(mLayout);
            }
        }

        // Notify next scene that it is entering. Subclasses may override to configure scene.
        if (mEnterAction != null) {
            mEnterAction.run();
        }

        setCurrentScene(mSceneRoot, this);
    }

从这个方法中,我们就能对场景有一个比较深入的了解了。

注释:进入这个场景,在这个场景中需要改变由这个场景指定的所有values. 这些values可能和将被添加到sceneRoot中的view又换,或是跟被使用setEnterAction(Runnable)指定的进入动作改变。在这个方法中并没有使用Transition, 所以当进入这个场景时不会有Transition触发。为了在场景发生改变时能够进行Transition变换动作,那么应该使用TransitionManager方法中的其中一个。

代码解释:当在这个场景中制定了layoutId或是view, 那么则移除sceneRoot中的所有children, 并且添加layoutId指定的view或是view, 最后如果设置了进入的动作,那么调用该动作。最终的setCurrentScene方法指定sceneRoot当前的场景。

下面是Scene.exit()方法

    /**
     * Exits this scene, if it is the current scene
     * on the scene's {@link #getSceneRoot() scene root}. The current scene is
     * set when {@link #enter() entering} a scene.
     * Exiting a scene runs the {@link #setExitAction(Runnable) exit action}
     * if there is one.
     */
    public void exit() {
        if (getCurrentScene(mSceneRoot) == this) {
            if (mExitAction != null) {
                mExitAction.run();
            }
        }
    }

这个方法则很简单,并没有对sceneRoot的进行任何的改变。不清楚会对sceneRoot产生什么样的影响。

注释:如果这个场景时sceneRoot的当前场景,则退出这个场景。

Transition

简单分析:Transition是一个抽象类,保留了两个抽象方法由子类实现captureStartValues(TransitionValues transitionValues)captureEndValues(TransitionValues transitionValues). 而Transition有一个常见的子类Visibility, Android中已经有的很多Transition的子类都是继承自这个Visibility.

直接继承自Transition的类:ChangeBounds, ChangeClipBounds, ChangeImageTransform, ChangeScroll, ChangeTransform, TransitionSet

继承自Visibility的类:Explode, Fade, Slide

下面是Visibility的注释

/**
 * This transition tracks changes to the visibility of target views in the
 * start and end scenes. Visibility is determined not just by the
 * {@link View#setVisibility(int)} state of views, but also whether
 * views exist in the current view hierarchy. The class is intended to be a
 * utility for subclasses such as {@link Fade}, which use this visibility
 * information to determine the specific animations to run when visibility
 * changes occur. Subclasses should implement one or both of the methods
 * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
 * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or
 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},
 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
 */
public abstract class Visibility extends Transition {
    // 省略...
}

注释:这个transition跟踪目标view在场景开始和场景结束时visibility的变化。Visibility不仅仅是决定view的visibility状态, 也决定了view是否存在在当前sceneRoot的view层次中。使用这个类想为其子类作为一个工具,例如Fade这种,使用view的visibility信息来决定当visibility发生变化时特定动画的运行。子类应该实现其中一个或成组的两个方法{@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}, {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}{@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},{@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.

下面是Transition的注释

/**
 * A Transition holds information about animations that will be run on its
 * targets during a scene change. Subclasses of this abstract class may
 * choreograph several child transitions ({@link TransitionSet} or they may
 * perform custom animations themselves. Any Transition has two main jobs:
 * (1) capture property values, and (2) play animations based on changes to
 * captured property values. A custom transition knows what property values
 * on View objects are of interest to it, and also knows how to animate
 * changes to those values. For example, the {@link Fade} transition tracks
 * changes to visibility-related properties and is able to construct and run
 * animations that fade items in or out based on changes to those properties.
 *
 * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
 * or {@link TextureView}, due to the way that these views are displayed
 * on the screen. For SurfaceView, the problem is that the view is updated from
 * a non-UI thread, so changes to the view due to transitions (such as moving
 * and resizing the view) may be out of sync with the display inside those bounds.
 * TextureView is more compatible with transitions in general, but some
 * specific transitions (such as {@link Fade}) may not be compatible
 * with TextureView because they rely on {@link ViewOverlay} functionality,
 * which does not currently work with TextureView.</p>
 *
 * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
 * directory. Transition resources consist of a tag name for one of the Transition
 * subclasses along with attributes to define some of the attributes of that transition.
 * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:
 *
 * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
 *
 * <p>This TransitionSet contains {@link android.transition.Explode} for visibility,
 * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform},
 * and {@link android.transition.ChangeClipBounds} and
 * {@link android.transition.ChangeImageTransform}:</p>
 *
 * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
 *
 * <p>Custom transition classes may be instantiated with a <code>transition</code> tag:</p>
 * <pre>&lt;transition class="my.app.transition.CustomTransition"/></pre>
 * <p>Custom transition classes loaded from XML should have a public constructor taking
 * a {@link android.content.Context} and {@link android.util.AttributeSet}.</p>
 *
 * <p>Note that attributes for the transition are not required, just as they are
 * optional when declared in code; Transitions created from XML resources will use
 * the same defaults as their code-created equivalents. Here is a slightly more
 * elaborate example which declares a {@link TransitionSet} transition with
 * {@link ChangeBounds} and {@link Fade} child transitions:</p>
 *
 * {@sample
 * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet}
 *
 * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
 * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
 * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
 * transition uses a fadingMode of {@link Fade#OUT} instead of the default
 * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
 * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
 * of which lists a specific <code>targetId</code>, <code>targetClass</code>,
 * <code>targetName</code>, <code>excludeId</code>, <code>excludeClass</code>, or
 * <code>excludeName</code>, which this transition acts upon.
 * Use of targets is optional, but can be used to either limit the time spent checking
 * attributes on unchanging views, or limiting the types of animations run on specific views.
 * In this case, we know that only the <code>grayscaleContainer</code> will be
 * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
 *
 * Further information on XML resource descriptions for transitions can be found for
 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
 * {@link android.R.styleable#Slide}, and {@link android.R.styleable#ChangeTransform}.
 *
 */
public abstract class Transition implements Cloneable {
    // 省略...
}

注释翻译:一个Transition拥有在它的目标view在场景切换时进行的动画的信息。这个类的子类可以使用TransitionSet组合多个子Transition, 或他们也可以进行自定义的动画。所有的Transition有两个主要的工作,1是捕获属性信息,2是基于属性信息的改变进行动画。一个自定义的Transition知道目标view它所关心的信息,也知道如果将这些信息的改变进行动画。例如,Fade跟踪visibility相关的属性,而且能够根据这些信息构造和运行动画,让这些item进行淡入或淡出。

注意:Transition可能不会再SurfaceView或TextureView上正确的执行, 因为这些view的更新不是在主线程…

我们可以在xml中定义Transition. Transition资源中以Transition子类的名字作为tag, 并且可以添加一些attributes作为Transition的属性。

TransitionManager

目前还不清楚TransitionManager的具体管理Transition的方式是什么。就使用而言,除了可以在代码中直接构造,还可以通过在xml中定义的方式来获取。

// transition-v21/manager
<transitionManager xmlns:android="http://schemas.android.com/apk/res/android">

    <transition
        android:fromScene="@layout/card_one"
        android:toScene="@layout/card_two"
        android:transition="@transition/decrease"/>

    <transition
        android:fromScene="@layout/card_two"
        android:toScene="@layout/card_one"
        android:transition="@android:transition/move"/>

</transitionManager>
// transition-v21/descrease
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <fade android:fadingMode="fade_out">
        <targets android:targetId="@id/red"/>
    </fade>
    <fade android:fadingMode="fade_out">
        <targets android:targetId="@id/blue"/>
    </fade>

    <changeBounds>
        <targets android:targetId="@id/green"/>
    </changeBounds>
    <changeBounds>
        <targets android:targetId="@id/yellow"/>
    </changeBounds>
</transitionSet>
// layout/card_one
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/red"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:background="@android:color/holo_red_light"/>

    <TextView
        android:id="@+id/blue"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_toEndOf="@id/red"
        android:background="@android:color/holo_blue_bright"/>

    <TextView
        android:id="@+id/green"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_below="@id/red"
        android:background="@android:color/holo_green_light"/>

    <TextView
        android:id="@+id/yellow"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_alignTop="@id/green"
        android:layout_toEndOf="@id/green"
        android:background="@android:color/holo_orange_light"/>
</RelativeLayout>
// layout/card_two
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/green"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:background="@android:color/holo_green_light"/>

    <TextView
        android:id="@+id/yellow"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_alignTop="@id/green"
        android:layout_toEndOf="@id/green"
        android:background="@android:color/holo_orange_light"/>
</RelativeLayout>

如上,在xml中定义一个TransitionManager, 在transitionManager中可以定义多个transition .

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        TransitionManager transitionManager = TransitionInflater.from(this).inflateTransitionManager(R.transition.manger, mContainer);
        if (isFirst) {
            isFirst = false;
            transitionManager.transitionTo(Scene.getSceneForLayout(mContainer, R.layout.card_one, this));
        } else {
            isFirst = true;
            transitionManager.transitionTo(Scene.getSceneForLayout(mContainer, R.layout.card_two, this));
        }
    }

如上,在代码中inflate出在xml中定义的TransitionManager, 然后进行动画。

这里可以看一下TransitionInflater.inflateTransitionManager方法进行inflate的过程

private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
        AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {

    // Make sure we are on a start tag.
    int type;
    int depth = parser.getDepth();
    TransitionManager transitionManager = null;

    while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
            && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        String  name = parser.getName();
        if (name.equals("transitionManager")) {
            transitionManager = new TransitionManager();
        } else if (name.equals("transition") && (transitionManager != null)) {
            loadTransition(attrs, sceneRoot, transitionManager);
        } else {
            throw new RuntimeException("Unknown scene name: " + parser.getName());
        }
    }
    return transitionManager;
}

private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
        TransitionManager transitionManager) throws Resources.NotFoundException {

    TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionManager);
    int transitionId = a.getResourceId(R.styleable.TransitionManager_transition, -1);
    int fromId = a.getResourceId(R.styleable.TransitionManager_fromScene, -1);
    Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
    int toId = a.getResourceId(R.styleable.TransitionManager_toScene, -1);
    Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);

    if (transitionId >= 0) {
        Transition transition = inflateTransition(transitionId);
        if (transition != null) {
            if (toScene == null) {
                throw new RuntimeException("No toScene for transition ID " + transitionId);
            }
            if (fromScene == null) {
                transitionManager.setTransition(toScene, transition);
            } else {
                transitionManager.setTransition(fromScene, toScene, transition);
            }
        }
    }
    a.recycle();
}

分析:在inflate TransitionManager的过程中,实际上是直接构造TransitionManger的。对其中的transition标签进行处理,也是调用TransitionManager.setTransition(Scene, Scene, Transiton)方法。这个方法有一个重载方法。在上面可以看到,toScene是必须存在的,如果fromScene存在,则调用TransitionManager.setTransition(Scene, Scene, Transiton), 否则调用TransitionManager.setTransition(Scene, Transiton).

我们可以从中看到什么呢?这个需要从TransitionManager.transitionTo(Scene)中看出来

/**
    * Change to the given scene, using the
    * appropriate transition for this particular scene change
    * (as specified to the TransitionManager, or the default
    * if no such transition exists).
    *
    * @param scene The Scene to change to
    */
public void transitionTo(Scene scene) {
    // Auto transition if there is no transition declared for the Scene, but there is
    // a root or parent view
    changeScene(scene, getTransition(scene));
}

注释翻译:变换到给定的场景,使用为这个场景变换指定的合适的Transition. (如果已经在TransitionManger中设置了, 或者使用默认的).

这里需要着重看括号中的部分,从其中我们可以猜到,实际上,调用TransitionManager.setTransition(就是为Scene变换设置一个Transition, 如果我们之前scene设置了一个Transition, 那么我们使用的是我们设置的Transition, 否则就是使用默认的AutoTransition. 其实,将setTransition改为putTransition更为合适。

使用

TransitionManager.beginDelayedTransition(ViewGroup sceneRoot, Transition transition)

很大一部分动画其实都可以使用这个方法来进行动画。这个方法有一个重载方法TransitionManager.beginDelayedTransition(ViewGroup sceneRoot), 在这个重载方法中,使用一个默认的AutoTransition.

    public void transition(View view) {
        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return;
        }
        final ViewGroup parent = (ViewGroup) findViewById(R.id.parent);

        final ViewGroup container = (ViewGroup) findViewById(R.id.container);

        TransitionManager.beginDelayedTransition(parent, new ChangeBounds());
        container.removeViewAt(0);
        container.removeViewAt(0);
    }

如上,有一个id为parent的ViewGroup, 其中有一个id为container的ViewGroup, 在container中,还有多个children, 我们以id为parent的ViewGroup作为sceneRoot, 在改变其hirerarchy前调用TransitionManager.beginDelayedTransition(ViewGroup sceneRoot, Transition transition), 然后对其hirerarchy进行操作, 这样就完成了动画的操作。

这里需要说明一下,我们以id为parent的ViewGroup作为sceneRoot, 而操作的并不是它的直接children, 也产生了效果,说明操作的是以sceneRoot为根的整个hirerarchy.

对于原理,这里暂时放在后面进行分析。

TransitionManager.go(Scene, Transition)

在这个方法中使用到了Scene的概念,当然上面的方法也使用到了Scene的概念,只是在方法上并没有暴露。对于Scene, 我们在上面已经分析了,进行场景切换时,是将sceneRoot中原来的所有的children移除,然后添加在Scene中指定的view.

这个方法也有一个重载方法TransitionManager.go(Scene), 其中也会使用默认的AutoTransition

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void remove(View view) {
        final ViewGroup group = (ViewGroup) findViewById(R.id.container);
        final Scene scene = new Scene(group, group.getChildAt(0));
        TransitionManager.go(scene);
    }

如上所示,我们将一个id为container的ViewGroup作为sceneRoot, 然后将其第一个child作为场景切换后添加到sceneRoot中的view, 然后调用TransitionManager.go(Scene)方法。当然,这样达到的效果和使用TransitionManager.beginDelayedTransition(ViewGroup sceneRoot, Transition transition)的效果是一样的。我们也可以像上面一样使用,保留id为container的第一个child, 移除其他所有的children, 在这些操作之前调用TransitionManager.beginDelayedTransition(ViewGroup sceneRoot, Transition transition)“`方法即可。

TransitionManager.transitionTo(Scene)

在上面我们已经演示了这个方法的使用。这里就不再赘述。

总结

在这里,其实已经对Transition框架的使用做了完整的分析,能够处理大部分的动画。Transition的使用也就只有上面三种方式。

如果需要自定义Transition, 那么就需要对Transition的实现进行分析了,这里暂时不做分析。

参考

使用 Transition API为安卓应用创建动画

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值