/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.transition; import android.animation.TimeInterpolator; import android.content.Context; import android.content.res.TypedArray; import android.util.AndroidRuntimeException; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.android.internal.R; import java.util.ArrayList; /** * A TransitionSet is a parent of child transitions (including other * TransitionSets). Using TransitionSets enables more complex * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition} * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition. * * <p>A TransitionSet can be described in a resource file by using the * tag <code>transitionSet</code>, along with the standard * attributes of {@link android.R.styleable#TransitionSet} and * {@link android.R.styleable#Transition}. Child transitions of the * TransitionSet object can be loaded by adding those child tags inside the * enclosing <code>transitionSet</code> tag. For example, the following xml * describes a TransitionSet that plays a Fade and then a ChangeBounds * transition on the affected view targets:</p> * <pre> * <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" * android:transitionOrdering="sequential"> * <fade/> * <changeBounds/> * </transitionSet> * </pre> */ public class TransitionSet extends Transition { ArrayList<Transition> mTransitions = new ArrayList<Transition>(); private boolean mPlayTogether = true; int mCurrentListeners; boolean mStarted = false; /** * A flag used to indicate that the child transitions of this set * should all start at the same time. */ public static final int ORDERING_TOGETHER = 0; /** * A flag used to indicate that the child transitions of this set should * play in sequence; when one child transition ends, the next child * transition begins. Note that a transition does not end until all * instances of it (which are playing on all applicable targets of the * transition) end. */ public static final int ORDERING_SEQUENTIAL = 1; /** * Constructs an empty transition set. Add child transitions to the * set by calling {@link #addTransition(Transition)} )}. By default, * child transitions will play {@link #ORDERING_TOGETHER together}. */ public TransitionSet() { } public TransitionSet(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TransitionSet); int ordering = a.getInt(R.styleable.TransitionSet_transitionOrdering, TransitionSet.ORDERING_TOGETHER); setOrdering(ordering); a.recycle(); } /** * Sets the play order of this set's child transitions. * * @param ordering {@link #ORDERING_TOGETHER} to play this set's child * transitions together, {@link #ORDERING_SEQUENTIAL} to play the child * transitions in sequence. * @return This transitionSet object. */ public TransitionSet setOrdering(int ordering) { switch (ordering) { case ORDERING_SEQUENTIAL: mPlayTogether = false; break; case ORDERING_TOGETHER: mPlayTogether = true; break; default: throw new AndroidRuntimeException("Invalid parameter for TransitionSet " + "ordering: " + ordering); } return this; } /** * Returns the ordering of this TransitionSet. By default, the value is * {@link #ORDERING_TOGETHER}. * * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence. * * @see #setOrdering(int) */ public int getOrdering() { return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL; } /** * Adds child transition to this set. The order in which this child transition * is added relative to other child transitions that are added, in addition to * the {@link #getOrdering() ordering} property, determines the * order in which the transitions are started. * * <p>If this transitionSet has a {@link #getDuration() duration} set on it, the * child transition will inherit that duration. Transitions are assumed to have * a maximum of one transitionSet parent.</p> * * @param transition A non-null child transition to be added to this set. * @return This transitionSet object. */ public TransitionSet addTransition(Transition transition) { if (transition != null) { mTransitions.add(transition); transition.mParent = this; if (mDuration >= 0) { transition.setDuration(mDuration); } } return this; } /** * Returns the number of child transitions in the TransitionSet. * * @return The number of child transitions in the TransitionSet. * @see #addTransition(Transition) * @see #getTransitionAt(int) */ public int getTransitionCount() { return mTransitions.size(); } /** * Returns the child Transition at the specified position in the TransitionSet. * * @param index The position of the Transition to retrieve. * @see #addTransition(Transition) * @see #getTransitionCount() */ public Transition getTransitionAt(int index) { if (index < 0 || index >= mTransitions.size()) { return null; } return mTransitions.get(index); } /** * Setting a non-negative duration on a TransitionSet causes all of the child * transitions (current and future) to inherit this duration. * * @param duration The length of the animation, in milliseconds. * @return This transitionSet object. */ @Override public TransitionSet setDuration(long duration) { super.setDuration(duration); if (mDuration >= 0 && mTransitions != null) { int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).setDuration(duration); } } return this; } @Override public TransitionSet setStartDelay(long startDelay) { return (TransitionSet) super.setStartDelay(startDelay); } @Override public TransitionSet setInterpolator(TimeInterpolator interpolator) { return (TransitionSet) super.setInterpolator(interpolator); } @Override public TransitionSet addTarget(View target) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).addTarget(target); } return (TransitionSet) super.addTarget(target); } @Override public TransitionSet addTarget(int targetId) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).addTarget(targetId); } return (TransitionSet) super.addTarget(targetId); } @Override public TransitionSet addTarget(String targetName) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).addTarget(targetName); } return (TransitionSet) super.addTarget(targetName); } @Override public TransitionSet addTarget(Class targetType) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).addTarget(targetType); } return (TransitionSet) super.addTarget(targetType); } @Override public TransitionSet addListener(TransitionListener listener) { return (TransitionSet) super.addListener(listener); } @Override public TransitionSet removeTarget(int targetId) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).removeTarget(targetId); } return (TransitionSet) super.removeTarget(targetId); } @Override public TransitionSet removeTarget(View target) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).removeTarget(target); } return (TransitionSet) super.removeTarget(target); } @Override public TransitionSet removeTarget(Class target) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).removeTarget(target); } return (TransitionSet) super.removeTarget(target); } @Override public TransitionSet removeTarget(String target) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).removeTarget(target); } return (TransitionSet) super.removeTarget(target); } @Override public Transition excludeTarget(View target, boolean exclude) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).excludeTarget(target, exclude); } return super.excludeTarget(target, exclude); } @Override public Transition excludeTarget(String targetName, boolean exclude) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).excludeTarget(targetName, exclude); } return super.excludeTarget(targetName, exclude); } @Override public Transition excludeTarget(int targetId, boolean exclude) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).excludeTarget(targetId, exclude); } return super.excludeTarget(targetId, exclude); } @Override public Transition excludeTarget(Class type, boolean exclude) { for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).excludeTarget(type, exclude); } return super.excludeTarget(type, exclude); } @Override public TransitionSet removeListener(TransitionListener listener) { return (TransitionSet) super.removeListener(listener); } @Override public void setPathMotion(PathMotion pathMotion) { super.setPathMotion(pathMotion); for (int i = 0; i < mTransitions.size(); i++) { mTransitions.get(i).setPathMotion(pathMotion); } } /** * Removes the specified child transition from this set. * * @param transition The transition to be removed. * @return This transitionSet object. */ public TransitionSet removeTransition(Transition transition) { mTransitions.remove(transition); transition.mParent = null; return this; } /** * Sets up listeners for each of the child transitions. This is used to * determine when this transition set is finished (all child transitions * must finish first). */ private void setupStartEndListeners() { TransitionSetListener listener = new TransitionSetListener(this); for (Transition childTransition : mTransitions) { childTransition.addListener(listener); } mCurrentListeners = mTransitions.size(); } /** * This listener is used to detect when all child transitions are done, at * which point this transition set is also done. */ static class TransitionSetListener extends TransitionListenerAdapter { TransitionSet mTransitionSet; TransitionSetListener(TransitionSet transitionSet) { mTransitionSet = transitionSet; } @Override public void onTransitionStart(Transition transition) { if (!mTransitionSet.mStarted) { mTransitionSet.start(); mTransitionSet.mStarted = true; } } @Override public void onTransitionEnd(Transition transition) { --mTransitionSet.mCurrentListeners; if (mTransitionSet.mCurrentListeners == 0) { // All child trans mTransitionSet.mStarted = false; mTransitionSet.end(); } transition.removeListener(this); } } /** * @hide */ @Override protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList) { long startDelay = getStartDelay(); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; i++) { Transition childTransition = mTransitions.get(i); // We only set the start delay on the first transition if we are playing // the transitions sequentially. if (startDelay > 0 && (mPlayTogether || i == 0)) { long childStartDelay = childTransition.getStartDelay(); if (childStartDelay > 0) { childTransition.setStartDelay(startDelay + childStartDelay); } else { childTransition.setStartDelay(startDelay); } } childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList, endValuesList); } } /** * @hide */ @Override protected void runAnimators() { if (mTransitions.isEmpty()) { start(); end(); return; } setupStartEndListeners(); int numTransitions = mTransitions.size(); if (!mPlayTogether) { // Setup sequence with listeners // TODO: Need to add listeners in such a way that we can remove them later if canceled for (int i = 1; i < numTransitions; ++i) { Transition previousTransition = mTransitions.get(i - 1); final Transition nextTransition = mTransitions.get(i); previousTransition.addListener(new TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { nextTransition.runAnimators(); transition.removeListener(this); } }); } Transition firstTransition = mTransitions.get(0); if (firstTransition != null) { firstTransition.runAnimators(); } } else { for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).runAnimators(); } } } @Override public void captureStartValues(TransitionValues transitionValues) { if (isValidTarget(transitionValues.view)) { for (Transition childTransition : mTransitions) { if (childTransition.isValidTarget(transitionValues.view)) { childTransition.captureStartValues(transitionValues); transitionValues.targetedTransitions.add(childTransition); } } } } @Override public void captureEndValues(TransitionValues transitionValues) { if (isValidTarget(transitionValues.view)) { for (Transition childTransition : mTransitions) { if (childTransition.isValidTarget(transitionValues.view)) { childTransition.captureEndValues(transitionValues); transitionValues.targetedTransitions.add(childTransition); } } } } @Override void capturePropagationValues(TransitionValues transitionValues) { super.capturePropagationValues(transitionValues); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).capturePropagationValues(transitionValues); } } /** @hide */ @Override public void pause(View sceneRoot) { super.pause(sceneRoot); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).pause(sceneRoot); } } /** @hide */ @Override public void resume(View sceneRoot) { super.resume(sceneRoot); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).resume(sceneRoot); } } /** @hide */ @Override protected void cancel() { super.cancel(); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).cancel(); } } /** @hide */ @Override void forceToEnd(ViewGroup sceneRoot) { super.forceToEnd(sceneRoot); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).forceToEnd(sceneRoot); } } @Override TransitionSet setSceneRoot(ViewGroup sceneRoot) { super.setSceneRoot(sceneRoot); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).setSceneRoot(sceneRoot); } return (TransitionSet) this; } @Override void setCanRemoveViews(boolean canRemoveViews) { super.setCanRemoveViews(canRemoveViews); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).setCanRemoveViews(canRemoveViews); } } @Override public void setPropagation(TransitionPropagation propagation) { super.setPropagation(propagation); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).setPropagation(propagation); } } @Override public void setEpicenterCallback(EpicenterCallback epicenterCallback) { super.setEpicenterCallback(epicenterCallback); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { mTransitions.get(i).setEpicenterCallback(epicenterCallback); } } @Override String toString(String indent) { String result = super.toString(indent); for (int i = 0; i < mTransitions.size(); ++i) { result += "\n" + mTransitions.get(i).toString(indent + " "); } return result; } @Override public TransitionSet clone() { TransitionSet clone = (TransitionSet) super.clone(); clone.mTransitions = new ArrayList<Transition>(); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { clone.addTransition((Transition) mTransitions.get(i).clone()); } return clone; } }