blob: 8ea9d4873ba01536b3062462133baa47dfb737d7 [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;
32
33import java.util.ArrayList;
Chet Haased82c8ac2013-08-26 14:20:16 -070034import java.util.List;
Chet Haasefaebd8f2012-05-18 14:17:57 -070035
36/**
37 * A Transition holds information about animations that will be run on its
38 * targets during a scene change. Subclasses of this abstract class may
Chet Haased82c8ac2013-08-26 14:20:16 -070039 * choreograph several child transitions ({@link TransitionSet} or they may
Chet Haasefaebd8f2012-05-18 14:17:57 -070040 * perform custom animations themselves. Any Transition has two main jobs:
41 * (1) capture property values, and (2) play animations based on changes to
42 * captured property values. A custom transition knows what property values
43 * on View objects are of interest to it, and also knows how to animate
44 * changes to those values. For example, the {@link Fade} transition tracks
45 * changes to visibility-related properties and is able to construct and run
46 * animations that fade items in or out based on changes to those properties.
47 *
48 * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
49 * or {@link TextureView}, due to the way that these views are displayed
50 * on the screen. For SurfaceView, the problem is that the view is updated from
51 * a non-UI thread, so changes to the view due to transitions (such as moving
52 * and resizing the view) may be out of sync with the display inside those bounds.
53 * TextureView is more compatible with transitions in general, but some
Chet Haased82c8ac2013-08-26 14:20:16 -070054 * specific transitions (such as {@link Fade}) may not be compatible
Chet Haasefaebd8f2012-05-18 14:17:57 -070055 * with TextureView because they rely on {@link ViewOverlay} functionality,
56 * which does not currently work with TextureView.</p>
Chet Haased82c8ac2013-08-26 14:20:16 -070057 *
58 * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
59 * directory. Transition resources consist of a tag name for one of the Transition
60 * subclasses along with attributes to define some of the attributes of that transition.
61 * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p>
62 *
63 * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
64 *
65 * <p>Note that attributes for the transition are not required, just as they are
66 * optional when declared in code; Transitions created from XML resources will use
67 * the same defaults as their code-created equivalents. Here is a slightly more
68 * elaborate example which declares a {@link TransitionSet} transition with
69 * {@link ChangeBounds} and {@link Fade} child transitions:</p>
70 *
71 * {@sample
72 * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet}
73 *
74 * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
75 * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
76 * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
77 * transition uses a fadingMode of {@link Fade#OUT} instead of the default
78 * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
79 * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
80 * of which lists a specific <code>targetId</code> which this transition acts upon.
81 * Use of targets is optional, but can be used to either limit the time spent checking
82 * attributes on unchanging views, or limiting the types of animations run on specific views.
83 * In this case, we know that only the <code>grayscaleContainer</code> will be
84 * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
85 *
86 * Further information on XML resource descriptions for transitions can be found for
87 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
88 * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}.
89 *
Chet Haasefaebd8f2012-05-18 14:17:57 -070090 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070091public abstract class Transition implements Cloneable {
Chet Haasefaebd8f2012-05-18 14:17:57 -070092
93 private static final String LOG_TAG = "Transition";
94 static final boolean DBG = false;
95
Chet Haase199acdf2013-07-24 18:40:55 -070096 private String mName = getClass().getName();
97
Chet Haasefaebd8f2012-05-18 14:17:57 -070098 long mStartDelay = -1;
99 long mDuration = -1;
100 TimeInterpolator mInterpolator = null;
Chet Haased82c8ac2013-08-26 14:20:16 -0700101 ArrayList<Integer> mTargetIds = new ArrayList<Integer>();
102 ArrayList<View> mTargets = new ArrayList<View>();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700103 private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
104 private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
Chet Haased82c8ac2013-08-26 14:20:16 -0700105 TransitionSet mParent = null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700106
Chet Haase199acdf2013-07-24 18:40:55 -0700107 // Per-animator information used for later canceling when future transitions overlap
108 private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
109 new ThreadLocal<ArrayMap<Animator, AnimationInfo>>();
110
Chet Haased82c8ac2013-08-26 14:20:16 -0700111 // Scene Root is set at createAnimator() time in the cloned Transition
Chet Haase6ebe3de2013-06-17 16:50:50 -0700112 ViewGroup mSceneRoot = null;
113
Chet Haasee9d32ea2013-06-04 08:46:42 -0700114 // Track all animators in use in case the transition gets canceled and needs to
115 // cancel running animators
116 private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
117
Chet Haasefaebd8f2012-05-18 14:17:57 -0700118 // Number of per-target instances of this Transition currently running. This count is
Chet Haase199acdf2013-07-24 18:40:55 -0700119 // determined by calls to start() and end()
Chet Haasefaebd8f2012-05-18 14:17:57 -0700120 int mNumInstances = 0;
121
Chet Haase199acdf2013-07-24 18:40:55 -0700122 // Whether this transition is currently paused, due to a call to pause()
123 boolean mPaused = false;
Chet Haasec43524f2013-07-16 14:40:11 -0700124
Chet Haasea56205c2013-09-10 11:30:22 -0700125 // Whether this transition has ended. Used to avoid pause/resume on transitions
126 // that have completed
127 private boolean mEnded = false;
128
Chet Haasec43524f2013-07-16 14:40:11 -0700129 // The set of listeners to be sent transition lifecycle events.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700130 ArrayList<TransitionListener> mListeners = null;
131
Chet Haased82c8ac2013-08-26 14:20:16 -0700132 // The set of animators collected from calls to createAnimator(),
133 // to be run in runAnimators()
Chet Haase199acdf2013-07-24 18:40:55 -0700134 ArrayList<Animator> mAnimators = new ArrayList<Animator>();
Chet Haasec43524f2013-07-16 14:40:11 -0700135
Chet Haasefaebd8f2012-05-18 14:17:57 -0700136 /**
137 * Constructs a Transition object with no target objects. A transition with
138 * no targets defaults to running on all target objects in the scene hierarchy
Chet Haased82c8ac2013-08-26 14:20:16 -0700139 * (if the transition is not contained in a TransitionSet), or all target
140 * objects passed down from its parent (if it is in a TransitionSet).
Chet Haasefaebd8f2012-05-18 14:17:57 -0700141 */
142 public Transition() {}
143
144 /**
145 * Sets the duration of this transition. By default, there is no duration
146 * (indicated by a negative number), which means that the Animator created by
147 * the transition will have its own specified duration. If the duration of a
148 * Transition is set, that duration will override the Animator duration.
149 *
150 * @param duration The length of the animation, in milliseconds.
151 * @return This transition object.
Chet Haased82c8ac2013-08-26 14:20:16 -0700152 * @attr ref android.R.styleable#Transition_duration
Chet Haasefaebd8f2012-05-18 14:17:57 -0700153 */
154 public Transition setDuration(long duration) {
155 mDuration = duration;
156 return this;
157 }
158
Chet Haase199acdf2013-07-24 18:40:55 -0700159 /**
160 * Returns the duration set on this transition. If no duration has been set,
161 * the returned value will be negative, indicating that resulting animators will
162 * retain their own durations.
163 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700164 * @return The duration set on this transition, in milliseconds, if one has been
165 * set, otherwise returns a negative number.
Chet Haase199acdf2013-07-24 18:40:55 -0700166 */
Chet Haasefaebd8f2012-05-18 14:17:57 -0700167 public long getDuration() {
168 return mDuration;
169 }
170
171 /**
172 * Sets the startDelay of this transition. By default, there is no delay
173 * (indicated by a negative number), which means that the Animator created by
174 * the transition will have its own specified startDelay. If the delay of a
175 * Transition is set, that delay will override the Animator delay.
176 *
177 * @param startDelay The length of the delay, in milliseconds.
Chet Haased82c8ac2013-08-26 14:20:16 -0700178 * @return This transition object.
179 * @attr ref android.R.styleable#Transition_startDelay
Chet Haasefaebd8f2012-05-18 14:17:57 -0700180 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700181 public Transition setStartDelay(long startDelay) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700182 mStartDelay = startDelay;
Chet Haased82c8ac2013-08-26 14:20:16 -0700183 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700184 }
185
Chet Haase199acdf2013-07-24 18:40:55 -0700186 /**
187 * Returns the startDelay set on this transition. If no startDelay has been set,
188 * the returned value will be negative, indicating that resulting animators will
189 * retain their own startDelays.
190 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700191 * @return The startDelay set on this transition, in milliseconds, if one has
192 * been set, otherwise returns a negative number.
Chet Haase199acdf2013-07-24 18:40:55 -0700193 */
Chet Haasefaebd8f2012-05-18 14:17:57 -0700194 public long getStartDelay() {
195 return mStartDelay;
196 }
197
198 /**
199 * Sets the interpolator of this transition. By default, the interpolator
200 * is null, which means that the Animator created by the transition
201 * will have its own specified interpolator. If the interpolator of a
202 * Transition is set, that interpolator will override the Animator interpolator.
203 *
204 * @param interpolator The time interpolator used by the transition
Chet Haased82c8ac2013-08-26 14:20:16 -0700205 * @return This transition object.
206 * @attr ref android.R.styleable#Transition_interpolator
Chet Haasefaebd8f2012-05-18 14:17:57 -0700207 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700208 public Transition setInterpolator(TimeInterpolator interpolator) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700209 mInterpolator = interpolator;
Chet Haased82c8ac2013-08-26 14:20:16 -0700210 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700211 }
212
Chet Haase199acdf2013-07-24 18:40:55 -0700213 /**
214 * Returns the interpolator set on this transition. If no interpolator has been set,
215 * the returned value will be null, indicating that resulting animators will
216 * retain their own interpolators.
217 *
218 * @return The interpolator set on this transition, if one has been set, otherwise
219 * returns null.
220 */
Chet Haasefaebd8f2012-05-18 14:17:57 -0700221 public TimeInterpolator getInterpolator() {
222 return mInterpolator;
223 }
224
225 /**
Chet Haase199acdf2013-07-24 18:40:55 -0700226 * Returns the set of property names used stored in the {@link TransitionValues}
Chet Haased82c8ac2013-08-26 14:20:16 -0700227 * object passed into {@link #captureStartValues(TransitionValues)} that
Chet Haase199acdf2013-07-24 18:40:55 -0700228 * this transition cares about for the purposes of canceling overlapping animations.
229 * When any transition is started on a given scene root, all transitions
230 * currently running on that same scene root are checked to see whether the
231 * properties on which they based their animations agree with the end values of
232 * the same properties in the new transition. If the end values are not equal,
233 * then the old animation is canceled since the new transition will start a new
234 * animation to these new values. If the values are equal, the old animation is
235 * allowed to continue and no new animation is started for that transition.
236 *
237 * <p>A transition does not need to override this method. However, not doing so
238 * will mean that the cancellation logic outlined in the previous paragraph
239 * will be skipped for that transition, possibly leading to artifacts as
240 * old transitions and new transitions on the same targets run in parallel,
241 * animating views toward potentially different end values.</p>
242 *
243 * @return An array of property names as described in the class documentation for
244 * {@link TransitionValues}. The default implementation returns <code>null</code>.
245 */
246 public String[] getTransitionProperties() {
247 return null;
248 }
249
250 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700251 * This method creates an animation that will be run for this transition
252 * given the information in the startValues and endValues structures captured
253 * earlier for the start and end scenes. Subclasses of Transition should override
254 * this method. The method should only be called by the transition system; it is
255 * not intended to be called from external classes.
256 *
257 * <p>This method is called by the transition's parent (all the way up to the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700258 * topmost Transition in the hierarchy) with the sceneRoot and start/end
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700259 * values that the transition may need to set up initial target values
260 * and construct an appropriate animation. For example, if an overall
Chet Haased82c8ac2013-08-26 14:20:16 -0700261 * Transition is a {@link TransitionSet} consisting of several
Chet Haasefaebd8f2012-05-18 14:17:57 -0700262 * child transitions in sequence, then some of the child transitions may
263 * want to set initial values on target views prior to the overall
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700264 * Transition commencing, to put them in an appropriate state for the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700265 * delay between that start and the child Transition start time. For
266 * example, a transition that fades an item in may wish to set the starting
267 * alpha value to 0, to avoid it blinking in prior to the transition
268 * actually starting the animation. This is necessary because the scene
269 * change that triggers the Transition will automatically set the end-scene
270 * on all target views, so a Transition that wants to animate from a
Chet Haased82c8ac2013-08-26 14:20:16 -0700271 * different value should set that value prior to returning from this method.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700272 *
273 * <p>Additionally, a Transition can perform logic to determine whether
274 * the transition needs to run on the given target and start/end values.
275 * For example, a transition that resizes objects on the screen may wish
276 * to avoid running for views which are not present in either the start
Chet Haased82c8ac2013-08-26 14:20:16 -0700277 * or end scenes.</p>
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700278 *
279 * <p>If there is an animator created and returned from this method, the
280 * transition mechanism will apply any applicable duration, startDelay,
281 * and interpolator to that animation and start it. A return value of
282 * <code>null</code> indicates that no animation should run. The default
283 * implementation returns null.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700284 *
285 * <p>The method is called for every applicable target object, which is
286 * stored in the {@link TransitionValues#view} field.</p>
287 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700288 *
289 * @param sceneRoot The root of the transition hierarchy.
290 * @param startValues The values for a specific target in the start scene.
291 * @param endValues The values for the target in the end scene.
292 * @return A Animator to be started at the appropriate time in the
293 * overall transition for this scene change. A null value means no animation
294 * should be run.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700295 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700296 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700297 TransitionValues endValues) {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700298 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700299 }
300
301 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700302 * This method, essentially a wrapper around all calls to createAnimator for all
303 * possible target views, is called with the entire set of start/end
Chet Haasefaebd8f2012-05-18 14:17:57 -0700304 * values. The implementation in Transition iterates through these lists
Chet Haased82c8ac2013-08-26 14:20:16 -0700305 * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
Chet Haasefaebd8f2012-05-18 14:17:57 -0700306 * with each set of start/end values on this transition. The
Chet Haased82c8ac2013-08-26 14:20:16 -0700307 * TransitionSet subclass overrides this method and delegates it to
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700308 * each of its children in succession.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700309 *
310 * @hide
311 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700312 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
Chet Haase6ebe3de2013-06-17 16:50:50 -0700313 TransitionValuesMaps endValues) {
Chet Haasec43524f2013-07-16 14:40:11 -0700314 if (DBG) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700315 Log.d(LOG_TAG, "createAnimators() for " + this);
Chet Haasec43524f2013-07-16 14:40:11 -0700316 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700317 ArrayMap<View, TransitionValues> endCopy =
318 new ArrayMap<View, TransitionValues>(endValues.viewValues);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700319 SparseArray<TransitionValues> endIdCopy =
Chet Haase6ebe3de2013-06-17 16:50:50 -0700320 new SparseArray<TransitionValues>(endValues.idValues.size());
321 for (int i = 0; i < endValues.idValues.size(); ++i) {
322 int id = endValues.idValues.keyAt(i);
323 endIdCopy.put(id, endValues.idValues.valueAt(i));
Chet Haasefaebd8f2012-05-18 14:17:57 -0700324 }
325 LongSparseArray<TransitionValues> endItemIdCopy =
Chet Haase6ebe3de2013-06-17 16:50:50 -0700326 new LongSparseArray<TransitionValues>(endValues.itemIdValues.size());
327 for (int i = 0; i < endValues.itemIdValues.size(); ++i) {
328 long id = endValues.itemIdValues.keyAt(i);
329 endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i));
Chet Haasefaebd8f2012-05-18 14:17:57 -0700330 }
331 // Walk through the start values, playing everything we find
332 // Remove from the end set as we go
333 ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
334 ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700335 for (View view : startValues.viewValues.keySet()) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700336 TransitionValues start = null;
337 TransitionValues end = null;
338 boolean isInListView = false;
339 if (view.getParent() instanceof ListView) {
340 isInListView = true;
341 }
342 if (!isInListView) {
343 int id = view.getId();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700344 start = startValues.viewValues.get(view) != null ?
345 startValues.viewValues.get(view) : startValues.idValues.get(id);
346 if (endValues.viewValues.get(view) != null) {
347 end = endValues.viewValues.get(view);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700348 endCopy.remove(view);
349 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700350 end = endValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700351 View removeView = null;
352 for (View viewToRemove : endCopy.keySet()) {
353 if (viewToRemove.getId() == id) {
354 removeView = viewToRemove;
355 }
356 }
357 if (removeView != null) {
358 endCopy.remove(removeView);
359 }
360 }
361 endIdCopy.remove(id);
362 if (isValidTarget(view, id)) {
363 startValuesList.add(start);
364 endValuesList.add(end);
365 }
366 } else {
367 ListView parent = (ListView) view.getParent();
368 if (parent.getAdapter().hasStableIds()) {
369 int position = parent.getPositionForView(view);
370 long itemId = parent.getItemIdAtPosition(position);
Chet Haase6ebe3de2013-06-17 16:50:50 -0700371 start = startValues.itemIdValues.get(itemId);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700372 endItemIdCopy.remove(itemId);
373 // TODO: deal with targetIDs for itemIDs for ListView items
374 startValuesList.add(start);
375 endValuesList.add(end);
376 }
377 }
378 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700379 int startItemIdCopySize = startValues.itemIdValues.size();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700380 for (int i = 0; i < startItemIdCopySize; ++i) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700381 long id = startValues.itemIdValues.keyAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700382 if (isValidTarget(null, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700383 TransitionValues start = startValues.itemIdValues.get(id);
384 TransitionValues end = endValues.itemIdValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700385 endItemIdCopy.remove(id);
386 startValuesList.add(start);
387 endValuesList.add(end);
388 }
389 }
390 // Now walk through the remains of the end set
391 for (View view : endCopy.keySet()) {
392 int id = view.getId();
393 if (isValidTarget(view, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700394 TransitionValues start = startValues.viewValues.get(view) != null ?
395 startValues.viewValues.get(view) : startValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700396 TransitionValues end = endCopy.get(view);
397 endIdCopy.remove(id);
398 startValuesList.add(start);
399 endValuesList.add(end);
400 }
401 }
402 int endIdCopySize = endIdCopy.size();
403 for (int i = 0; i < endIdCopySize; ++i) {
404 int id = endIdCopy.keyAt(i);
405 if (isValidTarget(null, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700406 TransitionValues start = startValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700407 TransitionValues end = endIdCopy.get(id);
408 startValuesList.add(start);
409 endValuesList.add(end);
410 }
411 }
412 int endItemIdCopySize = endItemIdCopy.size();
413 for (int i = 0; i < endItemIdCopySize; ++i) {
414 long id = endItemIdCopy.keyAt(i);
415 // TODO: Deal with targetIDs and itemIDs
Chet Haase6ebe3de2013-06-17 16:50:50 -0700416 TransitionValues start = startValues.itemIdValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700417 TransitionValues end = endItemIdCopy.get(id);
418 startValuesList.add(start);
419 endValuesList.add(end);
420 }
Chet Haase199acdf2013-07-24 18:40:55 -0700421 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700422 for (int i = 0; i < startValuesList.size(); ++i) {
423 TransitionValues start = startValuesList.get(i);
424 TransitionValues end = endValuesList.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -0700425 // Only bother trying to animate with values that differ between start/end
426 if (start != null || end != null) {
427 if (start == null || !start.equals(end)) {
428 if (DBG) {
429 View view = (end != null) ? end.view : start.view;
430 Log.d(LOG_TAG, " differing start/end values for view " +
431 view);
432 if (start == null || end == null) {
433 if (start == null) {
434 Log.d(LOG_TAG, " " + ((start == null) ?
435 "start null, end non-null" : "start non-null, end null"));
436 }
437 } else {
438 for (String key : start.values.keySet()) {
439 Object startValue = start.values.get(key);
440 Object endValue = end.values.get(key);
441 if (startValue != endValue && !startValue.equals(endValue)) {
442 Log.d(LOG_TAG, " " + key + ": start(" + startValue +
443 "), end(" + endValue +")");
444 }
445 }
446 }
447 }
448 // TODO: what to do about targetIds and itemIds?
Chet Haased82c8ac2013-08-26 14:20:16 -0700449 Animator animator = createAnimator(sceneRoot, start, end);
Chet Haasec43524f2013-07-16 14:40:11 -0700450 if (animator != null) {
Chet Haase199acdf2013-07-24 18:40:55 -0700451 // Save animation info for future cancellation purposes
452 View view = null;
453 TransitionValues infoValues = null;
454 if (end != null) {
455 view = end.view;
456 String[] properties = getTransitionProperties();
457 if (view != null && properties != null && properties.length > 0) {
458 infoValues = new TransitionValues();
459 infoValues.view = view;
460 TransitionValues newValues = endValues.viewValues.get(view);
461 if (newValues != null) {
462 for (int j = 0; j < properties.length; ++j) {
463 infoValues.values.put(properties[j],
464 newValues.values.get(properties[j]));
465 }
466 }
467 int numExistingAnims = runningAnimators.size();
468 for (int j = 0; j < numExistingAnims; ++j) {
469 Animator anim = runningAnimators.keyAt(j);
470 AnimationInfo info = runningAnimators.get(anim);
471 if (info.values != null && info.view == view &&
472 ((info.name == null && getName() == null) ||
473 info.name.equals(getName()))) {
474 if (info.values.equals(infoValues)) {
475 // Favor the old animator
476 animator = null;
477 break;
478 }
479 }
480 }
481 }
482 } else {
483 view = (start != null) ? start.view : null;
484 }
485 if (animator != null) {
486 AnimationInfo info = new AnimationInfo(view, getName(), infoValues);
487 runningAnimators.put(animator, info);
488 mAnimators.add(animator);
489 }
Chet Haasec43524f2013-07-16 14:40:11 -0700490 }
Chet Haasec43524f2013-07-16 14:40:11 -0700491 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700492 }
493 }
494 }
495
496 /**
497 * Internal utility method for checking whether a given view/id
498 * is valid for this transition, where "valid" means that either
499 * the Transition has no target/targetId list (the default, in which
500 * cause the transition should act on all views in the hiearchy), or
501 * the given view is in the target list or the view id is in the
502 * targetId list. If the target parameter is null, then the target list
503 * is not checked (this is in the case of ListView items, where the
504 * views are ignored and only the ids are used).
505 */
506 boolean isValidTarget(View target, long targetId) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700507 if (mTargetIds.size() == 0 && mTargets.size() == 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700508 return true;
509 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700510 if (mTargetIds.size() > 0) {
511 for (int i = 0; i < mTargetIds.size(); ++i) {
512 if (mTargetIds.get(i) == targetId) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700513 return true;
514 }
515 }
516 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700517 if (target != null && mTargets.size() > 0) {
518 for (int i = 0; i < mTargets.size(); ++i) {
519 if (mTargets.get(i) == target) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700520 return true;
521 }
522 }
523 }
524 return false;
525 }
526
Chet Haase199acdf2013-07-24 18:40:55 -0700527 private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
528 ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
529 if (runningAnimators == null) {
530 runningAnimators = new ArrayMap<Animator, AnimationInfo>();
531 sRunningAnimators.set(runningAnimators);
532 }
533 return runningAnimators;
534 }
535
Chet Haasefaebd8f2012-05-18 14:17:57 -0700536 /**
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700537 * This is called internally once all animations have been set up by the
538 * transition hierarchy. \
Chet Haasefaebd8f2012-05-18 14:17:57 -0700539 *
540 * @hide
541 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700542 protected void runAnimators() {
Chet Haase199acdf2013-07-24 18:40:55 -0700543 if (DBG) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700544 Log.d(LOG_TAG, "runAnimators() on " + this);
Chet Haasec43524f2013-07-16 14:40:11 -0700545 }
Chet Haase199acdf2013-07-24 18:40:55 -0700546 start();
547 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
Chet Haased82c8ac2013-08-26 14:20:16 -0700548 // Now start every Animator that was previously created for this transition
Chet Haase199acdf2013-07-24 18:40:55 -0700549 for (Animator anim : mAnimators) {
Chet Haasec43524f2013-07-16 14:40:11 -0700550 if (DBG) {
551 Log.d(LOG_TAG, " anim: " + anim);
552 }
Chet Haase199acdf2013-07-24 18:40:55 -0700553 if (runningAnimators.containsKey(anim)) {
554 start();
555 runAnimator(anim, runningAnimators);
556 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700557 }
Chet Haase199acdf2013-07-24 18:40:55 -0700558 mAnimators.clear();
559 end();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700560 }
561
Chet Haase199acdf2013-07-24 18:40:55 -0700562 private void runAnimator(Animator animator,
563 final ArrayMap<Animator, AnimationInfo> runningAnimators) {
Chet Haasee9d32ea2013-06-04 08:46:42 -0700564 if (animator != null) {
565 // TODO: could be a single listener instance for all of them since it uses the param
566 animator.addListener(new AnimatorListenerAdapter() {
567 @Override
568 public void onAnimationStart(Animator animation) {
569 mCurrentAnimators.add(animation);
570 }
571 @Override
572 public void onAnimationEnd(Animator animation) {
Chet Haase199acdf2013-07-24 18:40:55 -0700573 runningAnimators.remove(animation);
Chet Haasee9d32ea2013-06-04 08:46:42 -0700574 mCurrentAnimators.remove(animation);
575 }
576 });
577 animate(animator);
578 }
579 }
580
Chet Haasefaebd8f2012-05-18 14:17:57 -0700581 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700582 * Captures the values in the start scene for the properties that this
583 * transition monitors. These values are then passed as the startValues
584 * structure in a later call to
585 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
586 * The main concern for an implementation is what the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700587 * properties are that the transition cares about and what the values are
588 * for all of those properties. The start and end values will be compared
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700589 * later during the
Chet Haased82c8ac2013-08-26 14:20:16 -0700590 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700591 * method to determine what, if any, animations, should be run.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700592 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700593 * <p>Subclasses must implement this method. The method should only be called by the
594 * transition system; it is not intended to be called from external classes.</p>
595 *
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700596 * @param transitionValues The holder for any values that the Transition
597 * wishes to store. Values are stored in the <code>values</code> field
598 * of this TransitionValues object and are keyed from
599 * a String value. For example, to store a view's rotation value,
600 * a transition might call
601 * <code>transitionValues.values.put("appname:transitionname:rotation",
602 * view.getRotation())</code>. The target view will already be stored in
603 * the transitionValues structure when this method is called.
Chet Haased82c8ac2013-08-26 14:20:16 -0700604 *
605 * @see #captureEndValues(TransitionValues)
606 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
Chet Haasefaebd8f2012-05-18 14:17:57 -0700607 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700608 public abstract void captureStartValues(TransitionValues transitionValues);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700609
610 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700611 * Captures the values in the end scene for the properties that this
612 * transition monitors. These values are then passed as the endValues
613 * structure in a later call to
614 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
615 * The main concern for an implementation is what the
616 * 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
618 * later during the
619 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
620 * method to determine what, if any, animations, should be run.
621 *
622 * <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 *
625 * @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.
633 *
634 * @see #captureStartValues(TransitionValues)
635 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
636 */
637 public abstract void captureEndValues(TransitionValues transitionValues);
638
639 /**
640 * Adds the id of a target view that this Transition is interested in
Chet Haasefaebd8f2012-05-18 14:17:57 -0700641 * animating. By default, there are no targetIds, and a Transition will
642 * listen for changes on every view in the hierarchy below the sceneRoot
Chet Haased82c8ac2013-08-26 14:20:16 -0700643 * of the Scene being transitioned into. Setting targetIds constrains
Chet Haasefaebd8f2012-05-18 14:17:57 -0700644 * the Transition to only listen for, and act on, views with these IDs.
645 * Views with different IDs, or no IDs whatsoever, will be ignored.
646 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700647 * <p>Note that using ids to specify targets implies that ids should be unique
648 * within the view hierarchy underneat the scene root.</p>
649 *
Chet Haasefaebd8f2012-05-18 14:17:57 -0700650 * @see View#getId()
Chet Haased82c8ac2013-08-26 14:20:16 -0700651 * @param targetId The id of a target view, must be a positive number.
652 * @return The Transition to which the targetId is added.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700653 * Returning the same object makes it easier to chain calls during
654 * construction, such as
Chet Haased82c8ac2013-08-26 14:20:16 -0700655 * <code>transitionSet.addTransitions(new Fade()).addTargetId(someId);</code>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700656 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700657 public Transition addTargetId(int targetId) {
658 if (targetId > 0) {
659 mTargetIds.add(targetId);
660 }
661 return this;
662 }
663
664 /**
665 * Removes the given targetId from the list of ids that this Transition
666 * is interested in animating.
667 *
668 * @param targetId The id of a target view, must be a positive number.
669 * @return The Transition from which the targetId is removed.
670 * Returning the same object makes it easier to chain calls during
671 * construction, such as
672 * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
673 */
674 public Transition removeTargetId(int targetId) {
675 if (targetId > 0) {
676 mTargetIds.remove(targetId);
677 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700678 return this;
679 }
680
681 /**
682 * Sets the target view instances that this Transition is interested in
683 * animating. By default, there are no targets, and a Transition will
684 * listen for changes on every view in the hierarchy below the sceneRoot
685 * of the Scene being transitioned into. Setting targets constrains
686 * the Transition to only listen for, and act on, these views.
687 * All other views will be ignored.
688 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700689 * <p>The target list is like the {@link #addTargetId(int) targetId}
Chet Haasefaebd8f2012-05-18 14:17:57 -0700690 * list except this list specifies the actual View instances, not the ids
691 * of the views. This is an important distinction when scene changes involve
692 * view hierarchies which have been inflated separately; different views may
693 * share the same id but not actually be the same instance. If the transition
Chet Haased82c8ac2013-08-26 14:20:16 -0700694 * should treat those views as the same, then {@link #addTargetId(int)} should be used
695 * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
Chet Haasefaebd8f2012-05-18 14:17:57 -0700696 * changes all within the same view hierarchy, among views which do not
Chet Haased82c8ac2013-08-26 14:20:16 -0700697 * necessarily have ids set on them, then the target list of views may be more
Chet Haasefaebd8f2012-05-18 14:17:57 -0700698 * convenient.</p>
699 *
Chet Haased82c8ac2013-08-26 14:20:16 -0700700 * @see #addTargetId(int)
701 * @param target A View on which the Transition will act, must be non-null.
702 * @return The Transition to which the target is added.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700703 * Returning the same object makes it easier to chain calls during
704 * construction, such as
Chet Haased82c8ac2013-08-26 14:20:16 -0700705 * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700706 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700707 public Transition addTarget(View target) {
708 mTargets.add(target);
709 return this;
710 }
711
712 /**
713 * Removes the given target from the list of targets that this Transition
714 * is interested in animating.
715 *
716 * @param target The target view, must be non-null.
717 * @return Transition The Transition from which the target is removed.
718 * Returning the same object makes it easier to chain calls during
719 * construction, such as
720 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
721 */
722 public Transition removeTarget(View target) {
723 if (target != null) {
724 mTargets.remove(target);
725 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700726 return this;
727 }
728
729 /**
730 * Returns the array of target IDs that this transition limits itself to
731 * tracking and animating. If the array is null for both this method and
732 * {@link #getTargets()}, then this transition is
733 * not limited to specific views, and will handle changes to any views
734 * in the hierarchy of a scene change.
735 *
736 * @return the list of target IDs
737 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700738 public List<Integer> getTargetIds() {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700739 return mTargetIds;
740 }
741
742 /**
743 * Returns the array of target views that this transition limits itself to
744 * tracking and animating. If the array is null for both this method and
745 * {@link #getTargetIds()}, then this transition is
746 * not limited to specific views, and will handle changes to any views
747 * in the hierarchy of a scene change.
748 *
749 * @return the list of target views
750 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700751 public List<View> getTargets() {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700752 return mTargets;
753 }
754
755 /**
756 * Recursive method that captures values for the given view and the
757 * hierarchy underneath it.
758 * @param sceneRoot The root of the view hierarchy being captured
759 * @param start true if this capture is happening before the scene change,
760 * false otherwise
761 */
762 void captureValues(ViewGroup sceneRoot, boolean start) {
Chet Haasec81a8492013-07-12 12:54:38 -0700763 if (start) {
764 mStartValues.viewValues.clear();
765 mStartValues.idValues.clear();
766 mStartValues.itemIdValues.clear();
767 } else {
768 mEndValues.viewValues.clear();
769 mEndValues.idValues.clear();
770 mEndValues.itemIdValues.clear();
771 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700772 if (mTargetIds.size() > 0 || mTargets.size() > 0) {
773 if (mTargetIds.size() > 0) {
774 for (int i = 0; i < mTargetIds.size(); ++i) {
775 int id = mTargetIds.get(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700776 View view = sceneRoot.findViewById(id);
777 if (view != null) {
778 TransitionValues values = new TransitionValues();
779 values.view = view;
Chet Haased82c8ac2013-08-26 14:20:16 -0700780 if (start) {
781 captureStartValues(values);
782 } else {
783 captureEndValues(values);
784 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700785 if (start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700786 mStartValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700787 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700788 mStartValues.idValues.put(id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700789 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700790 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700791 mEndValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700792 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700793 mEndValues.idValues.put(id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700794 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700795 }
796 }
797 }
798 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700799 if (mTargets.size() > 0) {
800 for (int i = 0; i < mTargets.size(); ++i) {
801 View view = mTargets.get(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700802 if (view != null) {
803 TransitionValues values = new TransitionValues();
804 values.view = view;
Chet Haased82c8ac2013-08-26 14:20:16 -0700805 if (start) {
806 captureStartValues(values);
807 } else {
808 captureEndValues(values);
809 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700810 if (start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700811 mStartValues.viewValues.put(view, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700812 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700813 mEndValues.viewValues.put(view, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700814 }
815 }
816 }
817 }
818 } else {
819 captureHierarchy(sceneRoot, start);
820 }
821 }
822
823 /**
824 * Recursive method which captures values for an entire view hierarchy,
825 * starting at some root view. Transitions without targetIDs will use this
826 * method to capture values for all possible views.
827 *
828 * @param view The view for which to capture values. Children of this View
829 * will also be captured, recursively down to the leaf nodes.
830 * @param start true if values are being captured in the start scene, false
831 * otherwise.
832 */
833 private void captureHierarchy(View view, boolean start) {
834 if (view == null) {
835 return;
836 }
837 boolean isListViewItem = false;
838 if (view.getParent() instanceof ListView) {
839 isListViewItem = true;
840 }
841 if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
842 // ignore listview children unless we can track them with stable IDs
843 return;
844 }
845 long id;
846 if (!isListViewItem) {
847 id = view.getId();
848 } else {
849 ListView listview = (ListView) view.getParent();
850 int position = listview.getPositionForView(view);
851 id = listview.getItemIdAtPosition(position);
852 view.setHasTransientState(true);
853 }
854 TransitionValues values = new TransitionValues();
855 values.view = view;
Chet Haased82c8ac2013-08-26 14:20:16 -0700856 captureStartValues(values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700857 if (start) {
858 if (!isListViewItem) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700859 mStartValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700860 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700861 mStartValues.idValues.put((int) id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700862 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700863 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700864 mStartValues.itemIdValues.put(id, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700865 }
866 } else {
867 if (!isListViewItem) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700868 mEndValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700869 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700870 mEndValues.idValues.put((int) id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700871 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700872 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700873 mEndValues.itemIdValues.put(id, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700874 }
875 }
876 if (view instanceof ViewGroup) {
877 ViewGroup parent = (ViewGroup) view;
878 for (int i = 0; i < parent.getChildCount(); ++i) {
879 captureHierarchy(parent.getChildAt(i), start);
880 }
881 }
882 }
883
884 /**
Chet Haase6ebe3de2013-06-17 16:50:50 -0700885 * This method can be called by transitions to get the TransitionValues for
886 * any particular view during the transition-playing process. This might be
887 * necessary, for example, to query the before/after state of related views
888 * for a given transition.
889 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700890 public TransitionValues getTransitionValues(View view, boolean start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700891 if (mParent != null) {
892 return mParent.getTransitionValues(view, start);
893 }
894 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
895 TransitionValues values = valuesMaps.viewValues.get(view);
896 if (values == null) {
897 int id = view.getId();
898 if (id >= 0) {
899 values = valuesMaps.idValues.get(id);
900 }
901 if (values == null && view.getParent() instanceof ListView) {
902 ListView listview = (ListView) view.getParent();
903 int position = listview.getPositionForView(view);
904 long itemId = listview.getItemIdAtPosition(position);
905 values = valuesMaps.itemIdValues.get(itemId);
906 }
907 // TODO: Doesn't handle the case where a view was parented to a
908 // ListView (with an itemId), but no longer is
909 }
910 return values;
911 }
912
913 /**
Chet Haase199acdf2013-07-24 18:40:55 -0700914 * Pauses this transition, sending out calls to {@link
915 * TransitionListener#onTransitionPause(Transition)} to all listeners
916 * and pausing all running animators started by this transition.
917 *
918 * @hide
919 */
920 public void pause() {
Chet Haasea56205c2013-09-10 11:30:22 -0700921 if (!mEnded) {
922 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
923 int numOldAnims = runningAnimators.size();
924 for (int i = numOldAnims - 1; i >= 0; i--) {
925 Animator anim = runningAnimators.keyAt(i);
926 anim.pause();
Chet Haase199acdf2013-07-24 18:40:55 -0700927 }
Chet Haasea56205c2013-09-10 11:30:22 -0700928 if (mListeners != null && mListeners.size() > 0) {
929 ArrayList<TransitionListener> tmpListeners =
930 (ArrayList<TransitionListener>) mListeners.clone();
931 int numListeners = tmpListeners.size();
932 for (int i = 0; i < numListeners; ++i) {
933 tmpListeners.get(i).onTransitionPause(this);
934 }
935 }
936 mPaused = true;
Chet Haase199acdf2013-07-24 18:40:55 -0700937 }
Chet Haase199acdf2013-07-24 18:40:55 -0700938 }
939
940 /**
941 * Resumes this transition, sending out calls to {@link
942 * TransitionListener#onTransitionPause(Transition)} to all listeners
943 * and pausing all running animators started by this transition.
944 *
945 * @hide
946 */
947 public void resume() {
948 if (mPaused) {
Chet Haasea56205c2013-09-10 11:30:22 -0700949 if (!mEnded) {
950 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
951 int numOldAnims = runningAnimators.size();
952 for (int i = numOldAnims - 1; i >= 0; i--) {
953 Animator anim = runningAnimators.keyAt(i);
954 anim.resume();
955 }
956 if (mListeners != null && mListeners.size() > 0) {
957 ArrayList<TransitionListener> tmpListeners =
958 (ArrayList<TransitionListener>) mListeners.clone();
959 int numListeners = tmpListeners.size();
960 for (int i = 0; i < numListeners; ++i) {
961 tmpListeners.get(i).onTransitionResume(this);
962 }
Chet Haase199acdf2013-07-24 18:40:55 -0700963 }
964 }
965 mPaused = false;
966 }
967 }
968
969 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -0700970 * Called by TransitionManager to play the transition. This calls
Chet Haased82c8ac2013-08-26 14:20:16 -0700971 * createAnimators() to set things up and create all of the animations and then
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700972 * runAnimations() to actually start the animations.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700973 */
Chet Haase6ebe3de2013-06-17 16:50:50 -0700974 void playTransition(ViewGroup sceneRoot) {
Chet Haase199acdf2013-07-24 18:40:55 -0700975 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
976 int numOldAnims = runningAnimators.size();
977 for (int i = numOldAnims - 1; i >= 0; i--) {
978 Animator anim = runningAnimators.keyAt(i);
979 if (anim != null) {
Chet Haase199acdf2013-07-24 18:40:55 -0700980 AnimationInfo oldInfo = runningAnimators.get(anim);
981 if (oldInfo != null) {
982 boolean cancel = false;
983 TransitionValues oldValues = oldInfo.values;
984 View oldView = oldInfo.view;
985 TransitionValues newValues = mEndValues.viewValues != null ?
986 mEndValues.viewValues.get(oldView) : null;
Chet Haaseaf78bdd2013-08-27 16:06:26 -0700987 if (oldValues != null) {
988 // if oldValues null, then transition didn't care to stash values,
989 // and won't get canceled
990 if (newValues == null) {
Chet Haase199acdf2013-07-24 18:40:55 -0700991 cancel = true;
Chet Haaseaf78bdd2013-08-27 16:06:26 -0700992 } else {
993 for (String key : oldValues.values.keySet()) {
994 Object oldValue = oldValues.values.get(key);
995 Object newValue = newValues.values.get(key);
996 if (oldValue != null && newValue != null &&
997 !oldValue.equals(newValue)) {
998 cancel = true;
999 if (DBG) {
1000 Log.d(LOG_TAG, "Transition.playTransition: " +
1001 "oldValue != newValue for " + key +
1002 ": old, new = " + oldValue + ", " + newValue);
1003 }
1004 break;
Chet Haase199acdf2013-07-24 18:40:55 -07001005 }
Chet Haase199acdf2013-07-24 18:40:55 -07001006 }
1007 }
1008 }
1009 if (cancel) {
1010 if (anim.isRunning() || anim.isStarted()) {
1011 if (DBG) {
1012 Log.d(LOG_TAG, "Canceling anim " + anim);
1013 }
1014 anim.cancel();
1015 } else {
1016 if (DBG) {
1017 Log.d(LOG_TAG, "removing anim from info list: " + anim);
1018 }
1019 runningAnimators.remove(anim);
1020 }
1021 }
1022 }
1023 }
1024 }
1025
Chet Haased82c8ac2013-08-26 14:20:16 -07001026 createAnimators(sceneRoot, mStartValues, mEndValues);
1027 runAnimators();
Chet Haasefaebd8f2012-05-18 14:17:57 -07001028 }
1029
1030 /**
1031 * This is a utility method used by subclasses to handle standard parts of
1032 * setting up and running an Animator: it sets the {@link #getDuration()
1033 * duration} and the {@link #getStartDelay() startDelay}, starts the
Chet Haase199acdf2013-07-24 18:40:55 -07001034 * animation, and, when the animator ends, calls {@link #end()}.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001035 *
1036 * @param animator The Animator to be run during this transition.
1037 *
1038 * @hide
1039 */
1040 protected void animate(Animator animator) {
1041 // TODO: maybe pass auto-end as a boolean parameter?
1042 if (animator == null) {
Chet Haase199acdf2013-07-24 18:40:55 -07001043 end();
Chet Haasefaebd8f2012-05-18 14:17:57 -07001044 } else {
1045 if (getDuration() >= 0) {
1046 animator.setDuration(getDuration());
1047 }
1048 if (getStartDelay() >= 0) {
1049 animator.setStartDelay(getStartDelay());
1050 }
1051 if (getInterpolator() != null) {
1052 animator.setInterpolator(getInterpolator());
1053 }
1054 animator.addListener(new AnimatorListenerAdapter() {
1055 @Override
Chet Haasefaebd8f2012-05-18 14:17:57 -07001056 public void onAnimationEnd(Animator animation) {
Chet Haase199acdf2013-07-24 18:40:55 -07001057 end();
Chet Haasefaebd8f2012-05-18 14:17:57 -07001058 animation.removeListener(this);
1059 }
1060 });
1061 animator.start();
1062 }
1063 }
1064
1065 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -07001066 * This method is called automatically by the transition and
Chet Haased82c8ac2013-08-26 14:20:16 -07001067 * TransitionSet classes prior to a Transition subclass starting;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001068 * subclasses should not need to call it directly.
1069 *
1070 * @hide
1071 */
Chet Haase199acdf2013-07-24 18:40:55 -07001072 protected void start() {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001073 if (mNumInstances == 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001074 if (mListeners != null && mListeners.size() > 0) {
1075 ArrayList<TransitionListener> tmpListeners =
1076 (ArrayList<TransitionListener>) mListeners.clone();
1077 int numListeners = tmpListeners.size();
1078 for (int i = 0; i < numListeners; ++i) {
1079 tmpListeners.get(i).onTransitionStart(this);
1080 }
1081 }
Chet Haasea56205c2013-09-10 11:30:22 -07001082 mEnded = false;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001083 }
1084 mNumInstances++;
1085 }
1086
1087 /**
1088 * This method is called automatically by the Transition and
Chet Haased82c8ac2013-08-26 14:20:16 -07001089 * TransitionSet classes when a transition finishes, either because
Chet Haasefaebd8f2012-05-18 14:17:57 -07001090 * a transition did nothing (returned a null Animator from
Chet Haased82c8ac2013-08-26 14:20:16 -07001091 * {@link Transition#createAnimator(ViewGroup, TransitionValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -07001092 * TransitionValues)}) or because the transition returned a valid
Chet Haase199acdf2013-07-24 18:40:55 -07001093 * Animator and end() was called in the onAnimationEnd()
Chet Haasefaebd8f2012-05-18 14:17:57 -07001094 * callback of the AnimatorListener.
1095 *
1096 * @hide
1097 */
Chet Haase199acdf2013-07-24 18:40:55 -07001098 protected void end() {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001099 --mNumInstances;
1100 if (mNumInstances == 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001101 if (mListeners != null && mListeners.size() > 0) {
1102 ArrayList<TransitionListener> tmpListeners =
1103 (ArrayList<TransitionListener>) mListeners.clone();
1104 int numListeners = tmpListeners.size();
1105 for (int i = 0; i < numListeners; ++i) {
1106 tmpListeners.get(i).onTransitionEnd(this);
1107 }
1108 }
Chet Haase6ebe3de2013-06-17 16:50:50 -07001109 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
1110 TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001111 View v = tv.view;
1112 if (v.hasTransientState()) {
1113 v.setHasTransientState(false);
1114 }
1115 }
Chet Haase6ebe3de2013-06-17 16:50:50 -07001116 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
1117 TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001118 View v = tv.view;
1119 if (v.hasTransientState()) {
1120 v.setHasTransientState(false);
1121 }
1122 }
Chet Haasea56205c2013-09-10 11:30:22 -07001123 mEnded = true;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001124 }
1125 }
1126
1127 /**
1128 * This method cancels a transition that is currently running.
Chet Haased82c8ac2013-08-26 14:20:16 -07001129 *
1130 * @hide
Chet Haasefaebd8f2012-05-18 14:17:57 -07001131 */
Chet Haase199acdf2013-07-24 18:40:55 -07001132 protected void cancel() {
Chet Haasee9d32ea2013-06-04 08:46:42 -07001133 int numAnimators = mCurrentAnimators.size();
Chet Haase25a738f2013-06-04 16:35:14 -07001134 for (int i = numAnimators - 1; i >= 0; i--) {
Chet Haasee9d32ea2013-06-04 08:46:42 -07001135 Animator animator = mCurrentAnimators.get(i);
1136 animator.cancel();
1137 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001138 if (mListeners != null && mListeners.size() > 0) {
1139 ArrayList<TransitionListener> tmpListeners =
1140 (ArrayList<TransitionListener>) mListeners.clone();
1141 int numListeners = tmpListeners.size();
1142 for (int i = 0; i < numListeners; ++i) {
1143 tmpListeners.get(i).onTransitionCancel(this);
1144 }
1145 }
1146 }
1147
1148 /**
1149 * Adds a listener to the set of listeners that are sent events through the
1150 * life of an animation, such as start, repeat, and end.
1151 *
1152 * @param listener the listener to be added to the current set of listeners
1153 * for this animation.
Chet Haased82c8ac2013-08-26 14:20:16 -07001154 * @return This transition object.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001155 */
Chet Haased82c8ac2013-08-26 14:20:16 -07001156 public Transition addListener(TransitionListener listener) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001157 if (mListeners == null) {
1158 mListeners = new ArrayList<TransitionListener>();
1159 }
1160 mListeners.add(listener);
Chet Haased82c8ac2013-08-26 14:20:16 -07001161 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001162 }
1163
1164 /**
1165 * Removes a listener from the set listening to this animation.
1166 *
1167 * @param listener the listener to be removed from the current set of
1168 * listeners for this transition.
Chet Haased82c8ac2013-08-26 14:20:16 -07001169 * @return This transition object.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001170 */
Chet Haased82c8ac2013-08-26 14:20:16 -07001171 public Transition removeListener(TransitionListener listener) {
Chet Haasefaebd8f2012-05-18 14:17:57 -07001172 if (mListeners == null) {
Chet Haased82c8ac2013-08-26 14:20:16 -07001173 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001174 }
1175 mListeners.remove(listener);
1176 if (mListeners.size() == 0) {
1177 mListeners = null;
1178 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001179 return this;
Chet Haasefaebd8f2012-05-18 14:17:57 -07001180 }
1181
Chet Haased82c8ac2013-08-26 14:20:16 -07001182 Transition setSceneRoot(ViewGroup sceneRoot) {
Chet Haase6ebe3de2013-06-17 16:50:50 -07001183 mSceneRoot = sceneRoot;
Chet Haased82c8ac2013-08-26 14:20:16 -07001184 return this;
Chet Haase6ebe3de2013-06-17 16:50:50 -07001185 }
1186
Chet Haasefaebd8f2012-05-18 14:17:57 -07001187 @Override
1188 public String toString() {
1189 return toString("");
1190 }
1191
Chet Haase6ebe3de2013-06-17 16:50:50 -07001192 @Override
1193 public Transition clone() {
1194 Transition clone = null;
1195 try {
1196 clone = (Transition) super.clone();
Chet Haase199acdf2013-07-24 18:40:55 -07001197 clone.mAnimators = new ArrayList<Animator>();
Chet Haase6ebe3de2013-06-17 16:50:50 -07001198 } catch (CloneNotSupportedException e) {}
1199
1200 return clone;
1201 }
1202
Chet Haase199acdf2013-07-24 18:40:55 -07001203 /**
1204 * Returns the name of this Transition. This name is used internally to distinguish
1205 * between different transitions to determine when interrupting transitions overlap.
Chet Haased82c8ac2013-08-26 14:20:16 -07001206 * For example, a ChangeBounds running on the same target view as another ChangeBounds
1207 * should determine whether the old transition is animating to different end values
1208 * and should be canceled in favor of the new transition.
Chet Haase199acdf2013-07-24 18:40:55 -07001209 *
1210 * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
1211 * but subclasses are free to override and return something different.</p>
1212 *
1213 * @return The name of this transition.
1214 */
1215 public String getName() {
1216 return mName;
1217 }
1218
Chet Haasefaebd8f2012-05-18 14:17:57 -07001219 String toString(String indent) {
1220 String result = indent + getClass().getSimpleName() + "@" +
1221 Integer.toHexString(hashCode()) + ": ";
Chet Haasec43524f2013-07-16 14:40:11 -07001222 if (mDuration != -1) {
1223 result += "dur(" + mDuration + ") ";
Chet Haasefaebd8f2012-05-18 14:17:57 -07001224 }
Chet Haasec43524f2013-07-16 14:40:11 -07001225 if (mStartDelay != -1) {
1226 result += "dly(" + mStartDelay + ") ";
Chet Haasefaebd8f2012-05-18 14:17:57 -07001227 }
Chet Haasec43524f2013-07-16 14:40:11 -07001228 if (mInterpolator != null) {
1229 result += "interp(" + mInterpolator + ") ";
1230 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001231 if (mTargetIds.size() > 0 || mTargets.size() > 0) {
Chet Haasec43524f2013-07-16 14:40:11 -07001232 result += "tgts(";
Chet Haased82c8ac2013-08-26 14:20:16 -07001233 if (mTargetIds.size() > 0) {
1234 for (int i = 0; i < mTargetIds.size(); ++i) {
Chet Haasec43524f2013-07-16 14:40:11 -07001235 if (i > 0) {
1236 result += ", ";
1237 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001238 result += mTargetIds.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -07001239 }
1240 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001241 if (mTargets.size() > 0) {
1242 for (int i = 0; i < mTargets.size(); ++i) {
Chet Haasec43524f2013-07-16 14:40:11 -07001243 if (i > 0) {
1244 result += ", ";
1245 }
Chet Haased82c8ac2013-08-26 14:20:16 -07001246 result += mTargets.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -07001247 }
1248 }
1249 result += ")";
1250 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001251 return result;
1252 }
1253
1254 /**
1255 * A transition listener receives notifications from a transition.
Chet Haase199acdf2013-07-24 18:40:55 -07001256 * Notifications indicate transition lifecycle events.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001257 */
1258 public static interface TransitionListener {
1259 /**
1260 * Notification about the start of the transition.
1261 *
1262 * @param transition The started transition.
1263 */
1264 void onTransitionStart(Transition transition);
1265
1266 /**
1267 * Notification about the end of the transition. Canceled transitions
1268 * will always notify listeners of both the cancellation and end
Chet Haase199acdf2013-07-24 18:40:55 -07001269 * events. That is, {@link #onTransitionEnd(Transition)} is always called,
Chet Haasefaebd8f2012-05-18 14:17:57 -07001270 * regardless of whether the transition was canceled or played
1271 * through to completion.
1272 *
1273 * @param transition The transition which reached its end.
1274 */
1275 void onTransitionEnd(Transition transition);
1276
1277 /**
1278 * Notification about the cancellation of the transition.
Chet Haased82c8ac2013-08-26 14:20:16 -07001279 * Note that cancel may be called by a parent {@link TransitionSet} on
Chet Haase199acdf2013-07-24 18:40:55 -07001280 * a child transition which has not yet started. This allows the child
1281 * transition to restore state on target objects which was set at
Chet Haased82c8ac2013-08-26 14:20:16 -07001282 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
1283 * createAnimator()} time.
Chet Haasefaebd8f2012-05-18 14:17:57 -07001284 *
1285 * @param transition The transition which was canceled.
1286 */
1287 void onTransitionCancel(Transition transition);
Chet Haase199acdf2013-07-24 18:40:55 -07001288
1289 /**
1290 * Notification when a transition is paused.
Chet Haased82c8ac2013-08-26 14:20:16 -07001291 * Note that createAnimator() may be called by a parent {@link TransitionSet} on
Chet Haase199acdf2013-07-24 18:40:55 -07001292 * a child transition which has not yet started. This allows the child
1293 * transition to restore state on target objects which was set at
Chet Haased82c8ac2013-08-26 14:20:16 -07001294 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
1295 * createAnimator()} time.
Chet Haase199acdf2013-07-24 18:40:55 -07001296 *
1297 * @param transition The transition which was paused.
1298 */
1299 void onTransitionPause(Transition transition);
1300
1301 /**
1302 * Notification when a transition is resumed.
Chet Haased82c8ac2013-08-26 14:20:16 -07001303 * Note that resume() may be called by a parent {@link TransitionSet} on
Chet Haase199acdf2013-07-24 18:40:55 -07001304 * a child transition which has not yet started. This allows the child
1305 * transition to restore state which may have changed in an earlier call
1306 * to {@link #onTransitionPause(Transition)}.
1307 *
1308 * @param transition The transition which was resumed.
1309 */
1310 void onTransitionResume(Transition transition);
Chet Haasefaebd8f2012-05-18 14:17:57 -07001311 }
1312
1313 /**
1314 * Utility adapter class to avoid having to override all three methods
1315 * whenever someone just wants to listen for a single event.
1316 *
1317 * @hide
1318 * */
1319 public static class TransitionListenerAdapter implements TransitionListener {
1320 @Override
1321 public void onTransitionStart(Transition transition) {
1322 }
1323
1324 @Override
1325 public void onTransitionEnd(Transition transition) {
1326 }
1327
1328 @Override
1329 public void onTransitionCancel(Transition transition) {
1330 }
Chet Haase199acdf2013-07-24 18:40:55 -07001331
1332 @Override
1333 public void onTransitionPause(Transition transition) {
1334 }
1335
1336 @Override
1337 public void onTransitionResume(Transition transition) {
1338 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001339 }
1340
Chet Haase199acdf2013-07-24 18:40:55 -07001341 /**
1342 * Holds information about each animator used when a new transition starts
1343 * while other transitions are still running to determine whether a running
1344 * animation should be canceled or a new animation noop'd. The structure holds
1345 * information about the state that an animation is going to, to be compared to
1346 * end state of a new animation.
1347 */
1348 private static class AnimationInfo {
1349 View view;
1350 String name;
1351 TransitionValues values;
1352
1353 AnimationInfo(View view, String name, TransitionValues values) {
1354 this.view = view;
1355 this.name = name;
1356 this.values = values;
1357 }
1358 }
Chet Haasefaebd8f2012-05-18 14:17:57 -07001359}