blob: fd3f9b3c86f7d483f71ca58fb46f4d0d90e2481f [file] [log] [blame]
Chet Haasefaebd8f2012-05-18 14:17:57 -07001/*
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 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070016
Chet Haased82c8ac2013-08-26 14:20:16 -070017package android.transition;
Chet Haasefaebd8f2012-05-18 14:17:57 -070018
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.TimeInterpolator;
Chet Haase08735182013-06-04 10:44:40 -070022import android.util.ArrayMap;
Chet Haasec43524f2013-07-16 14:40:11 -070023import android.util.Log;
Chet Haasefaebd8f2012-05-18 14:17:57 -070024import android.util.LongSparseArray;
25import android.util.SparseArray;
26import android.view.SurfaceView;
27import android.view.TextureView;
28import android.view.View;
29import android.view.ViewGroup;
30import android.view.ViewOverlay;
31import android.widget.ListView;
Chet Haaseff58f922013-09-11 13:08:18 -070032import android.widget.Spinner;
Chet Haasefaebd8f2012-05-18 14:17:57 -070033
34import java.util.ArrayList;
Chet Haased82c8ac2013-08-26 14:20:16 -070035import java.util.List;
Chet Haasefaebd8f2012-05-18 14:17:57 -070036
37/**
38 * A Transition holds information about animations that will be run on its
39 * targets during a scene change. Subclasses of this abstract class may
Chet Haased82c8ac2013-08-26 14:20:16 -070040 * choreograph several child transitions ({@link TransitionSet} or they may
Chet Haasefaebd8f2012-05-18 14:17:57 -070041 * perform custom animations themselves. Any Transition has two main jobs:
42 * (1) capture property values, and (2) play animations based on changes to
43 * captured property values. A custom transition knows what property values
44 * on View objects are of interest to it, and also knows how to animate
45 * changes to those values. For example, the {@link Fade} transition tracks
46 * changes to visibility-related properties and is able to construct and run
47 * animations that fade items in or out based on changes to those properties.
48 *
49 * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
50 * or {@link TextureView}, due to the way that these views are displayed
51 * on the screen. For SurfaceView, the problem is that the view is updated from
52 * a non-UI thread, so changes to the view due to transitions (such as moving
53 * and resizing the view) may be out of sync with the display inside those bounds.
54 * TextureView is more compatible with transitions in general, but some
Chet Haased82c8ac2013-08-26 14:20:16 -070055 * specific transitions (such as {@link Fade}) may not be compatible
Chet Haasefaebd8f2012-05-18 14:17:57 -070056 * with TextureView because they rely on {@link ViewOverlay} functionality,
57 * which does not currently work with TextureView.</p>
Chet Haased82c8ac2013-08-26 14:20:16 -070058 *
59 * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
60 * directory. Transition resources consist of a tag name for one of the Transition
61 * subclasses along with attributes to define some of the attributes of that transition.
62 * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p>
63 *
64 * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
65 *
66 * <p>Note that attributes for the transition are not required, just as they are
67 * optional when declared in code; Transitions created from XML resources will use
68 * the same defaults as their code-created equivalents. Here is a slightly more
69 * elaborate example which declares a {@link TransitionSet} transition with
70 * {@link ChangeBounds} and {@link Fade} child transitions:</p>
71 *
72 * {@sample
73 * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet}
74 *
75 * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
76 * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
77 * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
78 * transition uses a fadingMode of {@link Fade#OUT} instead of the default
79 * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
80 * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
81 * of which lists a specific <code>targetId</code> which this transition acts upon.
82 * Use of targets is optional, but can be used to either limit the time spent checking
83 * attributes on unchanging views, or limiting the types of animations run on specific views.
84 * In this case, we know that only the <code>grayscaleContainer</code> will be
85 * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
86 *
87 * Further information on XML resource descriptions for transitions can be found for
88 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
89 * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}.
90 *
Chet Haasefaebd8f2012-05-18 14:17:57 -070091 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070092public abstract class Transition implements Cloneable {
Chet Haasefaebd8f2012-05-18 14:17:57 -070093
94 private static final String LOG_TAG = "Transition";
95 static final boolean DBG = false;
96
Chet Haase199acdf2013-07-24 18:40:55 -070097 private String mName = getClass().getName();
98
Chet Haasefaebd8f2012-05-18 14:17:57 -070099 long mStartDelay = -1;
100 long mDuration = -1;
101 TimeInterpolator mInterpolator = null;
Chet Haased82c8ac2013-08-26 14:20:16 -0700102 ArrayList<Integer> mTargetIds = new ArrayList<Integer>();
103 ArrayList<View> mTargets = new ArrayList<View>();
Chet Haaseff58f922013-09-11 13:08:18 -0700104 ArrayList<Integer> mTargetIdExcludes = null;
105 ArrayList<View> mTargetExcludes = null;
106 ArrayList<Class> mTargetTypeExcludes = null;
107 ArrayList<Integer> mTargetIdChildExcludes = null;
108 ArrayList<View> mTargetChildExcludes = null;
109 ArrayList<Class> mTargetTypeChildExcludes = null;
Chet Haase6ebe3de2013-06-17 16:50:50 -0700110 private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
111 private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
Chet Haased82c8ac2013-08-26 14:20:16 -0700112 TransitionSet mParent = null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700113
Chet Haase199acdf2013-07-24 18:40:55 -0700114 // Per-animator information used for later canceling when future transitions overlap
115 private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
116 new ThreadLocal<ArrayMap<Animator, AnimationInfo>>();
117
Chet Haased82c8ac2013-08-26 14:20:16 -0700118 // Scene Root is set at createAnimator() time in the cloned Transition
Chet Haase6ebe3de2013-06-17 16:50:50 -0700119 ViewGroup mSceneRoot = null;
120
Chet Haaseb7a7fc92013-09-20 16:33:08 -0700121 // Whether removing views from their parent is possible. This is only for views
122 // in the start scene, which are no longer in the view hierarchy. This property
123 // is determined by whether the previous Scene was created from a layout
124 // resource, and thus the views from the exited scene are going away anyway
125 // and can be removed as necessary to achieve a particular effect, such as
126 // removing them from parents to add them to overlays.
127 boolean mCanRemoveViews = false;
128
Chet Haasee9d32ea2013-06-04 08:46:42 -0700129 // Track all animators in use in case the transition gets canceled and needs to
130 // cancel running animators
131 private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
132
Chet Haasefaebd8f2012-05-18 14:17:57 -0700133 // Number of per-target instances of this Transition currently running. This count is
Chet Haase199acdf2013-07-24 18:40:55 -0700134 // determined by calls to start() and end()
Chet Haasefaebd8f2012-05-18 14:17:57 -0700135 int mNumInstances = 0;
136
Chet Haase199acdf2013-07-24 18:40:55 -0700137 // Whether this transition is currently paused, due to a call to pause()
138 boolean mPaused = false;
Chet Haasec43524f2013-07-16 14:40:11 -0700139
Chet Haasea56205c2013-09-10 11:30:22 -0700140 // Whether this transition has ended. Used to avoid pause/resume on transitions
141 // that have completed
142 private boolean mEnded = false;
143
Chet Haasec43524f2013-07-16 14:40:11 -0700144 // The set of listeners to be sent transition lifecycle events.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700145 ArrayList<TransitionListener> mListeners = null;
146
Chet Haased82c8ac2013-08-26 14:20:16 -0700147 // The set of animators collected from calls to createAnimator(),
148 // to be run in runAnimators()
Chet Haase199acdf2013-07-24 18:40:55 -0700149 ArrayList<Animator> mAnimators = new ArrayList<Animator>();
Chet Haasec43524f2013-07-16 14:40:11 -0700150
Chet Haasefaebd8f2012-05-18 14:17:57 -0700151 /**
152 * Constructs a Transition object with no target objects. A transition with
153 * no targets defaults to running on all target objects in the scene hierarchy
Chet Haased82c8ac2013-08-26 14:20:16 -0700154 * (if the transition is not contained in a TransitionSet), or all target
155 * objects passed down from its parent (if it is in a TransitionSet).
Chet Haasefaebd8f2012-05-18 14:17:57 -0700156 */
157 public Transition() {}
158
159 /**
160 * Sets the duration of this transition. By default, there is no duration
161 * (indicated by a negative number), which means that the Animator created by
162 * the transition will have its own specified duration. If the duration of a
163 * Transition is set, that duration will override the Animator duration.
164 *
165 * @param duration The length of the animation, in milliseconds.
166 * @return This transition object.
Chet Haased82c8ac2013-08-26 14:20:16 -0700167 * @attr ref android.R.styleable#Transition_duration
Chet Haasefaebd8f2012-05-18 14:17:57 -0700168 */
169 public Transition setDuration(long duration) {
170 mDuration = duration;
171 return this;
172 }
173
Chet Haase199acdf2013-07-24 18:40:55 -0700174 /**
175 * Returns the duration set on this transition. If no duration has been set,
176 * the returned value will be negative, indicating that resulting animators will
177 * retain their own durations.
178 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700179 * @return The duration set on this transition, in milliseconds, if one has been
180 * set, otherwise returns a negative number.
Chet Haase199acdf2013-07-24 18:40:55 -0700181 */
Chet Haasefaebd8f2012-05-18 14:17:57 -0700182 public long getDuration() {
183 return mDuration;
184 }
185
186 /**
187 * Sets the startDelay of this transition. By default, there is no delay
188 * (indicated by a negative number), which means that the Animator created by
189 * the transition will have its own specified startDelay. If the delay of a
190 * Transition is set, that delay will override the Animator delay.
191 *
192 * @param startDelay The length of the delay, in milliseconds.
Chet Haased82c8ac2013-08-26 14:20:16 -0700193 * @return This transition object.
194 * @attr ref android.R.styleable#Transition_startDelay
Chet Haasefaebd8f2012-05-18 14:17:57 -0700195 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700196 public Transition setStartDelay(long startDelay) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700197 mStartDelay = startDelay;
Chet Haased82c8ac2013-08-26 14:20:16 -0700198 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700199 }
200
Chet Haase199acdf2013-07-24 18:40:55 -0700201 /**
202 * Returns the startDelay set on this transition. If no startDelay has been set,
203 * the returned value will be negative, indicating that resulting animators will
204 * retain their own startDelays.
205 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700206 * @return The startDelay set on this transition, in milliseconds, if one has
207 * been set, otherwise returns a negative number.
Chet Haase199acdf2013-07-24 18:40:55 -0700208 */
Chet Haasefaebd8f2012-05-18 14:17:57 -0700209 public long getStartDelay() {
210 return mStartDelay;
211 }
212
213 /**
214 * Sets the interpolator of this transition. By default, the interpolator
215 * is null, which means that the Animator created by the transition
216 * will have its own specified interpolator. If the interpolator of a
217 * Transition is set, that interpolator will override the Animator interpolator.
218 *
219 * @param interpolator The time interpolator used by the transition
Chet Haased82c8ac2013-08-26 14:20:16 -0700220 * @return This transition object.
221 * @attr ref android.R.styleable#Transition_interpolator
Chet Haasefaebd8f2012-05-18 14:17:57 -0700222 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700223 public Transition setInterpolator(TimeInterpolator interpolator) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700224 mInterpolator = interpolator;
Chet Haased82c8ac2013-08-26 14:20:16 -0700225 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700226 }
227
Chet Haase199acdf2013-07-24 18:40:55 -0700228 /**
229 * Returns the interpolator set on this transition. If no interpolator has been set,
230 * the returned value will be null, indicating that resulting animators will
231 * retain their own interpolators.
232 *
233 * @return The interpolator set on this transition, if one has been set, otherwise
234 * returns null.
235 */
Chet Haasefaebd8f2012-05-18 14:17:57 -0700236 public TimeInterpolator getInterpolator() {
237 return mInterpolator;
238 }
239
240 /**
Chet Haase199acdf2013-07-24 18:40:55 -0700241 * Returns the set of property names used stored in the {@link TransitionValues}
Chet Haased82c8ac2013-08-26 14:20:16 -0700242 * object passed into {@link #captureStartValues(TransitionValues)} that
Chet Haase199acdf2013-07-24 18:40:55 -0700243 * this transition cares about for the purposes of canceling overlapping animations.
244 * When any transition is started on a given scene root, all transitions
245 * currently running on that same scene root are checked to see whether the
246 * properties on which they based their animations agree with the end values of
247 * the same properties in the new transition. If the end values are not equal,
248 * then the old animation is canceled since the new transition will start a new
249 * animation to these new values. If the values are equal, the old animation is
250 * allowed to continue and no new animation is started for that transition.
251 *
252 * <p>A transition does not need to override this method. However, not doing so
253 * will mean that the cancellation logic outlined in the previous paragraph
254 * will be skipped for that transition, possibly leading to artifacts as
255 * old transitions and new transitions on the same targets run in parallel,
256 * animating views toward potentially different end values.</p>
257 *
258 * @return An array of property names as described in the class documentation for
259 * {@link TransitionValues}. The default implementation returns <code>null</code>.
260 */
261 public String[] getTransitionProperties() {
262 return null;
263 }
264
265 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700266 * This method creates an animation that will be run for this transition
267 * given the information in the startValues and endValues structures captured
268 * earlier for the start and end scenes. Subclasses of Transition should override
269 * this method. The method should only be called by the transition system; it is
270 * not intended to be called from external classes.
271 *
272 * <p>This method is called by the transition's parent (all the way up to the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700273 * topmost Transition in the hierarchy) with the sceneRoot and start/end
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700274 * values that the transition may need to set up initial target values
275 * and construct an appropriate animation. For example, if an overall
Chet Haased82c8ac2013-08-26 14:20:16 -0700276 * Transition is a {@link TransitionSet} consisting of several
Chet Haasefaebd8f2012-05-18 14:17:57 -0700277 * child transitions in sequence, then some of the child transitions may
278 * want to set initial values on target views prior to the overall
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700279 * Transition commencing, to put them in an appropriate state for the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700280 * delay between that start and the child Transition start time. For
281 * example, a transition that fades an item in may wish to set the starting
282 * alpha value to 0, to avoid it blinking in prior to the transition
283 * actually starting the animation. This is necessary because the scene
284 * change that triggers the Transition will automatically set the end-scene
285 * on all target views, so a Transition that wants to animate from a
Chet Haased82c8ac2013-08-26 14:20:16 -0700286 * different value should set that value prior to returning from this method.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700287 *
288 * <p>Additionally, a Transition can perform logic to determine whether
289 * the transition needs to run on the given target and start/end values.
290 * For example, a transition that resizes objects on the screen may wish
291 * to avoid running for views which are not present in either the start
Chet Haased82c8ac2013-08-26 14:20:16 -0700292 * or end scenes.</p>
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700293 *
294 * <p>If there is an animator created and returned from this method, the
295 * transition mechanism will apply any applicable duration, startDelay,
296 * and interpolator to that animation and start it. A return value of
297 * <code>null</code> indicates that no animation should run. The default
298 * implementation returns null.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700299 *
300 * <p>The method is called for every applicable target object, which is
301 * stored in the {@link TransitionValues#view} field.</p>
302 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700303 *
304 * @param sceneRoot The root of the transition hierarchy.
305 * @param startValues The values for a specific target in the start scene.
306 * @param endValues The values for the target in the end scene.
307 * @return A Animator to be started at the appropriate time in the
308 * overall transition for this scene change. A null value means no animation
309 * should be run.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700310 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700311 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700312 TransitionValues endValues) {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700313 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700314 }
315
316 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700317 * This method, essentially a wrapper around all calls to createAnimator for all
318 * possible target views, is called with the entire set of start/end
Chet Haasefaebd8f2012-05-18 14:17:57 -0700319 * values. The implementation in Transition iterates through these lists
Chet Haased82c8ac2013-08-26 14:20:16 -0700320 * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
Chet Haasefaebd8f2012-05-18 14:17:57 -0700321 * with each set of start/end values on this transition. The
Chet Haased82c8ac2013-08-26 14:20:16 -0700322 * TransitionSet subclass overrides this method and delegates it to
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700323 * each of its children in succession.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700324 *
325 * @hide
326 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700327 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
Chet Haase6ebe3de2013-06-17 16:50:50 -0700328 TransitionValuesMaps endValues) {
Chet Haasec43524f2013-07-16 14:40:11 -0700329 if (DBG) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700330 Log.d(LOG_TAG, "createAnimators() for " + this);
Chet Haasec43524f2013-07-16 14:40:11 -0700331 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700332 ArrayMap<View, TransitionValues> endCopy =
333 new ArrayMap<View, TransitionValues>(endValues.viewValues);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700334 SparseArray<TransitionValues> endIdCopy =
Chet Haase6ebe3de2013-06-17 16:50:50 -0700335 new SparseArray<TransitionValues>(endValues.idValues.size());
336 for (int i = 0; i < endValues.idValues.size(); ++i) {
337 int id = endValues.idValues.keyAt(i);
338 endIdCopy.put(id, endValues.idValues.valueAt(i));
Chet Haasefaebd8f2012-05-18 14:17:57 -0700339 }
340 LongSparseArray<TransitionValues> endItemIdCopy =
Chet Haase6ebe3de2013-06-17 16:50:50 -0700341 new LongSparseArray<TransitionValues>(endValues.itemIdValues.size());
342 for (int i = 0; i < endValues.itemIdValues.size(); ++i) {
343 long id = endValues.itemIdValues.keyAt(i);
344 endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i));
Chet Haasefaebd8f2012-05-18 14:17:57 -0700345 }
346 // Walk through the start values, playing everything we find
347 // Remove from the end set as we go
348 ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
349 ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700350 for (View view : startValues.viewValues.keySet()) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700351 TransitionValues start = null;
352 TransitionValues end = null;
353 boolean isInListView = false;
354 if (view.getParent() instanceof ListView) {
355 isInListView = true;
356 }
357 if (!isInListView) {
358 int id = view.getId();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700359 start = startValues.viewValues.get(view) != null ?
360 startValues.viewValues.get(view) : startValues.idValues.get(id);
361 if (endValues.viewValues.get(view) != null) {
362 end = endValues.viewValues.get(view);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700363 endCopy.remove(view);
Chet Haasec46181a2013-09-16 13:56:21 -0700364 } else if (id != View.NO_ID) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700365 end = endValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700366 View removeView = null;
367 for (View viewToRemove : endCopy.keySet()) {
368 if (viewToRemove.getId() == id) {
369 removeView = viewToRemove;
370 }
371 }
372 if (removeView != null) {
373 endCopy.remove(removeView);
374 }
375 }
376 endIdCopy.remove(id);
377 if (isValidTarget(view, id)) {
378 startValuesList.add(start);
379 endValuesList.add(end);
380 }
381 } else {
382 ListView parent = (ListView) view.getParent();
383 if (parent.getAdapter().hasStableIds()) {
384 int position = parent.getPositionForView(view);
385 long itemId = parent.getItemIdAtPosition(position);
Chet Haase6ebe3de2013-06-17 16:50:50 -0700386 start = startValues.itemIdValues.get(itemId);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700387 endItemIdCopy.remove(itemId);
388 // TODO: deal with targetIDs for itemIDs for ListView items
389 startValuesList.add(start);
390 endValuesList.add(end);
391 }
392 }
393 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700394 int startItemIdCopySize = startValues.itemIdValues.size();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700395 for (int i = 0; i < startItemIdCopySize; ++i) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700396 long id = startValues.itemIdValues.keyAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700397 if (isValidTarget(null, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700398 TransitionValues start = startValues.itemIdValues.get(id);
399 TransitionValues end = endValues.itemIdValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700400 endItemIdCopy.remove(id);
401 startValuesList.add(start);
402 endValuesList.add(end);
403 }
404 }
405 // Now walk through the remains of the end set
406 for (View view : endCopy.keySet()) {
407 int id = view.getId();
408 if (isValidTarget(view, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700409 TransitionValues start = startValues.viewValues.get(view) != null ?
410 startValues.viewValues.get(view) : startValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700411 TransitionValues end = endCopy.get(view);
412 endIdCopy.remove(id);
413 startValuesList.add(start);
414 endValuesList.add(end);
415 }
416 }
417 int endIdCopySize = endIdCopy.size();
418 for (int i = 0; i < endIdCopySize; ++i) {
419 int id = endIdCopy.keyAt(i);
420 if (isValidTarget(null, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700421 TransitionValues start = startValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700422 TransitionValues end = endIdCopy.get(id);
423 startValuesList.add(start);
424 endValuesList.add(end);
425 }
426 }
427 int endItemIdCopySize = endItemIdCopy.size();
428 for (int i = 0; i < endItemIdCopySize; ++i) {
429 long id = endItemIdCopy.keyAt(i);
430 // TODO: Deal with targetIDs and itemIDs
Chet Haase6ebe3de2013-06-17 16:50:50 -0700431 TransitionValues start = startValues.itemIdValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700432 TransitionValues end = endItemIdCopy.get(id);
433 startValuesList.add(start);
434 endValuesList.add(end);
435 }
Chet Haase199acdf2013-07-24 18:40:55 -0700436 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700437 for (int i = 0; i < startValuesList.size(); ++i) {
438 TransitionValues start = startValuesList.get(i);
439 TransitionValues end = endValuesList.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -0700440 // Only bother trying to animate with values that differ between start/end
441 if (start != null || end != null) {
442 if (start == null || !start.equals(end)) {
443 if (DBG) {
444 View view = (end != null) ? end.view : start.view;
445 Log.d(LOG_TAG, " differing start/end values for view " +
446 view);
447 if (start == null || end == null) {
Chet Haaseff58f922013-09-11 13:08:18 -0700448 Log.d(LOG_TAG, " " + ((start == null) ?
449 "start null, end non-null" : "start non-null, end null"));
Chet Haasec43524f2013-07-16 14:40:11 -0700450 } else {
451 for (String key : start.values.keySet()) {
452 Object startValue = start.values.get(key);
453 Object endValue = end.values.get(key);
454 if (startValue != endValue && !startValue.equals(endValue)) {
455 Log.d(LOG_TAG, " " + key + ": start(" + startValue +
456 "), end(" + endValue +")");
457 }
458 }
459 }
460 }
461 // TODO: what to do about targetIds and itemIds?
Chet Haased82c8ac2013-08-26 14:20:16 -0700462 Animator animator = createAnimator(sceneRoot, start, end);
Chet Haasec43524f2013-07-16 14:40:11 -0700463 if (animator != null) {
Chet Haase199acdf2013-07-24 18:40:55 -0700464 // Save animation info for future cancellation purposes
465 View view = null;
466 TransitionValues infoValues = null;
467 if (end != null) {
468 view = end.view;
469 String[] properties = getTransitionProperties();
470 if (view != null && properties != null && properties.length > 0) {
471 infoValues = new TransitionValues();
472 infoValues.view = view;
473 TransitionValues newValues = endValues.viewValues.get(view);
474 if (newValues != null) {
475 for (int j = 0; j < properties.length; ++j) {
476 infoValues.values.put(properties[j],
477 newValues.values.get(properties[j]));
478 }
479 }
480 int numExistingAnims = runningAnimators.size();
481 for (int j = 0; j < numExistingAnims; ++j) {
482 Animator anim = runningAnimators.keyAt(j);
483 AnimationInfo info = runningAnimators.get(anim);
484 if (info.values != null && info.view == view &&
485 ((info.name == null && getName() == null) ||
486 info.name.equals(getName()))) {
487 if (info.values.equals(infoValues)) {
488 // Favor the old animator
489 animator = null;
490 break;
491 }
492 }
493 }
494 }
495 } else {
496 view = (start != null) ? start.view : null;
497 }
498 if (animator != null) {
499 AnimationInfo info = new AnimationInfo(view, getName(), infoValues);
500 runningAnimators.put(animator, info);
501 mAnimators.add(animator);
502 }
Chet Haasec43524f2013-07-16 14:40:11 -0700503 }
Chet Haasec43524f2013-07-16 14:40:11 -0700504 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700505 }
506 }
507 }
508
509 /**
510 * Internal utility method for checking whether a given view/id
511 * is valid for this transition, where "valid" means that either
512 * the Transition has no target/targetId list (the default, in which
513 * cause the transition should act on all views in the hiearchy), or
514 * the given view is in the target list or the view id is in the
515 * targetId list. If the target parameter is null, then the target list
516 * is not checked (this is in the case of ListView items, where the
517 * views are ignored and only the ids are used).
518 */
519 boolean isValidTarget(View target, long targetId) {
Chet Haaseff58f922013-09-11 13:08:18 -0700520 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
521 return false;
522 }
523 if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
524 return false;
525 }
526 if (mTargetTypeExcludes != null && target != null) {
527 int numTypes = mTargetTypeExcludes.size();
528 for (int i = 0; i < numTypes; ++i) {
529 Class type = mTargetTypeExcludes.get(i);
530 if (type.isInstance(target)) {
531 return false;
532 }
533 }
534 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700535 if (mTargetIds.size() == 0 && mTargets.size() == 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700536 return true;
537 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700538 if (mTargetIds.size() > 0) {
539 for (int i = 0; i < mTargetIds.size(); ++i) {
540 if (mTargetIds.get(i) == targetId) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700541 return true;
542 }
543 }
544 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700545 if (target != null && mTargets.size() > 0) {
546 for (int i = 0; i < mTargets.size(); ++i) {
547 if (mTargets.get(i) == target) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700548 return true;
549 }
550 }
551 }
552 return false;
553 }
554
George Mount0a778ed2013-12-13 13:35:36 -0800555 /** @hide */
556 public static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
Chet Haase199acdf2013-07-24 18:40:55 -0700557 ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
558 if (runningAnimators == null) {
559 runningAnimators = new ArrayMap<Animator, AnimationInfo>();
560 sRunningAnimators.set(runningAnimators);
561 }
562 return runningAnimators;
563 }
564
Chet Haasefaebd8f2012-05-18 14:17:57 -0700565 /**
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700566 * This is called internally once all animations have been set up by the
567 * transition hierarchy. \
Chet Haasefaebd8f2012-05-18 14:17:57 -0700568 *
569 * @hide
570 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700571 protected void runAnimators() {
Chet Haase199acdf2013-07-24 18:40:55 -0700572 if (DBG) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700573 Log.d(LOG_TAG, "runAnimators() on " + this);
Chet Haasec43524f2013-07-16 14:40:11 -0700574 }
Chet Haase199acdf2013-07-24 18:40:55 -0700575 start();
576 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
Chet Haased82c8ac2013-08-26 14:20:16 -0700577 // Now start every Animator that was previously created for this transition
Chet Haase199acdf2013-07-24 18:40:55 -0700578 for (Animator anim : mAnimators) {
Chet Haasec43524f2013-07-16 14:40:11 -0700579 if (DBG) {
580 Log.d(LOG_TAG, " anim: " + anim);
581 }
Chet Haase199acdf2013-07-24 18:40:55 -0700582 if (runningAnimators.containsKey(anim)) {
583 start();
584 runAnimator(anim, runningAnimators);
585 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700586 }
Chet Haase199acdf2013-07-24 18:40:55 -0700587 mAnimators.clear();
588 end();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700589 }
590
Chet Haase199acdf2013-07-24 18:40:55 -0700591 private void runAnimator(Animator animator,
592 final ArrayMap<Animator, AnimationInfo> runningAnimators) {
Chet Haasee9d32ea2013-06-04 08:46:42 -0700593 if (animator != null) {
594 // TODO: could be a single listener instance for all of them since it uses the param
595 animator.addListener(new AnimatorListenerAdapter() {
596 @Override
597 public void onAnimationStart(Animator animation) {
598 mCurrentAnimators.add(animation);
599 }
600 @Override
601 public void onAnimationEnd(Animator animation) {
Chet Haase199acdf2013-07-24 18:40:55 -0700602 runningAnimators.remove(animation);
Chet Haasee9d32ea2013-06-04 08:46:42 -0700603 mCurrentAnimators.remove(animation);
604 }
605 });
606 animate(animator);
607 }
608 }
609
Chet Haasefaebd8f2012-05-18 14:17:57 -0700610 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700611 * Captures the values in the start scene for the properties that this
612 * transition monitors. These values are then passed as the startValues
613 * structure in a later call to
614 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
615 * The main concern for an implementation is what the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700616 * properties are that the transition cares about and what the values are
617 * for all of those properties. The start and end values will be compared
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700618 * later during the
Chet Haased82c8ac2013-08-26 14:20:16 -0700619 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700620 * method to determine what, if any, animations, should be run.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700621 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700622 * <p>Subclasses must implement this method. The method should only be called by the
623 * transition system; it is not intended to be called from external classes.</p>
624 *
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700625 * @param transitionValues The holder for any values that the Transition
626 * wishes to store. Values are stored in the <code>values</code> field
627 * of this TransitionValues object and are keyed from
628 * a String value. For example, to store a view's rotation value,
629 * a transition might call
630 * <code>transitionValues.values.put("appname:transitionname:rotation",
631 * view.getRotation())</code>. The target view will already be stored in
632 * the transitionValues structure when this method is called.
Chet Haased82c8ac2013-08-26 14:20:16 -0700633 *
634 * @see #captureEndValues(TransitionValues)
635 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
Chet Haasefaebd8f2012-05-18 14:17:57 -0700636 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700637 public abstract void captureStartValues(TransitionValues transitionValues);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700638
639 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700640 * Captures the values in the end scene for the properties that this
641 * transition monitors. These values are then passed as the endValues
642 * structure in a later call to
643 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
644 * The main concern for an implementation is what the
645 * properties are that the transition cares about and what the values are
646 * for all of those properties. The start and end values will be compared
647 * later during the
648 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
649 * method to determine what, if any, animations, should be run.
650 *
651 * <p>Subclasses must implement this method. The method should only be called by the
652 * transition system; it is not intended to be called from external classes.</p>
653 *
654 * @param transitionValues The holder for any values that the Transition
655 * wishes to store. Values are stored in the <code>values</code> field
656 * of this TransitionValues object and are keyed from
657 * a String value. For example, to store a view's rotation value,
658 * a transition might call
659 * <code>transitionValues.values.put("appname:transitionname:rotation",
660 * view.getRotation())</code>. The target view will already be stored in
661 * the transitionValues structure when this method is called.
662 *
663 * @see #captureStartValues(TransitionValues)
664 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
665 */
666 public abstract void captureEndValues(TransitionValues transitionValues);
667
668 /**
669 * Adds the id of a target view that this Transition is interested in
Chet Haasefaebd8f2012-05-18 14:17:57 -0700670 * animating. By default, there are no targetIds, and a Transition will
671 * listen for changes on every view in the hierarchy below the sceneRoot
Chet Haased82c8ac2013-08-26 14:20:16 -0700672 * of the Scene being transitioned into. Setting targetIds constrains
Chet Haasefaebd8f2012-05-18 14:17:57 -0700673 * the Transition to only listen for, and act on, views with these IDs.
674 * Views with different IDs, or no IDs whatsoever, will be ignored.
675 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700676 * <p>Note that using ids to specify targets implies that ids should be unique
677 * within the view hierarchy underneat the scene root.</p>
678 *
Chet Haasefaebd8f2012-05-18 14:17:57 -0700679 * @see View#getId()
Chet Haased82c8ac2013-08-26 14:20:16 -0700680 * @param targetId The id of a target view, must be a positive number.
681 * @return The Transition to which the targetId is added.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700682 * Returning the same object makes it easier to chain calls during
683 * construction, such as
Chet Haaseff58f922013-09-11 13:08:18 -0700684 * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700685 */
Chet Haaseff58f922013-09-11 13:08:18 -0700686 public Transition addTarget(int targetId) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700687 if (targetId > 0) {
688 mTargetIds.add(targetId);
689 }
690 return this;
691 }
692
693 /**
694 * Removes the given targetId from the list of ids that this Transition
695 * is interested in animating.
696 *
697 * @param targetId The id of a target view, must be a positive number.
698 * @return The Transition from which the targetId is removed.
699 * Returning the same object makes it easier to chain calls during
700 * construction, such as
701 * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
702 */
Chet Haaseff58f922013-09-11 13:08:18 -0700703 public Transition removeTarget(int targetId) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700704 if (targetId > 0) {
705 mTargetIds.remove(targetId);
706 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700707 return this;
708 }
709
710 /**
Chet Haaseff58f922013-09-11 13:08:18 -0700711 * Whether to add the given id to the list of target ids to exclude from this
712 * transition. The <code>exclude</code> parameter specifies whether the target
713 * should be added to or removed from the excluded list.
714 *
715 * <p>Excluding targets is a general mechanism for allowing transitions to run on
716 * a view hierarchy while skipping target views that should not be part of
717 * the transition. For example, you may want to avoid animating children
718 * of a specific ListView or Spinner. Views can be excluded either by their
719 * id, or by their instance reference, or by the Class of that view
720 * (eg, {@link Spinner}).</p>
721 *
722 * @see #excludeChildren(int, boolean)
723 * @see #excludeTarget(View, boolean)
724 * @see #excludeTarget(Class, boolean)
725 *
726 * @param targetId The id of a target to ignore when running this transition.
727 * @param exclude Whether to add the target to or remove the target from the
728 * current list of excluded targets.
729 * @return This transition object.
730 */
731 public Transition excludeTarget(int targetId, boolean exclude) {
732 mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
733 return this;
734 }
735
736 /**
737 * Whether to add the children of the given id to the list of targets to exclude
738 * from this transition. The <code>exclude</code> parameter specifies whether
739 * the children of the target should be added to or removed from the excluded list.
740 * Excluding children in this way provides a simple mechanism for excluding all
741 * children of specific targets, rather than individually excluding each
742 * child individually.
743 *
744 * <p>Excluding targets is a general mechanism for allowing transitions to run on
745 * a view hierarchy while skipping target views that should not be part of
746 * the transition. For example, you may want to avoid animating children
747 * of a specific ListView or Spinner. Views can be excluded either by their
748 * id, or by their instance reference, or by the Class of that view
749 * (eg, {@link Spinner}).</p>
750 *
751 * @see #excludeTarget(int, boolean)
752 * @see #excludeChildren(View, boolean)
753 * @see #excludeChildren(Class, boolean)
754 *
755 * @param targetId The id of a target whose children should be ignored when running
756 * this transition.
757 * @param exclude Whether to add the target to or remove the target from the
758 * current list of excluded-child targets.
759 * @return This transition object.
760 */
761 public Transition excludeChildren(int targetId, boolean exclude) {
762 mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
763 return this;
764 }
765
766 /**
767 * Utility method to manage the boilerplate code that is the same whether we
768 * are excluding targets or their children.
769 */
770 private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
771 if (targetId > 0) {
772 if (exclude) {
773 list = ArrayListManager.add(list, targetId);
774 } else {
775 list = ArrayListManager.remove(list, targetId);
776 }
777 }
778 return list;
779 }
780
781 /**
782 * Whether to add the given target to the list of targets to exclude from this
783 * transition. The <code>exclude</code> parameter specifies whether the target
784 * should be added to or removed from the excluded list.
785 *
786 * <p>Excluding targets is a general mechanism for allowing transitions to run on
787 * a view hierarchy while skipping target views that should not be part of
788 * the transition. For example, you may want to avoid animating children
789 * of a specific ListView or Spinner. Views can be excluded either by their
790 * id, or by their instance reference, or by the Class of that view
791 * (eg, {@link Spinner}).</p>
792 *
793 * @see #excludeChildren(View, boolean)
794 * @see #excludeTarget(int, boolean)
795 * @see #excludeTarget(Class, boolean)
796 *
797 * @param target The target to ignore when running this transition.
798 * @param exclude Whether to add the target to or remove the target from the
799 * current list of excluded targets.
800 * @return This transition object.
801 */
802 public Transition excludeTarget(View target, boolean exclude) {
803 mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
804 return this;
805 }
806
807 /**
808 * Whether to add the children of given target to the list of target children
809 * to exclude from this transition. The <code>exclude</code> parameter specifies
810 * whether the target should be added to or removed from the excluded list.
811 *
812 * <p>Excluding targets is a general mechanism for allowing transitions to run on
813 * a view hierarchy while skipping target views that should not be part of
814 * the transition. For example, you may want to avoid animating children
815 * of a specific ListView or Spinner. Views can be excluded either by their
816 * id, or by their instance reference, or by the Class of that view
817 * (eg, {@link Spinner}).</p>
818 *
819 * @see #excludeTarget(View, boolean)
820 * @see #excludeChildren(int, boolean)
821 * @see #excludeChildren(Class, boolean)
822 *
823 * @param target The target to ignore when running this transition.
824 * @param exclude Whether to add the target to or remove the target from the
825 * current list of excluded targets.
826 * @return This transition object.
827 */
828 public Transition excludeChildren(View target, boolean exclude) {
829 mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
830 return this;
831 }
832
833 /**
834 * Utility method to manage the boilerplate code that is the same whether we
835 * are excluding targets or their children.
836 */
837 private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
838 if (target != null) {
839 if (exclude) {
840 list = ArrayListManager.add(list, target);
841 } else {
842 list = ArrayListManager.remove(list, target);
843 }
844 }
845 return list;
846 }
847
848 /**
849 * Whether to add the given type to the list of types to exclude from this
850 * transition. The <code>exclude</code> parameter specifies whether the target
851 * type should be added to or removed from the excluded list.
852 *
853 * <p>Excluding targets is a general mechanism for allowing transitions to run on
854 * a view hierarchy while skipping target views that should not be part of
855 * the transition. For example, you may want to avoid animating children
856 * of a specific ListView or Spinner. Views can be excluded either by their
857 * id, or by their instance reference, or by the Class of that view
858 * (eg, {@link Spinner}).</p>
859 *
860 * @see #excludeChildren(Class, boolean)
861 * @see #excludeTarget(int, boolean)
862 * @see #excludeTarget(View, boolean)
863 *
864 * @param type The type to ignore when running this transition.
865 * @param exclude Whether to add the target type to or remove it from the
866 * current list of excluded target types.
867 * @return This transition object.
868 */
869 public Transition excludeTarget(Class type, boolean exclude) {
870 mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
871 return this;
872 }
873
874 /**
875 * Whether to add the given type to the list of types whose children should
876 * be excluded from this transition. The <code>exclude</code> parameter
877 * specifies whether the target type should be added to or removed from
878 * the excluded list.
879 *
880 * <p>Excluding targets is a general mechanism for allowing transitions to run on
881 * a view hierarchy while skipping target views that should not be part of
882 * the transition. For example, you may want to avoid animating children
883 * of a specific ListView or Spinner. Views can be excluded either by their
884 * id, or by their instance reference, or by the Class of that view
885 * (eg, {@link Spinner}).</p>
886 *
887 * @see #excludeTarget(Class, boolean)
888 * @see #excludeChildren(int, boolean)
889 * @see #excludeChildren(View, boolean)
890 *
891 * @param type The type to ignore when running this transition.
892 * @param exclude Whether to add the target type to or remove it from the
893 * current list of excluded target types.
894 * @return This transition object.
895 */
896 public Transition excludeChildren(Class type, boolean exclude) {
897 mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
898 return this;
899 }
900
901 /**
902 * Utility method to manage the boilerplate code that is the same whether we
903 * are excluding targets or their children.
904 */
905 private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
906 if (type != null) {
907 if (exclude) {
908 list = ArrayListManager.add(list, type);
909 } else {
910 list = ArrayListManager.remove(list, type);
911 }
912 }
913 return list;
914 }
915
916 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -0700917 * Sets the target view instances that this Transition is interested in
918 * animating. By default, there are no targets, and a Transition will
919 * listen for changes on every view in the hierarchy below the sceneRoot
920 * of the Scene being transitioned into. Setting targets constrains
921 * the Transition to only listen for, and act on, these views.
922 * All other views will be ignored.
923 *
Chet Haaseff58f922013-09-11 13:08:18 -0700924 * <p>The target list is like the {@link #addTarget(int) targetId}
Chet Haasefaebd8f2012-05-18 14:17:57 -0700925 * list except this list specifies the actual View instances, not the ids
926 * of the views. This is an important distinction when scene changes involve
927 * view hierarchies which have been inflated separately; different views may
928 * share the same id but not actually be the same instance. If the transition
Chet Haaseff58f922013-09-11 13:08:18 -0700929 * should treat those views as the same, then {@link #addTarget(int)} should be used
Chet Haased82c8ac2013-08-26 14:20:16 -0700930 * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
Chet Haasefaebd8f2012-05-18 14:17:57 -0700931 * changes all within the same view hierarchy, among views which do not
Chet Haased82c8ac2013-08-26 14:20:16 -0700932 * necessarily have ids set on them, then the target list of views may be more
Chet Haasefaebd8f2012-05-18 14:17:57 -0700933 * convenient.</p>
934 *
Chet Haaseff58f922013-09-11 13:08:18 -0700935 * @see #addTarget(int)
Chet Haased82c8ac2013-08-26 14:20:16 -0700936 * @param target A View on which the Transition will act, must be non-null.
937 * @return The Transition to which the target is added.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700938 * Returning the same object makes it easier to chain calls during
939 * construction, such as
Chet Haased82c8ac2013-08-26 14:20:16 -0700940 * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700941 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700942 public Transition addTarget(View target) {
943 mTargets.add(target);
944 return this;
945 }
946
947 /**
948 * Removes the given target from the list of targets that this Transition
949 * is interested in animating.
950 *
951 * @param target The target view, must be non-null.
952 * @return Transition The Transition from which the target is removed.
953 * Returning the same object makes it easier to chain calls during
954 * construction, such as
955 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
956 */
957 public Transition removeTarget(View target) {
958 if (target != null) {
959 mTargets.remove(target);
960 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700961 return this;
962 }
963
964 /**
965 * Returns the array of target IDs that this transition limits itself to
966 * tracking and animating. If the array is null for both this method and
967 * {@link #getTargets()}, then this transition is
968 * not limited to specific views, and will handle changes to any views
969 * in the hierarchy of a scene change.
970 *
971 * @return the list of target IDs
972 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700973 public List<Integer> getTargetIds() {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700974 return mTargetIds;
975 }
976
977 /**
978 * Returns the array of target views that this transition limits itself to
979 * tracking and animating. If the array is null for both this method and
980 * {@link #getTargetIds()}, then this transition is
981 * not limited to specific views, and will handle changes to any views
982 * in the hierarchy of a scene change.
983 *
984 * @return the list of target views
985 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700986 public List<View> getTargets() {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700987 return mTargets;
988 }
989
990 /**
991 * Recursive method that captures values for the given view and the
992 * hierarchy underneath it.
993 * @param sceneRoot The root of the view hierarchy being captured
994 * @param start true if this capture is happening before the scene change,
995 * false otherwise
996 */
997 void captureValues(ViewGroup sceneRoot, boolean start) {
Chet Haasedf32aa82013-10-21 17:19:37 -0700998 clearValues(start);
Chet Haased82c8ac2013-08-26 14:20:16 -0700999 if (mTargetIds.size() > 0 || mTargets.size() > 0) {
1000 if (mTargetIds.size() > 0) {
1001 for (int i = 0; i < mTargetIds.size(); ++i) {
1002 int id = mTargetIds.get(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001003 View view = sceneRoot.findViewById(id);
1004 if (view != null) {
1005 TransitionValues values = new TransitionValues();
1006 values.view = view;
Chet Haased82c8ac2013-08-26 14:20:16 -07001007 if (start) {
1008 captureStartValues(values);
1009 } else {
1010 captureEndValues(values);
1011 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001012 if (start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001013 mStartValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -07001014 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001015 mStartValues.idValues.put(id, values);
Chet Haase867a8662013-06-03 07:30:21 -07001016 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001017 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001018 mEndValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -07001019 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001020 mEndValues.idValues.put(id, values);
Chet Haase867a8662013-06-03 07:30:21 -07001021 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001022 }
1023 }
1024 }
1025 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001026 if (mTargets.size() > 0) {
1027 for (int i = 0; i < mTargets.size(); ++i) {
1028 View view = mTargets.get(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001029 if (view != null) {
1030 TransitionValues values = new TransitionValues();
1031 values.view = view;
Chet Haased82c8ac2013-08-26 14:20:16 -07001032 if (start) {
1033 captureStartValues(values);
1034 } else {
1035 captureEndValues(values);
1036 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001037 if (start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001038 mStartValues.viewValues.put(view, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001039 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001040 mEndValues.viewValues.put(view, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001041 }
1042 }
1043 }
1044 }
1045 } else {
1046 captureHierarchy(sceneRoot, start);
1047 }
1048 }
1049
1050 /**
Chet Haasedf32aa82013-10-21 17:19:37 -07001051 * Clear valuesMaps for specified start/end state
1052 *
1053 * @param start true if the start values should be cleared, false otherwise
1054 */
1055 void clearValues(boolean start) {
1056 if (start) {
1057 mStartValues.viewValues.clear();
1058 mStartValues.idValues.clear();
1059 mStartValues.itemIdValues.clear();
1060 } else {
1061 mEndValues.viewValues.clear();
1062 mEndValues.idValues.clear();
1063 mEndValues.itemIdValues.clear();
1064 }
1065 }
1066
1067 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -07001068 * Recursive method which captures values for an entire view hierarchy,
1069 * starting at some root view. Transitions without targetIDs will use this
1070 * method to capture values for all possible views.
1071 *
1072 * @param view The view for which to capture values. Children of this View
1073 * will also be captured, recursively down to the leaf nodes.
1074 * @param start true if values are being captured in the start scene, false
1075 * otherwise.
1076 */
1077 private void captureHierarchy(View view, boolean start) {
1078 if (view == null) {
1079 return;
1080 }
George Mount0a778ed2013-12-13 13:35:36 -08001081 if (!isValidTarget(view, view.getId())) {
1082 return;
1083 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001084 boolean isListViewItem = false;
1085 if (view.getParent() instanceof ListView) {
1086 isListViewItem = true;
1087 }
1088 if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
1089 // ignore listview children unless we can track them with stable IDs
1090 return;
1091 }
Chet Haaseff58f922013-09-11 13:08:18 -07001092 int id = View.NO_ID;
1093 long itemId = View.NO_ID;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001094 if (!isListViewItem) {
1095 id = view.getId();
1096 } else {
1097 ListView listview = (ListView) view.getParent();
1098 int position = listview.getPositionForView(view);
Chet Haaseff58f922013-09-11 13:08:18 -07001099 itemId = listview.getItemIdAtPosition(position);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001100 view.setHasTransientState(true);
1101 }
Chet Haaseff58f922013-09-11 13:08:18 -07001102 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
1103 return;
1104 }
1105 if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
1106 return;
1107 }
1108 if (mTargetTypeExcludes != null && view != null) {
1109 int numTypes = mTargetTypeExcludes.size();
1110 for (int i = 0; i < numTypes; ++i) {
1111 if (mTargetTypeExcludes.get(i).isInstance(view)) {
1112 return;
1113 }
1114 }
1115 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001116 TransitionValues values = new TransitionValues();
1117 values.view = view;
Chet Haased8d7c382013-09-23 11:26:36 -07001118 if (start) {
1119 captureStartValues(values);
1120 } else {
1121 captureEndValues(values);
1122 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001123 if (start) {
1124 if (!isListViewItem) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001125 mStartValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -07001126 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001127 mStartValues.idValues.put((int) id, values);
Chet Haase867a8662013-06-03 07:30:21 -07001128 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001129 } else {
Chet Haaseff58f922013-09-11 13:08:18 -07001130 mStartValues.itemIdValues.put(itemId, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001131 }
1132 } else {
1133 if (!isListViewItem) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001134 mEndValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -07001135 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001136 mEndValues.idValues.put((int) id, values);
Chet Haase867a8662013-06-03 07:30:21 -07001137 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001138 } else {
Chet Haaseff58f922013-09-11 13:08:18 -07001139 mEndValues.itemIdValues.put(itemId, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001140 }
1141 }
1142 if (view instanceof ViewGroup) {
Chet Haaseff58f922013-09-11 13:08:18 -07001143 // Don't traverse child hierarchy if there are any child-excludes on this view
1144 if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
1145 return;
1146 }
1147 if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
1148 return;
1149 }
1150 if (mTargetTypeChildExcludes != null && view != null) {
1151 int numTypes = mTargetTypeChildExcludes.size();
1152 for (int i = 0; i < numTypes; ++i) {
1153 if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
1154 return;
1155 }
1156 }
1157 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001158 ViewGroup parent = (ViewGroup) view;
1159 for (int i = 0; i < parent.getChildCount(); ++i) {
1160 captureHierarchy(parent.getChildAt(i), start);
1161 }
1162 }
1163 }
1164
1165 /**
Chet Haase6ebe3de2013-06-17 16:50:50 -07001166 * This method can be called by transitions to get the TransitionValues for
1167 * any particular view during the transition-playing process. This might be
1168 * necessary, for example, to query the before/after state of related views
1169 * for a given transition.
1170 */
Chet Haased82c8ac2013-08-26 14:20:16 -07001171 public TransitionValues getTransitionValues(View view, boolean start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001172 if (mParent != null) {
1173 return mParent.getTransitionValues(view, start);
1174 }
1175 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
1176 TransitionValues values = valuesMaps.viewValues.get(view);
1177 if (values == null) {
1178 int id = view.getId();
1179 if (id >= 0) {
1180 values = valuesMaps.idValues.get(id);
1181 }
1182 if (values == null && view.getParent() instanceof ListView) {
1183 ListView listview = (ListView) view.getParent();
1184 int position = listview.getPositionForView(view);
1185 long itemId = listview.getItemIdAtPosition(position);
1186 values = valuesMaps.itemIdValues.get(itemId);
1187 }
1188 // TODO: Doesn't handle the case where a view was parented to a
1189 // ListView (with an itemId), but no longer is
1190 }
1191 return values;
1192 }
1193
1194 /**
Chet Haase199acdf2013-07-24 18:40:55 -07001195 * Pauses this transition, sending out calls to {@link
1196 * TransitionListener#onTransitionPause(Transition)} to all listeners
1197 * and pausing all running animators started by this transition.
1198 *
1199 * @hide
1200 */
1201 public void pause() {
Chet Haasea56205c2013-09-10 11:30:22 -07001202 if (!mEnded) {
1203 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1204 int numOldAnims = runningAnimators.size();
1205 for (int i = numOldAnims - 1; i >= 0; i--) {
1206 Animator anim = runningAnimators.keyAt(i);
1207 anim.pause();
Chet Haase199acdf2013-07-24 18:40:55 -07001208 }
Chet Haasea56205c2013-09-10 11:30:22 -07001209 if (mListeners != null && mListeners.size() > 0) {
1210 ArrayList<TransitionListener> tmpListeners =
1211 (ArrayList<TransitionListener>) mListeners.clone();
1212 int numListeners = tmpListeners.size();
1213 for (int i = 0; i < numListeners; ++i) {
1214 tmpListeners.get(i).onTransitionPause(this);
1215 }
1216 }
1217 mPaused = true;
Chet Haase199acdf2013-07-24 18:40:55 -07001218 }
Chet Haase199acdf2013-07-24 18:40:55 -07001219 }
1220
1221 /**
1222 * Resumes this transition, sending out calls to {@link
1223 * TransitionListener#onTransitionPause(Transition)} to all listeners
1224 * and pausing all running animators started by this transition.
1225 *
1226 * @hide
1227 */
1228 public void resume() {
1229 if (mPaused) {
Chet Haasea56205c2013-09-10 11:30:22 -07001230 if (!mEnded) {
1231 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1232 int numOldAnims = runningAnimators.size();
1233 for (int i = numOldAnims - 1; i >= 0; i--) {
1234 Animator anim = runningAnimators.keyAt(i);
1235 anim.resume();
1236 }
1237 if (mListeners != null && mListeners.size() > 0) {
1238 ArrayList<TransitionListener> tmpListeners =
1239 (ArrayList<TransitionListener>) mListeners.clone();
1240 int numListeners = tmpListeners.size();
1241 for (int i = 0; i < numListeners; ++i) {
1242 tmpListeners.get(i).onTransitionResume(this);
1243 }
Chet Haase199acdf2013-07-24 18:40:55 -07001244 }
1245 }
1246 mPaused = false;
1247 }
1248 }
1249
1250 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -07001251 * Called by TransitionManager to play the transition. This calls
Chet Haased82c8ac2013-08-26 14:20:16 -07001252 * createAnimators() to set things up and create all of the animations and then
Chet Haase2ea7f8b2013-06-21 15:00:05 -07001253 * runAnimations() to actually start the animations.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001254 */
Chet Haase6ebe3de2013-06-17 16:50:50 -07001255 void playTransition(ViewGroup sceneRoot) {
Chet Haase199acdf2013-07-24 18:40:55 -07001256 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1257 int numOldAnims = runningAnimators.size();
1258 for (int i = numOldAnims - 1; i >= 0; i--) {
1259 Animator anim = runningAnimators.keyAt(i);
1260 if (anim != null) {
Chet Haase199acdf2013-07-24 18:40:55 -07001261 AnimationInfo oldInfo = runningAnimators.get(anim);
Chet Haase58ad1222013-10-30 06:37:29 -07001262 if (oldInfo != null && oldInfo.view != null &&
1263 oldInfo.view.getContext() == sceneRoot.getContext()) {
Chet Haase199acdf2013-07-24 18:40:55 -07001264 boolean cancel = false;
1265 TransitionValues oldValues = oldInfo.values;
1266 View oldView = oldInfo.view;
1267 TransitionValues newValues = mEndValues.viewValues != null ?
1268 mEndValues.viewValues.get(oldView) : null;
Chet Haase23c61f62013-09-14 11:28:46 -07001269 if (newValues == null) {
1270 newValues = mEndValues.idValues.get(oldView.getId());
1271 }
Chet Haaseaf78bdd2013-08-27 16:06:26 -07001272 if (oldValues != null) {
1273 // if oldValues null, then transition didn't care to stash values,
1274 // and won't get canceled
Chet Haase23c61f62013-09-14 11:28:46 -07001275 if (newValues != null) {
Chet Haaseaf78bdd2013-08-27 16:06:26 -07001276 for (String key : oldValues.values.keySet()) {
1277 Object oldValue = oldValues.values.get(key);
1278 Object newValue = newValues.values.get(key);
1279 if (oldValue != null && newValue != null &&
1280 !oldValue.equals(newValue)) {
1281 cancel = true;
1282 if (DBG) {
1283 Log.d(LOG_TAG, "Transition.playTransition: " +
1284 "oldValue != newValue for " + key +
1285 ": old, new = " + oldValue + ", " + newValue);
1286 }
1287 break;
Chet Haase199acdf2013-07-24 18:40:55 -07001288 }
Chet Haase199acdf2013-07-24 18:40:55 -07001289 }
1290 }
1291 }
1292 if (cancel) {
1293 if (anim.isRunning() || anim.isStarted()) {
1294 if (DBG) {
1295 Log.d(LOG_TAG, "Canceling anim " + anim);
1296 }
1297 anim.cancel();
1298 } else {
1299 if (DBG) {
1300 Log.d(LOG_TAG, "removing anim from info list: " + anim);
1301 }
1302 runningAnimators.remove(anim);
1303 }
1304 }
1305 }
1306 }
1307 }
1308
Chet Haased82c8ac2013-08-26 14:20:16 -07001309 createAnimators(sceneRoot, mStartValues, mEndValues);
1310 runAnimators();
Chet Haasefaebd8f2012-05-18 14:17:57 -07001311 }
1312
1313 /**
1314 * This is a utility method used by subclasses to handle standard parts of
1315 * setting up and running an Animator: it sets the {@link #getDuration()
1316 * duration} and the {@link #getStartDelay() startDelay}, starts the
Chet Haase199acdf2013-07-24 18:40:55 -07001317 * animation, and, when the animator ends, calls {@link #end()}.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001318 *
1319 * @param animator The Animator to be run during this transition.
1320 *
1321 * @hide
1322 */
1323 protected void animate(Animator animator) {
1324 // TODO: maybe pass auto-end as a boolean parameter?
1325 if (animator == null) {
Chet Haase199acdf2013-07-24 18:40:55 -07001326 end();
Chet Haasefaebd8f2012-05-18 14:17:57 -07001327 } else {
1328 if (getDuration() >= 0) {
1329 animator.setDuration(getDuration());
1330 }
1331 if (getStartDelay() >= 0) {
1332 animator.setStartDelay(getStartDelay());
1333 }
1334 if (getInterpolator() != null) {
1335 animator.setInterpolator(getInterpolator());
1336 }
1337 animator.addListener(new AnimatorListenerAdapter() {
1338 @Override
Chet Haasefaebd8f2012-05-18 14:17:57 -07001339 public void onAnimationEnd(Animator animation) {
Chet Haase199acdf2013-07-24 18:40:55 -07001340 end();
Chet Haasefaebd8f2012-05-18 14:17:57 -07001341 animation.removeListener(this);
1342 }
1343 });
1344 animator.start();
1345 }
1346 }
1347
1348 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -07001349 * This method is called automatically by the transition and
Chet Haased82c8ac2013-08-26 14:20:16 -07001350 * TransitionSet classes prior to a Transition subclass starting;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001351 * subclasses should not need to call it directly.
1352 *
1353 * @hide
1354 */
Chet Haase199acdf2013-07-24 18:40:55 -07001355 protected void start() {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001356 if (mNumInstances == 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001357 if (mListeners != null && mListeners.size() > 0) {
1358 ArrayList<TransitionListener> tmpListeners =
1359 (ArrayList<TransitionListener>) mListeners.clone();
1360 int numListeners = tmpListeners.size();
1361 for (int i = 0; i < numListeners; ++i) {
1362 tmpListeners.get(i).onTransitionStart(this);
1363 }
1364 }
Chet Haasea56205c2013-09-10 11:30:22 -07001365 mEnded = false;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001366 }
1367 mNumInstances++;
1368 }
1369
1370 /**
1371 * This method is called automatically by the Transition and
Chet Haased82c8ac2013-08-26 14:20:16 -07001372 * TransitionSet classes when a transition finishes, either because
Chet Haasefaebd8f2012-05-18 14:17:57 -07001373 * a transition did nothing (returned a null Animator from
Chet Haased82c8ac2013-08-26 14:20:16 -07001374 * {@link Transition#createAnimator(ViewGroup, TransitionValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -07001375 * TransitionValues)}) or because the transition returned a valid
Chet Haase199acdf2013-07-24 18:40:55 -07001376 * Animator and end() was called in the onAnimationEnd()
Chet Haasefaebd8f2012-05-18 14:17:57 -07001377 * callback of the AnimatorListener.
1378 *
1379 * @hide
1380 */
Chet Haase199acdf2013-07-24 18:40:55 -07001381 protected void end() {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001382 --mNumInstances;
1383 if (mNumInstances == 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001384 if (mListeners != null && mListeners.size() > 0) {
1385 ArrayList<TransitionListener> tmpListeners =
1386 (ArrayList<TransitionListener>) mListeners.clone();
1387 int numListeners = tmpListeners.size();
1388 for (int i = 0; i < numListeners; ++i) {
1389 tmpListeners.get(i).onTransitionEnd(this);
1390 }
1391 }
Chet Haase6ebe3de2013-06-17 16:50:50 -07001392 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
1393 TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001394 View v = tv.view;
1395 if (v.hasTransientState()) {
1396 v.setHasTransientState(false);
1397 }
1398 }
Chet Haase6ebe3de2013-06-17 16:50:50 -07001399 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
1400 TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001401 View v = tv.view;
1402 if (v.hasTransientState()) {
1403 v.setHasTransientState(false);
1404 }
1405 }
Chet Haasea56205c2013-09-10 11:30:22 -07001406 mEnded = true;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001407 }
1408 }
1409
1410 /**
1411 * This method cancels a transition that is currently running.
Chet Haased82c8ac2013-08-26 14:20:16 -07001412 *
1413 * @hide
Chet Haasefaebd8f2012-05-18 14:17:57 -07001414 */
Chet Haase199acdf2013-07-24 18:40:55 -07001415 protected void cancel() {
Chet Haasee9d32ea2013-06-04 08:46:42 -07001416 int numAnimators = mCurrentAnimators.size();
Chet Haase25a738f2013-06-04 16:35:14 -07001417 for (int i = numAnimators - 1; i >= 0; i--) {
Chet Haasee9d32ea2013-06-04 08:46:42 -07001418 Animator animator = mCurrentAnimators.get(i);
1419 animator.cancel();
1420 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001421 if (mListeners != null && mListeners.size() > 0) {
1422 ArrayList<TransitionListener> tmpListeners =
1423 (ArrayList<TransitionListener>) mListeners.clone();
1424 int numListeners = tmpListeners.size();
1425 for (int i = 0; i < numListeners; ++i) {
1426 tmpListeners.get(i).onTransitionCancel(this);
1427 }
1428 }
1429 }
1430
1431 /**
1432 * Adds a listener to the set of listeners that are sent events through the
1433 * life of an animation, such as start, repeat, and end.
1434 *
1435 * @param listener the listener to be added to the current set of listeners
1436 * for this animation.
Chet Haased82c8ac2013-08-26 14:20:16 -07001437 * @return This transition object.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001438 */
Chet Haased82c8ac2013-08-26 14:20:16 -07001439 public Transition addListener(TransitionListener listener) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001440 if (mListeners == null) {
1441 mListeners = new ArrayList<TransitionListener>();
1442 }
1443 mListeners.add(listener);
Chet Haased82c8ac2013-08-26 14:20:16 -07001444 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001445 }
1446
1447 /**
1448 * Removes a listener from the set listening to this animation.
1449 *
1450 * @param listener the listener to be removed from the current set of
1451 * listeners for this transition.
Chet Haased82c8ac2013-08-26 14:20:16 -07001452 * @return This transition object.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001453 */
Chet Haased82c8ac2013-08-26 14:20:16 -07001454 public Transition removeListener(TransitionListener listener) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001455 if (mListeners == null) {
Chet Haased82c8ac2013-08-26 14:20:16 -07001456 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001457 }
1458 mListeners.remove(listener);
1459 if (mListeners.size() == 0) {
1460 mListeners = null;
1461 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001462 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001463 }
1464
Chet Haased82c8ac2013-08-26 14:20:16 -07001465 Transition setSceneRoot(ViewGroup sceneRoot) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001466 mSceneRoot = sceneRoot;
Chet Haased82c8ac2013-08-26 14:20:16 -07001467 return this;
Chet Haase6ebe3de2013-06-17 16:50:50 -07001468 }
1469
Chet Haaseb7a7fc92013-09-20 16:33:08 -07001470 void setCanRemoveViews(boolean canRemoveViews) {
1471 mCanRemoveViews = canRemoveViews;
1472 }
1473
George Mount0a778ed2013-12-13 13:35:36 -08001474 public boolean canRemoveViews() {
1475 return mCanRemoveViews;
1476 }
1477
Chet Haasefaebd8f2012-05-18 14:17:57 -07001478 @Override
1479 public String toString() {
1480 return toString("");
1481 }
1482
Chet Haase6ebe3de2013-06-17 16:50:50 -07001483 @Override
1484 public Transition clone() {
1485 Transition clone = null;
1486 try {
1487 clone = (Transition) super.clone();
Chet Haase199acdf2013-07-24 18:40:55 -07001488 clone.mAnimators = new ArrayList<Animator>();
Chet Haase7660d122013-09-13 13:29:31 -07001489 clone.mStartValues = new TransitionValuesMaps();
1490 clone.mEndValues = new TransitionValuesMaps();
Chet Haase6ebe3de2013-06-17 16:50:50 -07001491 } catch (CloneNotSupportedException e) {}
1492
1493 return clone;
1494 }
1495
Chet Haase199acdf2013-07-24 18:40:55 -07001496 /**
1497 * Returns the name of this Transition. This name is used internally to distinguish
1498 * between different transitions to determine when interrupting transitions overlap.
Chet Haased82c8ac2013-08-26 14:20:16 -07001499 * For example, a ChangeBounds running on the same target view as another ChangeBounds
1500 * should determine whether the old transition is animating to different end values
1501 * and should be canceled in favor of the new transition.
Chet Haase199acdf2013-07-24 18:40:55 -07001502 *
1503 * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
1504 * but subclasses are free to override and return something different.</p>
1505 *
1506 * @return The name of this transition.
1507 */
1508 public String getName() {
1509 return mName;
1510 }
1511
Chet Haasefaebd8f2012-05-18 14:17:57 -07001512 String toString(String indent) {
1513 String result = indent + getClass().getSimpleName() + "@" +
1514 Integer.toHexString(hashCode()) + ": ";
Chet Haasec43524f2013-07-16 14:40:11 -07001515 if (mDuration != -1) {
1516 result += "dur(" + mDuration + ") ";
Chet Haasefaebd8f2012-05-18 14:17:57 -07001517 }
Chet Haasec43524f2013-07-16 14:40:11 -07001518 if (mStartDelay != -1) {
1519 result += "dly(" + mStartDelay + ") ";
Chet Haasefaebd8f2012-05-18 14:17:57 -07001520 }
Chet Haasec43524f2013-07-16 14:40:11 -07001521 if (mInterpolator != null) {
1522 result += "interp(" + mInterpolator + ") ";
1523 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001524 if (mTargetIds.size() > 0 || mTargets.size() > 0) {
Chet Haasec43524f2013-07-16 14:40:11 -07001525 result += "tgts(";
Chet Haased82c8ac2013-08-26 14:20:16 -07001526 if (mTargetIds.size() > 0) {
1527 for (int i = 0; i < mTargetIds.size(); ++i) {
Chet Haasec43524f2013-07-16 14:40:11 -07001528 if (i > 0) {
1529 result += ", ";
1530 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001531 result += mTargetIds.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -07001532 }
1533 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001534 if (mTargets.size() > 0) {
1535 for (int i = 0; i < mTargets.size(); ++i) {
Chet Haasec43524f2013-07-16 14:40:11 -07001536 if (i > 0) {
1537 result += ", ";
1538 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001539 result += mTargets.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -07001540 }
1541 }
1542 result += ")";
1543 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001544 return result;
1545 }
1546
1547 /**
1548 * A transition listener receives notifications from a transition.
Chet Haase199acdf2013-07-24 18:40:55 -07001549 * Notifications indicate transition lifecycle events.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001550 */
1551 public static interface TransitionListener {
1552 /**
1553 * Notification about the start of the transition.
1554 *
1555 * @param transition The started transition.
1556 */
1557 void onTransitionStart(Transition transition);
1558
1559 /**
1560 * Notification about the end of the transition. Canceled transitions
1561 * will always notify listeners of both the cancellation and end
Chet Haase199acdf2013-07-24 18:40:55 -07001562 * events. That is, {@link #onTransitionEnd(Transition)} is always called,
Chet Haasefaebd8f2012-05-18 14:17:57 -07001563 * regardless of whether the transition was canceled or played
1564 * through to completion.
1565 *
1566 * @param transition The transition which reached its end.
1567 */
1568 void onTransitionEnd(Transition transition);
1569
1570 /**
1571 * Notification about the cancellation of the transition.
Chet Haased82c8ac2013-08-26 14:20:16 -07001572 * Note that cancel may be called by a parent {@link TransitionSet} on
Chet Haase199acdf2013-07-24 18:40:55 -07001573 * a child transition which has not yet started. This allows the child
1574 * transition to restore state on target objects which was set at
Chet Haased82c8ac2013-08-26 14:20:16 -07001575 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
1576 * createAnimator()} time.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001577 *
1578 * @param transition The transition which was canceled.
1579 */
1580 void onTransitionCancel(Transition transition);
Chet Haase199acdf2013-07-24 18:40:55 -07001581
1582 /**
1583 * Notification when a transition is paused.
Chet Haased82c8ac2013-08-26 14:20:16 -07001584 * Note that createAnimator() may be called by a parent {@link TransitionSet} on
Chet Haase199acdf2013-07-24 18:40:55 -07001585 * a child transition which has not yet started. This allows the child
1586 * transition to restore state on target objects which was set at
Chet Haased82c8ac2013-08-26 14:20:16 -07001587 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
1588 * createAnimator()} time.
Chet Haase199acdf2013-07-24 18:40:55 -07001589 *
1590 * @param transition The transition which was paused.
1591 */
1592 void onTransitionPause(Transition transition);
1593
1594 /**
1595 * Notification when a transition is resumed.
Chet Haased82c8ac2013-08-26 14:20:16 -07001596 * Note that resume() may be called by a parent {@link TransitionSet} on
Chet Haase199acdf2013-07-24 18:40:55 -07001597 * a child transition which has not yet started. This allows the child
1598 * transition to restore state which may have changed in an earlier call
1599 * to {@link #onTransitionPause(Transition)}.
1600 *
1601 * @param transition The transition which was resumed.
1602 */
1603 void onTransitionResume(Transition transition);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001604 }
1605
1606 /**
1607 * Utility adapter class to avoid having to override all three methods
1608 * whenever someone just wants to listen for a single event.
1609 *
1610 * @hide
1611 * */
1612 public static class TransitionListenerAdapter implements TransitionListener {
1613 @Override
1614 public void onTransitionStart(Transition transition) {
1615 }
1616
1617 @Override
1618 public void onTransitionEnd(Transition transition) {
1619 }
1620
1621 @Override
1622 public void onTransitionCancel(Transition transition) {
1623 }
Chet Haase199acdf2013-07-24 18:40:55 -07001624
1625 @Override
1626 public void onTransitionPause(Transition transition) {
1627 }
1628
1629 @Override
1630 public void onTransitionResume(Transition transition) {
1631 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001632 }
1633
Chet Haase199acdf2013-07-24 18:40:55 -07001634 /**
1635 * Holds information about each animator used when a new transition starts
1636 * while other transitions are still running to determine whether a running
1637 * animation should be canceled or a new animation noop'd. The structure holds
1638 * information about the state that an animation is going to, to be compared to
1639 * end state of a new animation.
George Mount0a778ed2013-12-13 13:35:36 -08001640 * @hide
Chet Haase199acdf2013-07-24 18:40:55 -07001641 */
George Mount0a778ed2013-12-13 13:35:36 -08001642 public static class AnimationInfo {
1643 public View view;
Chet Haase199acdf2013-07-24 18:40:55 -07001644 String name;
1645 TransitionValues values;
1646
1647 AnimationInfo(View view, String name, TransitionValues values) {
1648 this.view = view;
1649 this.name = name;
1650 this.values = values;
1651 }
1652 }
Chet Haaseff58f922013-09-11 13:08:18 -07001653
1654 /**
1655 * Utility class for managing typed ArrayLists efficiently. In particular, this
1656 * can be useful for lists that we don't expect to be used often (eg, the exclude
1657 * lists), so we'd like to keep them nulled out by default. This causes the code to
1658 * become tedious, with constant null checks, code to allocate when necessary,
1659 * and code to null out the reference when the list is empty. This class encapsulates
1660 * all of that functionality into simple add()/remove() methods which perform the
1661 * necessary checks, allocation/null-out as appropriate, and return the
1662 * resulting list.
1663 */
1664 private static class ArrayListManager {
1665
1666 /**
1667 * Add the specified item to the list, returning the resulting list.
1668 * The returned list can either the be same list passed in or, if that
1669 * list was null, the new list that was created.
1670 *
1671 * Note that the list holds unique items; if the item already exists in the
1672 * list, the list is not modified.
1673 */
1674 static <T> ArrayList<T> add(ArrayList<T> list, T item) {
1675 if (list == null) {
1676 list = new ArrayList<T>();
1677 }
1678 if (!list.contains(item)) {
1679 list.add(item);
1680 }
1681 return list;
1682 }
1683
1684 /**
1685 * Remove the specified item from the list, returning the resulting list.
1686 * The returned list can either the be same list passed in or, if that
1687 * list becomes empty as a result of the remove(), the new list was created.
1688 */
1689 static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
1690 if (list != null) {
1691 list.remove(item);
1692 if (list.isEmpty()) {
1693 list = null;
1694 }
1695 }
1696 return list;
1697 }
1698 }
1699
Chet Haasefaebd8f2012-05-18 14:17:57 -07001700}