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