• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
TransitionSet()107     public TransitionSet() {
108     }
109 
TransitionSet(Context context, AttributeSet attrs)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
setOrdering(int ordering)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      */
getOrdering()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
addTransition(@onNull Transition transition)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      */
getTransitionCount()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      */
getTransitionAt(int index)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
setDuration(long duration)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
setStartDelay(long startDelay)241     public TransitionSet setStartDelay(long startDelay) {
242         return (TransitionSet) super.setStartDelay(startDelay);
243     }
244 
245     @NonNull
246     @Override
setInterpolator(@ullable TimeInterpolator interpolator)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
addTarget(@onNull View target)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
addTarget(@dRes int targetId)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
addTarget(@onNull String targetName)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
addTarget(@onNull Class targetType)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
addListener(@onNull TransitionListener listener)296     public TransitionSet addListener(@NonNull TransitionListener listener) {
297         return (TransitionSet) super.addListener(listener);
298     }
299 
300     @NonNull
301     @Override
removeTarget(@dRes int targetId)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
removeTarget(@onNull View target)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
removeTarget(@onNull Class target)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
removeTarget(@onNull String target)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
excludeTarget(@onNull View target, boolean exclude)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
excludeTarget(@onNull String targetName, boolean exclude)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
excludeTarget(int targetId, boolean exclude)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
excludeTarget(@onNull Class type, boolean exclude)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
removeListener(@onNull TransitionListener listener)374     public TransitionSet removeListener(@NonNull TransitionListener listener) {
375         return (TransitionSet) super.removeListener(listener);
376     }
377 
378     @Override
setPathMotion(PathMotion pathMotion)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
removeTransition(@onNull Transition transition)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      */
setupStartEndListeners()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 
TransitionSetListener(TransitionSet transitionSet)421         TransitionSetListener(TransitionSet transitionSet) {
422             mTransitionSet = transitionSet;
423         }
424 
425         @Override
onTransitionStart(@onNull Transition transition)426         public void onTransitionStart(@NonNull Transition transition) {
427             if (!mTransitionSet.mStarted) {
428                 mTransitionSet.start();
429                 mTransitionSet.mStarted = true;
430             }
431         }
432 
433         @Override
onTransitionEnd(@onNull Transition transition)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
createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList)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
runAnimators()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
captureStartValues(@onNull TransitionValues transitionValues)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
captureEndValues(@onNull TransitionValues transitionValues)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
capturePropagationValues(TransitionValues transitionValues)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
pause(View sceneRoot)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
resume(View sceneRoot)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
cancel()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
forceToEnd(ViewGroup sceneRoot)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
setSceneRoot(ViewGroup sceneRoot)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
setCanRemoveViews(boolean canRemoveViews)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
setPropagation(TransitionPropagation propagation)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
setEpicenterCallback(EpicenterCallback epicenterCallback)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
toString(String indent)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
clone()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