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 com.android.internal.R;
     20 
     21 import android.animation.TimeInterpolator;
     22 import android.content.Context;
     23 import android.content.res.TypedArray;
     24 import android.util.AndroidRuntimeException;
     25 import android.util.AttributeSet;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     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:ordering="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     /** @hide */
    322     @Override
    323     public void forceVisibility(int visibility, boolean isStartValue) {
    324         int numTransitions = mTransitions.size();
    325         for (int i = 0; i < numTransitions; i++) {
    326             mTransitions.get(i).forceVisibility(visibility, isStartValue);
    327         }
    328     }
    329 
    330     /**
    331      * Removes the specified child transition from this set.
    332      *
    333      * @param transition The transition to be removed.
    334      * @return This transitionSet object.
    335      */
    336     public TransitionSet removeTransition(Transition transition) {
    337         mTransitions.remove(transition);
    338         transition.mParent = null;
    339         return this;
    340     }
    341 
    342     /**
    343      * Sets up listeners for each of the child transitions. This is used to
    344      * determine when this transition set is finished (all child transitions
    345      * must finish first).
    346      */
    347     private void setupStartEndListeners() {
    348         TransitionSetListener listener = new TransitionSetListener(this);
    349         for (Transition childTransition : mTransitions) {
    350             childTransition.addListener(listener);
    351         }
    352         mCurrentListeners = mTransitions.size();
    353     }
    354 
    355     /**
    356      * This listener is used to detect when all child transitions are done, at
    357      * which point this transition set is also done.
    358      */
    359     static class TransitionSetListener extends TransitionListenerAdapter {
    360         TransitionSet mTransitionSet;
    361         TransitionSetListener(TransitionSet transitionSet) {
    362             mTransitionSet = transitionSet;
    363         }
    364         @Override
    365         public void onTransitionStart(Transition transition) {
    366             if (!mTransitionSet.mStarted) {
    367                 mTransitionSet.start();
    368                 mTransitionSet.mStarted = true;
    369             }
    370         }
    371 
    372         @Override
    373         public void onTransitionEnd(Transition transition) {
    374             --mTransitionSet.mCurrentListeners;
    375             if (mTransitionSet.mCurrentListeners == 0) {
    376                 // All child trans
    377                 mTransitionSet.mStarted = false;
    378                 mTransitionSet.end();
    379             }
    380             transition.removeListener(this);
    381         }
    382     }
    383 
    384     /**
    385      * @hide
    386      */
    387     @Override
    388     protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
    389             TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
    390             ArrayList<TransitionValues> endValuesList) {
    391         long startDelay = getStartDelay();
    392         int numTransitions = mTransitions.size();
    393         for (int i = 0; i < numTransitions; i++) {
    394             Transition childTransition = mTransitions.get(i);
    395             // We only set the start delay on the first transition if we are playing
    396             // the transitions sequentially.
    397             if (startDelay > 0 && (mPlayTogether || i == 0)) {
    398                 long childStartDelay = childTransition.getStartDelay();
    399                 if (childStartDelay > 0) {
    400                     childTransition.setStartDelay(startDelay + childStartDelay);
    401                 } else {
    402                     childTransition.setStartDelay(startDelay);
    403                 }
    404             }
    405             childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList,
    406                     endValuesList);
    407         }
    408     }
    409 
    410     /**
    411      * @hide
    412      */
    413     @Override
    414     protected void runAnimators() {
    415         if (mTransitions.isEmpty()) {
    416             start();
    417             end();
    418             return;
    419         }
    420         setupStartEndListeners();
    421         int numTransitions = mTransitions.size();
    422         if (!mPlayTogether) {
    423             // Setup sequence with listeners
    424             // TODO: Need to add listeners in such a way that we can remove them later if canceled
    425             for (int i = 1; i < numTransitions; ++i) {
    426                 Transition previousTransition = mTransitions.get(i - 1);
    427                 final Transition nextTransition = mTransitions.get(i);
    428                 previousTransition.addListener(new TransitionListenerAdapter() {
    429                     @Override
    430                     public void onTransitionEnd(Transition transition) {
    431                         nextTransition.runAnimators();
    432                         transition.removeListener(this);
    433                     }
    434                 });
    435             }
    436             Transition firstTransition = mTransitions.get(0);
    437             if (firstTransition != null) {
    438                 firstTransition.runAnimators();
    439             }
    440         } else {
    441             for (int i = 0; i < numTransitions; ++i) {
    442                 mTransitions.get(i).runAnimators();
    443             }
    444         }
    445     }
    446 
    447     @Override
    448     public void captureStartValues(TransitionValues transitionValues) {
    449         if (isValidTarget(transitionValues.view)) {
    450             for (Transition childTransition : mTransitions) {
    451                 if (childTransition.isValidTarget(transitionValues.view)) {
    452                     childTransition.captureStartValues(transitionValues);
    453                     transitionValues.targetedTransitions.add(childTransition);
    454                 }
    455             }
    456         }
    457     }
    458 
    459     @Override
    460     public void captureEndValues(TransitionValues transitionValues) {
    461         if (isValidTarget(transitionValues.view)) {
    462             for (Transition childTransition : mTransitions) {
    463                 if (childTransition.isValidTarget(transitionValues.view)) {
    464                     childTransition.captureEndValues(transitionValues);
    465                     transitionValues.targetedTransitions.add(childTransition);
    466                 }
    467             }
    468         }
    469     }
    470 
    471     @Override
    472     void capturePropagationValues(TransitionValues transitionValues) {
    473         super.capturePropagationValues(transitionValues);
    474         int numTransitions = mTransitions.size();
    475         for (int i = 0; i < numTransitions; ++i) {
    476             mTransitions.get(i).capturePropagationValues(transitionValues);
    477         }
    478     }
    479 
    480     /** @hide */
    481     @Override
    482     public void pause(View sceneRoot) {
    483         super.pause(sceneRoot);
    484         int numTransitions = mTransitions.size();
    485         for (int i = 0; i < numTransitions; ++i) {
    486             mTransitions.get(i).pause(sceneRoot);
    487         }
    488     }
    489 
    490     /** @hide */
    491     @Override
    492     public void resume(View sceneRoot) {
    493         super.resume(sceneRoot);
    494         int numTransitions = mTransitions.size();
    495         for (int i = 0; i < numTransitions; ++i) {
    496             mTransitions.get(i).resume(sceneRoot);
    497         }
    498     }
    499 
    500     /** @hide */
    501     @Override
    502     protected void cancel() {
    503         super.cancel();
    504         int numTransitions = mTransitions.size();
    505         for (int i = 0; i < numTransitions; ++i) {
    506             mTransitions.get(i).cancel();
    507         }
    508     }
    509 
    510     @Override
    511     TransitionSet setSceneRoot(ViewGroup sceneRoot) {
    512         super.setSceneRoot(sceneRoot);
    513         int numTransitions = mTransitions.size();
    514         for (int i = 0; i < numTransitions; ++i) {
    515             mTransitions.get(i).setSceneRoot(sceneRoot);
    516         }
    517         return (TransitionSet) this;
    518     }
    519 
    520     @Override
    521     void setCanRemoveViews(boolean canRemoveViews) {
    522         super.setCanRemoveViews(canRemoveViews);
    523         int numTransitions = mTransitions.size();
    524         for (int i = 0; i < numTransitions; ++i) {
    525             mTransitions.get(i).setCanRemoveViews(canRemoveViews);
    526         }
    527     }
    528 
    529     @Override
    530     public void setPropagation(TransitionPropagation propagation) {
    531         super.setPropagation(propagation);
    532         int numTransitions = mTransitions.size();
    533         for (int i = 0; i < numTransitions; ++i) {
    534             mTransitions.get(i).setPropagation(propagation);
    535         }
    536     }
    537 
    538     @Override
    539     public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
    540         super.setEpicenterCallback(epicenterCallback);
    541         int numTransitions = mTransitions.size();
    542         for (int i = 0; i < numTransitions; ++i) {
    543             mTransitions.get(i).setEpicenterCallback(epicenterCallback);
    544         }
    545     }
    546 
    547     @Override
    548     String toString(String indent) {
    549         String result = super.toString(indent);
    550         for (int i = 0; i < mTransitions.size(); ++i) {
    551             result += "\n" + mTransitions.get(i).toString(indent + "  ");
    552         }
    553         return result;
    554     }
    555 
    556     @Override
    557     public TransitionSet clone() {
    558         TransitionSet clone = (TransitionSet) super.clone();
    559         clone.mTransitions = new ArrayList<Transition>();
    560         int numTransitions = mTransitions.size();
    561         for (int i = 0; i < numTransitions; ++i) {
    562             clone.addTransition((Transition) mTransitions.get(i).clone());
    563         }
    564         return clone;
    565     }
    566 }
    567