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