Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.transition;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.animation.TimeInterpolator;
     22 import android.content.Context;
     23 import android.content.res.TypedArray;
     24 import android.content.res.XmlResourceParser;
     25 import android.util.AndroidRuntimeException;
     26 import android.util.AttributeSet;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 
     30 import androidx.annotation.IdRes;
     31 import androidx.annotation.NonNull;
     32 import androidx.annotation.Nullable;
     33 import androidx.annotation.RestrictTo;
     34 import androidx.core.content.res.TypedArrayUtils;
     35 
     36 import java.util.ArrayList;
     37 
     38 /**
     39  * A TransitionSet is a parent of child transitions (including other
     40  * TransitionSets). Using TransitionSets enables more complex
     41  * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and
     42  * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition}
     43  * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by
     44  * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition.
     45  *
     46  * <p>A TransitionSet can be described in a resource file by using the
     47  * tag <code>transitionSet</code>, along with the standard
     48  * attributes of {@code TransitionSet} and {@link Transition}. Child transitions of the
     49  * TransitionSet object can be loaded by adding those child tags inside the
     50  * enclosing <code>transitionSet</code> tag. For example, the following xml
     51  * describes a TransitionSet that plays a Fade and then a ChangeBounds
     52  * transition on the affected view targets:</p>
     53  * <pre>
     54  *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
     55  *             android:transitionOrdering="sequential"&gt;
     56  *         &lt;fade/&gt;
     57  *         &lt;changeBounds/&gt;
     58  *     &lt;/transitionSet&gt;
     59  * </pre>
     60  */
     61 public class TransitionSet extends Transition {
     62     /**
     63      * Flag indicating the the interpolator changed.
     64      */
     65     private static final int FLAG_CHANGE_INTERPOLATOR = 0x01;
     66     /**
     67      * Flag indicating the the propagation changed.
     68      */
     69     private static final int FLAG_CHANGE_PROPAGATION = 0x02;
     70     /**
     71      * Flag indicating the the path motion changed.
     72      */
     73     private static final int FLAG_CHANGE_PATH_MOTION = 0x04;
     74     /**
     75      * Flag indicating the the epicentera callback changed.
     76      */
     77     private static final int FLAG_CHANGE_EPICENTER = 0x08;
     78 
     79     private ArrayList<Transition> mTransitions = new ArrayList<>();
     80     private boolean mPlayTogether = true;
     81     private int mCurrentListeners;
     82     private boolean mStarted = false;
     83     // Flags to know whether or not the interpolator, path motion, epicenter, propagation
     84     // have changed
     85     private int mChangeFlags = 0;
     86 
     87     /**
     88      * A flag used to indicate that the child transitions of this set
     89      * should all start at the same time.
     90      */
     91     public static final int ORDERING_TOGETHER = 0;
     92 
     93     /**
     94      * A flag used to indicate that the child transitions of this set should
     95      * play in sequence; when one child transition ends, the next child
     96      * transition begins. Note that a transition does not end until all
     97      * instances of it (which are playing on all applicable targets of the
     98      * transition) end.
     99      */
    100     public static final int ORDERING_SEQUENTIAL = 1;
    101 
    102     /**
    103      * Constructs an empty transition set. Add child transitions to the
    104      * set by calling {@link #addTransition(Transition)} )}. By default,
    105      * child transitions will play {@link #ORDERING_TOGETHER together}.
    106      */
    107     public TransitionSet() {
    108     }
    109 
    110     public TransitionSet(Context context, AttributeSet attrs) {
    111         super(context, attrs);
    112         TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION_SET);
    113         int ordering = TypedArrayUtils.getNamedInt(a, (XmlResourceParser) attrs,
    114                 "transitionOrdering", Styleable.TransitionSet.TRANSITION_ORDERING,
    115                 TransitionSet.ORDERING_TOGETHER);
    116         setOrdering(ordering);
    117         a.recycle();
    118     }
    119 
    120     /**
    121      * Sets the play order of this set's child transitions.
    122      *
    123      * @param ordering {@link #ORDERING_TOGETHER} to play this set's child
    124      *                 transitions together, {@link #ORDERING_SEQUENTIAL} to play the child
    125      *                 transitions in sequence.
    126      * @return This transitionSet object.
    127      */
    128     @NonNull
    129     public TransitionSet setOrdering(int ordering) {
    130         switch (ordering) {
    131             case ORDERING_SEQUENTIAL:
    132                 mPlayTogether = false;
    133                 break;
    134             case ORDERING_TOGETHER:
    135                 mPlayTogether = true;
    136                 break;
    137             default:
    138                 throw new AndroidRuntimeException("Invalid parameter for TransitionSet "
    139                         + "ordering: " + ordering);
    140         }
    141         return this;
    142     }
    143 
    144     /**
    145      * Returns the ordering of this TransitionSet. By default, the value is
    146      * {@link #ORDERING_TOGETHER}.
    147      *
    148      * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same
    149      * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence.
    150      * @see #setOrdering(int)
    151      */
    152     public int getOrdering() {
    153         return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL;
    154     }
    155 
    156     /**
    157      * Adds child transition to this set. The order in which this child transition
    158      * is added relative to other child transitions that are added, in addition to
    159      * the {@link #getOrdering() ordering} property, determines the
    160      * order in which the transitions are started.
    161      *
    162      * <p>If this transitionSet has a {@link #getDuration() duration},
    163      * {@link #getInterpolator() interpolator}, {@link #getPropagation() propagation delay},
    164      * {@link #getPathMotion() path motion}, or
    165      * {@link #setEpicenterCallback(EpicenterCallback) epicenter callback}
    166      * set on it, the child transition will inherit the values that are set.
    167      * Transitions are assumed to have a maximum of one transitionSet parent.</p>
    168      *
    169      * @param transition A non-null child transition to be added to this set.
    170      * @return This transitionSet object.
    171      */
    172     @NonNull
    173     public TransitionSet addTransition(@NonNull Transition transition) {
    174         mTransitions.add(transition);
    175         transition.mParent = this;
    176         if (mDuration >= 0) {
    177             transition.setDuration(mDuration);
    178         }
    179         if ((mChangeFlags & FLAG_CHANGE_INTERPOLATOR) != 0) {
    180             transition.setInterpolator(getInterpolator());
    181         }
    182         if ((mChangeFlags & FLAG_CHANGE_PROPAGATION) != 0) {
    183             transition.setPropagation(getPropagation());
    184         }
    185         if ((mChangeFlags & FLAG_CHANGE_PATH_MOTION) != 0) {
    186             transition.setPathMotion(getPathMotion());
    187         }
    188         if ((mChangeFlags & FLAG_CHANGE_EPICENTER) != 0) {
    189             transition.setEpicenterCallback(getEpicenterCallback());
    190         }
    191         return this;
    192     }
    193 
    194     /**
    195      * Returns the number of child transitions in the TransitionSet.
    196      *
    197      * @return The number of child transitions in the TransitionSet.
    198      * @see #addTransition(Transition)
    199      * @see #getTransitionAt(int)
    200      */
    201     public int getTransitionCount() {
    202         return mTransitions.size();
    203     }
    204 
    205     /**
    206      * Returns the child Transition at the specified position in the TransitionSet.
    207      *
    208      * @param index The position of the Transition to retrieve.
    209      * @see #addTransition(Transition)
    210      * @see #getTransitionCount()
    211      */
    212     public Transition getTransitionAt(int index) {
    213         if (index < 0 || index >= mTransitions.size()) {
    214             return null;
    215         }
    216         return mTransitions.get(index);
    217     }
    218 
    219     /**
    220      * Setting a non-negative duration on a TransitionSet causes all of the child
    221      * transitions (current and future) to inherit this duration.
    222      *
    223      * @param duration The length of the animation, in milliseconds.
    224      * @return This transitionSet object.
    225      */
    226     @NonNull
    227     @Override
    228     public TransitionSet setDuration(long duration) {
    229         super.setDuration(duration);
    230         if (mDuration >= 0) {
    231             int numTransitions = mTransitions.size();
    232             for (int i = 0; i < numTransitions; ++i) {
    233                 mTransitions.get(i).setDuration(duration);
    234             }
    235         }
    236         return this;
    237     }
    238 
    239     @NonNull
    240     @Override
    241     public TransitionSet setStartDelay(long startDelay) {
    242         return (TransitionSet) super.setStartDelay(startDelay);
    243     }
    244 
    245     @NonNull
    246     @Override
    247     public TransitionSet setInterpolator(@Nullable TimeInterpolator interpolator) {
    248         mChangeFlags |= FLAG_CHANGE_INTERPOLATOR;
    249         if (mTransitions != null) {
    250             int numTransitions = mTransitions.size();
    251             for (int i = 0; i < numTransitions; ++i) {
    252                 mTransitions.get(i).setInterpolator(interpolator);
    253             }
    254         }
    255         return (TransitionSet) super.setInterpolator(interpolator);
    256     }
    257 
    258     @NonNull
    259     @Override
    260     public TransitionSet addTarget(@NonNull View target) {
    261         for (int i = 0; i < mTransitions.size(); i++) {
    262             mTransitions.get(i).addTarget(target);
    263         }
    264         return (TransitionSet) super.addTarget(target);
    265     }
    266 
    267     @NonNull
    268     @Override
    269     public TransitionSet addTarget(@IdRes int targetId) {
    270         for (int i = 0; i < mTransitions.size(); i++) {
    271             mTransitions.get(i).addTarget(targetId);
    272         }
    273         return (TransitionSet) super.addTarget(targetId);
    274     }
    275 
    276     @NonNull
    277     @Override
    278     public TransitionSet addTarget(@NonNull String targetName) {
    279         for (int i = 0; i < mTransitions.size(); i++) {
    280             mTransitions.get(i).addTarget(targetName);
    281         }
    282         return (TransitionSet) super.addTarget(targetName);
    283     }
    284 
    285     @NonNull
    286     @Override
    287     public TransitionSet addTarget(@NonNull Class targetType) {
    288         for (int i = 0; i < mTransitions.size(); i++) {
    289             mTransitions.get(i).addTarget(targetType);
    290         }
    291         return (TransitionSet) super.addTarget(targetType);
    292     }
    293 
    294     @NonNull
    295     @Override
    296     public TransitionSet addListener(@NonNull TransitionListener listener) {
    297         return (TransitionSet) super.addListener(listener);
    298     }
    299 
    300     @NonNull
    301     @Override
    302     public TransitionSet removeTarget(@IdRes int targetId) {
    303         for (int i = 0; i < mTransitions.size(); i++) {
    304             mTransitions.get(i).removeTarget(targetId);
    305         }
    306         return (TransitionSet) super.removeTarget(targetId);
    307     }
    308 
    309     @NonNull
    310     @Override
    311     public TransitionSet removeTarget(@NonNull View target) {
    312         for (int i = 0; i < mTransitions.size(); i++) {
    313             mTransitions.get(i).removeTarget(target);
    314         }
    315         return (TransitionSet) super.removeTarget(target);
    316     }
    317 
    318     @NonNull
    319     @Override
    320     public TransitionSet removeTarget(@NonNull Class target) {
    321         for (int i = 0; i < mTransitions.size(); i++) {
    322             mTransitions.get(i).removeTarget(target);
    323         }
    324         return (TransitionSet) super.removeTarget(target);
    325     }
    326 
    327     @NonNull
    328     @Override
    329     public TransitionSet removeTarget(@NonNull String target) {
    330         for (int i = 0; i < mTransitions.size(); i++) {
    331             mTransitions.get(i).removeTarget(target);
    332         }
    333         return (TransitionSet) super.removeTarget(target);
    334     }
    335 
    336     @NonNull
    337     @Override
    338     public Transition excludeTarget(@NonNull View target, boolean exclude) {
    339         for (int i = 0; i < mTransitions.size(); i++) {
    340             mTransitions.get(i).excludeTarget(target, exclude);
    341         }
    342         return super.excludeTarget(target, exclude);
    343     }
    344 
    345     @NonNull
    346     @Override
    347     public Transition excludeTarget(@NonNull String targetName, boolean exclude) {
    348         for (int i = 0; i < mTransitions.size(); i++) {
    349             mTransitions.get(i).excludeTarget(targetName, exclude);
    350         }
    351         return super.excludeTarget(targetName, exclude);
    352     }
    353 
    354     @NonNull
    355     @Override
    356     public Transition excludeTarget(int targetId, boolean exclude) {
    357         for (int i = 0; i < mTransitions.size(); i++) {
    358             mTransitions.get(i).excludeTarget(targetId, exclude);
    359         }
    360         return super.excludeTarget(targetId, exclude);
    361     }
    362 
    363     @NonNull
    364     @Override
    365     public Transition excludeTarget(@NonNull Class type, boolean exclude) {
    366         for (int i = 0; i < mTransitions.size(); i++) {
    367             mTransitions.get(i).excludeTarget(type, exclude);
    368         }
    369         return super.excludeTarget(type, exclude);
    370     }
    371 
    372     @NonNull
    373     @Override
    374     public TransitionSet removeListener(@NonNull TransitionListener listener) {
    375         return (TransitionSet) super.removeListener(listener);
    376     }
    377 
    378     @Override
    379     public void setPathMotion(PathMotion pathMotion) {
    380         super.setPathMotion(pathMotion);
    381         mChangeFlags |= FLAG_CHANGE_PATH_MOTION;
    382         for (int i = 0; i < mTransitions.size(); i++) {
    383             mTransitions.get(i).setPathMotion(pathMotion);
    384         }
    385     }
    386 
    387     /**
    388      * Removes the specified child transition from this set.
    389      *
    390      * @param transition The transition to be removed.
    391      * @return This transitionSet object.
    392      */
    393     @NonNull
    394     public TransitionSet removeTransition(@NonNull Transition transition) {
    395         mTransitions.remove(transition);
    396         transition.mParent = null;
    397         return this;
    398     }
    399 
    400     /**
    401      * Sets up listeners for each of the child transitions. This is used to
    402      * determine when this transition set is finished (all child transitions
    403      * must finish first).
    404      */
    405     private void setupStartEndListeners() {
    406         TransitionSetListener listener = new TransitionSetListener(this);
    407         for (Transition childTransition : mTransitions) {
    408             childTransition.addListener(listener);
    409         }
    410         mCurrentListeners = mTransitions.size();
    411     }
    412 
    413     /**
    414      * This listener is used to detect when all child transitions are done, at
    415      * which point this transition set is also done.
    416      */
    417     static class TransitionSetListener extends TransitionListenerAdapter {
    418 
    419         TransitionSet mTransitionSet;
    420 
    421         TransitionSetListener(TransitionSet transitionSet) {
    422             mTransitionSet = transitionSet;
    423         }
    424 
    425         @Override
    426         public void onTransitionStart(@NonNull Transition transition) {
    427             if (!mTransitionSet.mStarted) {
    428                 mTransitionSet.start();
    429                 mTransitionSet.mStarted = true;
    430             }
    431         }
    432 
    433         @Override
    434         public void onTransitionEnd(@NonNull Transition transition) {
    435             --mTransitionSet.mCurrentListeners;
    436             if (mTransitionSet.mCurrentListeners == 0) {
    437                 // All child trans
    438                 mTransitionSet.mStarted = false;
    439                 mTransitionSet.end();
    440             }
    441             transition.removeListener(this);
    442         }
    443 
    444     }
    445 
    446     /**
    447      * @hide
    448      */
    449     @RestrictTo(LIBRARY_GROUP)
    450     @Override
    451     protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
    452             TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
    453             ArrayList<TransitionValues> endValuesList) {
    454         long startDelay = getStartDelay();
    455         int numTransitions = mTransitions.size();
    456         for (int i = 0; i < numTransitions; i++) {
    457             Transition childTransition = mTransitions.get(i);
    458             // We only set the start delay on the first transition if we are playing
    459             // the transitions sequentially.
    460             if (startDelay > 0 && (mPlayTogether || i == 0)) {
    461                 long childStartDelay = childTransition.getStartDelay();
    462                 if (childStartDelay > 0) {
    463                     childTransition.setStartDelay(startDelay + childStartDelay);
    464                 } else {
    465                     childTransition.setStartDelay(startDelay);
    466                 }
    467             }
    468             childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList,
    469                     endValuesList);
    470         }
    471     }
    472 
    473     /**
    474      * @hide
    475      */
    476     @RestrictTo(LIBRARY_GROUP)
    477     @Override
    478     protected void runAnimators() {
    479         if (mTransitions.isEmpty()) {
    480             start();
    481             end();
    482             return;
    483         }
    484         setupStartEndListeners();
    485         if (!mPlayTogether) {
    486             // Setup sequence with listeners
    487             // TODO: Need to add listeners in such a way that we can remove them later if canceled
    488             for (int i = 1; i < mTransitions.size(); ++i) {
    489                 Transition previousTransition = mTransitions.get(i - 1);
    490                 final Transition nextTransition = mTransitions.get(i);
    491                 previousTransition.addListener(new TransitionListenerAdapter() {
    492                     @Override
    493                     public void onTransitionEnd(@NonNull Transition transition) {
    494                         nextTransition.runAnimators();
    495                         transition.removeListener(this);
    496                     }
    497                 });
    498             }
    499             Transition firstTransition = mTransitions.get(0);
    500             if (firstTransition != null) {
    501                 firstTransition.runAnimators();
    502             }
    503         } else {
    504             for (Transition childTransition : mTransitions) {
    505                 childTransition.runAnimators();
    506             }
    507         }
    508     }
    509 
    510     @Override
    511     public void captureStartValues(@NonNull TransitionValues transitionValues) {
    512         if (isValidTarget(transitionValues.view)) {
    513             for (Transition childTransition : mTransitions) {
    514                 if (childTransition.isValidTarget(transitionValues.view)) {
    515                     childTransition.captureStartValues(transitionValues);
    516                     transitionValues.mTargetedTransitions.add(childTransition);
    517                 }
    518             }
    519         }
    520     }
    521 
    522     @Override
    523     public void captureEndValues(@NonNull TransitionValues transitionValues) {
    524         if (isValidTarget(transitionValues.view)) {
    525             for (Transition childTransition : mTransitions) {
    526                 if (childTransition.isValidTarget(transitionValues.view)) {
    527                     childTransition.captureEndValues(transitionValues);
    528                     transitionValues.mTargetedTransitions.add(childTransition);
    529                 }
    530             }
    531         }
    532     }
    533 
    534     @Override
    535     void capturePropagationValues(TransitionValues transitionValues) {
    536         super.capturePropagationValues(transitionValues);
    537         int numTransitions = mTransitions.size();
    538         for (int i = 0; i < numTransitions; ++i) {
    539             mTransitions.get(i).capturePropagationValues(transitionValues);
    540         }
    541     }
    542 
    543     /** @hide */
    544     @RestrictTo(LIBRARY_GROUP)
    545     @Override
    546     public void pause(View sceneRoot) {
    547         super.pause(sceneRoot);
    548         int numTransitions = mTransitions.size();
    549         for (int i = 0; i < numTransitions; ++i) {
    550             mTransitions.get(i).pause(sceneRoot);
    551         }
    552     }
    553 
    554     /** @hide */
    555     @RestrictTo(LIBRARY_GROUP)
    556     @Override
    557     public void resume(View sceneRoot) {
    558         super.resume(sceneRoot);
    559         int numTransitions = mTransitions.size();
    560         for (int i = 0; i < numTransitions; ++i) {
    561             mTransitions.get(i).resume(sceneRoot);
    562         }
    563     }
    564 
    565     /** @hide */
    566     @RestrictTo(LIBRARY_GROUP)
    567     @Override
    568     protected void cancel() {
    569         super.cancel();
    570         int numTransitions = mTransitions.size();
    571         for (int i = 0; i < numTransitions; ++i) {
    572             mTransitions.get(i).cancel();
    573         }
    574     }
    575 
    576     /** @hide */
    577     @RestrictTo(LIBRARY_GROUP)
    578     @Override
    579     void forceToEnd(ViewGroup sceneRoot) {
    580         super.forceToEnd(sceneRoot);
    581         int numTransitions = mTransitions.size();
    582         for (int i = 0; i < numTransitions; ++i) {
    583             mTransitions.get(i).forceToEnd(sceneRoot);
    584         }
    585     }
    586 
    587     @Override
    588     TransitionSet setSceneRoot(ViewGroup sceneRoot) {
    589         super.setSceneRoot(sceneRoot);
    590         int numTransitions = mTransitions.size();
    591         for (int i = 0; i < numTransitions; ++i) {
    592             mTransitions.get(i).setSceneRoot(sceneRoot);
    593         }
    594         return this;
    595     }
    596 
    597     @Override
    598     void setCanRemoveViews(boolean canRemoveViews) {
    599         super.setCanRemoveViews(canRemoveViews);
    600         int numTransitions = mTransitions.size();
    601         for (int i = 0; i < numTransitions; ++i) {
    602             mTransitions.get(i).setCanRemoveViews(canRemoveViews);
    603         }
    604     }
    605 
    606     @Override
    607     public void setPropagation(TransitionPropagation propagation) {
    608         super.setPropagation(propagation);
    609         mChangeFlags |= FLAG_CHANGE_PROPAGATION;
    610         int numTransitions = mTransitions.size();
    611         for (int i = 0; i < numTransitions; ++i) {
    612             mTransitions.get(i).setPropagation(propagation);
    613         }
    614     }
    615 
    616     @Override
    617     public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
    618         super.setEpicenterCallback(epicenterCallback);
    619         mChangeFlags |= FLAG_CHANGE_EPICENTER;
    620         int numTransitions = mTransitions.size();
    621         for (int i = 0; i < numTransitions; ++i) {
    622             mTransitions.get(i).setEpicenterCallback(epicenterCallback);
    623         }
    624     }
    625 
    626     @Override
    627     String toString(String indent) {
    628         String result = super.toString(indent);
    629         for (int i = 0; i < mTransitions.size(); ++i) {
    630             result += "\n" + mTransitions.get(i).toString(indent + "  ");
    631         }
    632         return result;
    633     }
    634 
    635     @Override
    636     public Transition clone() {
    637         TransitionSet clone = (TransitionSet) super.clone();
    638         clone.mTransitions = new ArrayList<>();
    639         int numTransitions = mTransitions.size();
    640         for (int i = 0; i < numTransitions; ++i) {
    641             clone.addTransition(mTransitions.get(i).clone());
    642         }
    643         return clone;
    644     }
    645 
    646 }
    647