blob: f99ddc0f018d39f1e0248b8fbd456eb07e9d734d [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 Haasefaebd8f2012-05-18 14:17:57 -070017package android.view.transition;
18
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;
Chet Haase2ea7f8b2013-06-21 15:00:05 -070025import android.util.Pair;
Chet Haasefaebd8f2012-05-18 14:17:57 -070026import android.util.SparseArray;
27import android.view.SurfaceView;
28import android.view.TextureView;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.ViewOverlay;
32import android.widget.ListView;
33
34import java.util.ArrayList;
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
39 * choreograph several child transitions ({@link TransitionGroup} or they may
40 * 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
54 * specific transitions (such as {@link Crossfade}) may not be compatible
55 * with TextureView because they rely on {@link ViewOverlay} functionality,
56 * which does not currently work with TextureView.</p>
57 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070058public abstract class Transition implements Cloneable {
Chet Haasefaebd8f2012-05-18 14:17:57 -070059
60 private static final String LOG_TAG = "Transition";
61 static final boolean DBG = false;
62
63 long mStartDelay = -1;
64 long mDuration = -1;
65 TimeInterpolator mInterpolator = null;
66 int[] mTargetIds;
67 View[] mTargets;
Chet Haase6ebe3de2013-06-17 16:50:50 -070068 private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
69 private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
70 TransitionGroup mParent = null;
Chet Haasefaebd8f2012-05-18 14:17:57 -070071
Chet Haase6ebe3de2013-06-17 16:50:50 -070072 // Scene Root is set at play() time in the cloned Transition
73 ViewGroup mSceneRoot = null;
74
75 // Used to carry data between setup() and play(), cleared before every scene transition
Chet Haasefaebd8f2012-05-18 14:17:57 -070076 private ArrayList<TransitionValues> mPlayStartValuesList = new ArrayList<TransitionValues>();
77 private ArrayList<TransitionValues> mPlayEndValuesList = new ArrayList<TransitionValues>();
78
Chet Haasee9d32ea2013-06-04 08:46:42 -070079 // Track all animators in use in case the transition gets canceled and needs to
80 // cancel running animators
81 private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
82
Chet Haasefaebd8f2012-05-18 14:17:57 -070083 // Number of per-target instances of this Transition currently running. This count is
84 // determined by calls to startTransition() and endTransition()
85 int mNumInstances = 0;
86
87
Chet Haasec43524f2013-07-16 14:40:11 -070088
89 // The set of listeners to be sent transition lifecycle events.
Chet Haasefaebd8f2012-05-18 14:17:57 -070090 ArrayList<TransitionListener> mListeners = null;
91
Chet Haasec43524f2013-07-16 14:40:11 -070092 // The set of animators collected from calls to play(), to be run in runAnimations()
93 ArrayMap<Pair<TransitionValues, TransitionValues>, Animator> mAnimatorMap =
94 new ArrayMap<Pair<TransitionValues, TransitionValues>, Animator>();
95
Chet Haasefaebd8f2012-05-18 14:17:57 -070096 /**
97 * Constructs a Transition object with no target objects. A transition with
98 * no targets defaults to running on all target objects in the scene hierarchy
99 * (if the transition is not contained in a TransitionGroup), or all target
100 * objects passed down from its parent (if it is in a TransitionGroup).
101 */
102 public Transition() {}
103
104 /**
105 * Sets the duration of this transition. By default, there is no duration
106 * (indicated by a negative number), which means that the Animator created by
107 * the transition will have its own specified duration. If the duration of a
108 * Transition is set, that duration will override the Animator duration.
109 *
110 * @param duration The length of the animation, in milliseconds.
111 * @return This transition object.
112 */
113 public Transition setDuration(long duration) {
114 mDuration = duration;
115 return this;
116 }
117
118 public long getDuration() {
119 return mDuration;
120 }
121
122 /**
123 * Sets the startDelay of this transition. By default, there is no delay
124 * (indicated by a negative number), which means that the Animator created by
125 * the transition will have its own specified startDelay. If the delay of a
126 * Transition is set, that delay will override the Animator delay.
127 *
128 * @param startDelay The length of the delay, in milliseconds.
129 */
130 public void setStartDelay(long startDelay) {
131 mStartDelay = startDelay;
132 }
133
134 public long getStartDelay() {
135 return mStartDelay;
136 }
137
138 /**
139 * Sets the interpolator of this transition. By default, the interpolator
140 * is null, which means that the Animator created by the transition
141 * will have its own specified interpolator. If the interpolator of a
142 * Transition is set, that interpolator will override the Animator interpolator.
143 *
144 * @param interpolator The time interpolator used by the transition
145 */
146 public void setInterpolator(TimeInterpolator interpolator) {
147 mInterpolator = interpolator;
148 }
149
150 public TimeInterpolator getInterpolator() {
151 return mInterpolator;
152 }
153
154 /**
155 * This method is called by the transition's parent (all the way up to the
156 * topmost Transition in the hierarchy) with the sceneRoot and start/end
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700157 * values that the transition may need to set up initial target values
158 * and construct an appropriate animation. For example, if an overall
159 * Transition is a {@link TransitionGroup} consisting of several
Chet Haasefaebd8f2012-05-18 14:17:57 -0700160 * child transitions in sequence, then some of the child transitions may
161 * want to set initial values on target views prior to the overall
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700162 * Transition commencing, to put them in an appropriate state for the
Chet Haasefaebd8f2012-05-18 14:17:57 -0700163 * delay between that start and the child Transition start time. For
164 * example, a transition that fades an item in may wish to set the starting
165 * alpha value to 0, to avoid it blinking in prior to the transition
166 * actually starting the animation. This is necessary because the scene
167 * change that triggers the Transition will automatically set the end-scene
168 * on all target views, so a Transition that wants to animate from a
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700169 * different value should set that value prior to returning from this method.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700170 *
171 * <p>Additionally, a Transition can perform logic to determine whether
172 * the transition needs to run on the given target and start/end values.
173 * For example, a transition that resizes objects on the screen may wish
174 * to avoid running for views which are not present in either the start
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700175 * or end scenes. A return value of <code>null</code> indicates that
176 * no animation should run. The default implementation returns null.</p>
177 *
178 * <p>If there is an animator created and returned from this method, the
179 * transition mechanism will apply any applicable duration, startDelay,
180 * and interpolator to that animation and start it. A return value of
181 * <code>null</code> indicates that no animation should run. The default
182 * implementation returns null.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700183 *
184 * <p>The method is called for every applicable target object, which is
185 * stored in the {@link TransitionValues#view} field.</p>
186 *
187 * @param sceneRoot
188 * @param startValues
189 * @param endValues
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700190 * @return A non-null Animator to be started at the appropriate time in the
191 * overall transition for this scene change, null otherwise.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700192 */
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700193 protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700194 TransitionValues endValues) {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700195 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700196 }
197
198 /**
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700199 * This version of play() is called with the entire set of start/end
Chet Haasefaebd8f2012-05-18 14:17:57 -0700200 * values. The implementation in Transition iterates through these lists
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700201 * and calls {@link #play(ViewGroup, TransitionValues, TransitionValues)}
Chet Haasefaebd8f2012-05-18 14:17:57 -0700202 * with each set of start/end values on this transition. The
203 * TransitionGroup subclass overrides this method and delegates it to
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700204 * each of its children in succession.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700205 *
206 * @hide
207 */
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700208 protected void play(ViewGroup sceneRoot, TransitionValuesMaps startValues,
Chet Haase6ebe3de2013-06-17 16:50:50 -0700209 TransitionValuesMaps endValues) {
Chet Haasec43524f2013-07-16 14:40:11 -0700210 if (DBG) {
211 Log.d(LOG_TAG, "play() for " + this);
212 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700213 mPlayStartValuesList.clear();
214 mPlayEndValuesList.clear();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700215 ArrayMap<View, TransitionValues> endCopy =
216 new ArrayMap<View, TransitionValues>(endValues.viewValues);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700217 SparseArray<TransitionValues> endIdCopy =
Chet Haase6ebe3de2013-06-17 16:50:50 -0700218 new SparseArray<TransitionValues>(endValues.idValues.size());
219 for (int i = 0; i < endValues.idValues.size(); ++i) {
220 int id = endValues.idValues.keyAt(i);
221 endIdCopy.put(id, endValues.idValues.valueAt(i));
Chet Haasefaebd8f2012-05-18 14:17:57 -0700222 }
223 LongSparseArray<TransitionValues> endItemIdCopy =
Chet Haase6ebe3de2013-06-17 16:50:50 -0700224 new LongSparseArray<TransitionValues>(endValues.itemIdValues.size());
225 for (int i = 0; i < endValues.itemIdValues.size(); ++i) {
226 long id = endValues.itemIdValues.keyAt(i);
227 endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i));
Chet Haasefaebd8f2012-05-18 14:17:57 -0700228 }
229 // Walk through the start values, playing everything we find
230 // Remove from the end set as we go
231 ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
232 ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700233 for (View view : startValues.viewValues.keySet()) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700234 TransitionValues start = null;
235 TransitionValues end = null;
236 boolean isInListView = false;
237 if (view.getParent() instanceof ListView) {
238 isInListView = true;
239 }
240 if (!isInListView) {
241 int id = view.getId();
Chet Haase6ebe3de2013-06-17 16:50:50 -0700242 start = startValues.viewValues.get(view) != null ?
243 startValues.viewValues.get(view) : startValues.idValues.get(id);
244 if (endValues.viewValues.get(view) != null) {
245 end = endValues.viewValues.get(view);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700246 endCopy.remove(view);
247 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700248 end = endValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700249 View removeView = null;
250 for (View viewToRemove : endCopy.keySet()) {
251 if (viewToRemove.getId() == id) {
252 removeView = viewToRemove;
253 }
254 }
255 if (removeView != null) {
256 endCopy.remove(removeView);
257 }
258 }
259 endIdCopy.remove(id);
260 if (isValidTarget(view, id)) {
261 startValuesList.add(start);
262 endValuesList.add(end);
263 }
264 } else {
265 ListView parent = (ListView) view.getParent();
266 if (parent.getAdapter().hasStableIds()) {
267 int position = parent.getPositionForView(view);
268 long itemId = parent.getItemIdAtPosition(position);
Chet Haase6ebe3de2013-06-17 16:50:50 -0700269 start = startValues.itemIdValues.get(itemId);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700270 endItemIdCopy.remove(itemId);
271 // TODO: deal with targetIDs for itemIDs for ListView items
272 startValuesList.add(start);
273 endValuesList.add(end);
274 }
275 }
276 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700277 int startItemIdCopySize = startValues.itemIdValues.size();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700278 for (int i = 0; i < startItemIdCopySize; ++i) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700279 long id = startValues.itemIdValues.keyAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700280 if (isValidTarget(null, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700281 TransitionValues start = startValues.itemIdValues.get(id);
282 TransitionValues end = endValues.itemIdValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700283 endItemIdCopy.remove(id);
284 startValuesList.add(start);
285 endValuesList.add(end);
286 }
287 }
288 // Now walk through the remains of the end set
289 for (View view : endCopy.keySet()) {
290 int id = view.getId();
291 if (isValidTarget(view, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700292 TransitionValues start = startValues.viewValues.get(view) != null ?
293 startValues.viewValues.get(view) : startValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700294 TransitionValues end = endCopy.get(view);
295 endIdCopy.remove(id);
296 startValuesList.add(start);
297 endValuesList.add(end);
298 }
299 }
300 int endIdCopySize = endIdCopy.size();
301 for (int i = 0; i < endIdCopySize; ++i) {
302 int id = endIdCopy.keyAt(i);
303 if (isValidTarget(null, id)) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700304 TransitionValues start = startValues.idValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700305 TransitionValues end = endIdCopy.get(id);
306 startValuesList.add(start);
307 endValuesList.add(end);
308 }
309 }
310 int endItemIdCopySize = endItemIdCopy.size();
311 for (int i = 0; i < endItemIdCopySize; ++i) {
312 long id = endItemIdCopy.keyAt(i);
313 // TODO: Deal with targetIDs and itemIDs
Chet Haase6ebe3de2013-06-17 16:50:50 -0700314 TransitionValues start = startValues.itemIdValues.get(id);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700315 TransitionValues end = endItemIdCopy.get(id);
316 startValuesList.add(start);
317 endValuesList.add(end);
318 }
319 for (int i = 0; i < startValuesList.size(); ++i) {
320 TransitionValues start = startValuesList.get(i);
321 TransitionValues end = endValuesList.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -0700322 // Only bother trying to animate with values that differ between start/end
323 if (start != null || end != null) {
324 if (start == null || !start.equals(end)) {
325 if (DBG) {
326 View view = (end != null) ? end.view : start.view;
327 Log.d(LOG_TAG, " differing start/end values for view " +
328 view);
329 if (start == null || end == null) {
330 if (start == null) {
331 Log.d(LOG_TAG, " " + ((start == null) ?
332 "start null, end non-null" : "start non-null, end null"));
333 }
334 } else {
335 for (String key : start.values.keySet()) {
336 Object startValue = start.values.get(key);
337 Object endValue = end.values.get(key);
338 if (startValue != endValue && !startValue.equals(endValue)) {
339 Log.d(LOG_TAG, " " + key + ": start(" + startValue +
340 "), end(" + endValue +")");
341 }
342 }
343 }
344 }
345 // TODO: what to do about targetIds and itemIds?
346 Animator animator = play(sceneRoot, start, end);
347 if (animator != null) {
348 mAnimatorMap.put(new Pair(start, end), animator);
349 // Note: we've already done the check against targetIDs in these lists
350 mPlayStartValuesList.add(start);
351 mPlayEndValuesList.add(end);
352 }
353 } else if (DBG) {
354 View view = (end != null) ? end.view : start.view;
355 Log.d(LOG_TAG, " No change for view " + view);
356 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700357 }
358 }
359 }
360
361 /**
362 * Internal utility method for checking whether a given view/id
363 * is valid for this transition, where "valid" means that either
364 * the Transition has no target/targetId list (the default, in which
365 * cause the transition should act on all views in the hiearchy), or
366 * the given view is in the target list or the view id is in the
367 * targetId list. If the target parameter is null, then the target list
368 * is not checked (this is in the case of ListView items, where the
369 * views are ignored and only the ids are used).
370 */
371 boolean isValidTarget(View target, long targetId) {
372 if (mTargetIds == null && mTargets == null) {
373 return true;
374 }
375 if (mTargetIds != null) {
376 for (int i = 0; i < mTargetIds.length; ++i) {
377 if (mTargetIds[i] == targetId) {
378 return true;
379 }
380 }
381 }
382 if (target != null && mTargets != null) {
383 for (int i = 0; i < mTargets.length; ++i) {
384 if (mTargets[i] == target) {
385 return true;
386 }
387 }
388 }
389 return false;
390 }
391
392 /**
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700393 * This is called internally once all animations have been set up by the
394 * transition hierarchy. \
Chet Haasefaebd8f2012-05-18 14:17:57 -0700395 *
396 * @hide
397 */
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700398 protected void runAnimations() {
Chet Haasec43524f2013-07-16 14:40:11 -0700399 if (DBG && mPlayStartValuesList.size() > 0) {
400 Log.d(LOG_TAG, "runAnimations (" + mPlayStartValuesList.size() + ") on " + this);
401 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700402 startTransition();
403 // Now walk the list of TransitionValues, calling play for each pair
404 for (int i = 0; i < mPlayStartValuesList.size(); ++i) {
405 TransitionValues start = mPlayStartValuesList.get(i);
406 TransitionValues end = mPlayEndValuesList.get(i);
Chet Haasec43524f2013-07-16 14:40:11 -0700407 Animator anim = mAnimatorMap.get(new Pair(start, end));
408 if (DBG) {
409 Log.d(LOG_TAG, " anim: " + anim);
410 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700411 startTransition();
Chet Haasec43524f2013-07-16 14:40:11 -0700412 runAnimator(anim);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700413 }
414 mPlayStartValuesList.clear();
415 mPlayEndValuesList.clear();
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700416 mAnimatorMap.clear();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700417 endTransition();
418 }
419
Chet Haasee9d32ea2013-06-04 08:46:42 -0700420 private void runAnimator(Animator animator) {
421 if (animator != null) {
422 // TODO: could be a single listener instance for all of them since it uses the param
423 animator.addListener(new AnimatorListenerAdapter() {
424 @Override
425 public void onAnimationStart(Animator animation) {
426 mCurrentAnimators.add(animation);
427 }
428 @Override
429 public void onAnimationEnd(Animator animation) {
430 mCurrentAnimators.remove(animation);
431 }
432 });
433 animate(animator);
434 }
435 }
436
Chet Haasefaebd8f2012-05-18 14:17:57 -0700437 /**
438 * Captures the current scene of values for the properties that this
439 * transition monitors. These values can be either the start or end
440 * values used in a subsequent call to
441 * {@link #play(ViewGroup, TransitionValues, TransitionValues)}, as indicated by
442 * <code>start</code>. The main concern for an implementation is what the
443 * properties are that the transition cares about and what the values are
444 * for all of those properties. The start and end values will be compared
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700445 * later during the
446 * {@link #play(android.view.ViewGroup, TransitionValues, TransitionValues)}
447 * method to determine what, if any, animations, should be run.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700448 *
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700449 * @param transitionValues The holder for any values that the Transition
450 * wishes to store. Values are stored in the <code>values</code> field
451 * of this TransitionValues object and are keyed from
452 * a String value. For example, to store a view's rotation value,
453 * a transition might call
454 * <code>transitionValues.values.put("appname:transitionname:rotation",
455 * view.getRotation())</code>. The target view will already be stored in
456 * the transitionValues structure when this method is called.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700457 */
458 protected abstract void captureValues(TransitionValues transitionValues, boolean start);
459
460 /**
461 * Sets the ids of target views that this Transition is interested in
462 * animating. By default, there are no targetIds, and a Transition will
463 * listen for changes on every view in the hierarchy below the sceneRoot
464 * of the Scene being transitioned into. Setting targetIDs constrains
465 * the Transition to only listen for, and act on, views with these IDs.
466 * Views with different IDs, or no IDs whatsoever, will be ignored.
467 *
468 * @see View#getId()
469 * @param targetIds A list of IDs which specify the set of Views on which
470 * the Transition will act.
471 * @return Transition The Transition on which the targetIds have been set.
472 * Returning the same object makes it easier to chain calls during
473 * construction, such as
474 * <code>transitionGroup.addTransitions(new Fade()).setTargetIds(someId);</code>
475 */
476 public Transition setTargetIds(int... targetIds) {
477 int numTargets = targetIds.length;
478 mTargetIds = new int[numTargets];
479 System.arraycopy(targetIds, 0, mTargetIds, 0, numTargets);
480 return this;
481 }
482
483 /**
484 * Sets the target view instances that this Transition is interested in
485 * animating. By default, there are no targets, and a Transition will
486 * listen for changes on every view in the hierarchy below the sceneRoot
487 * of the Scene being transitioned into. Setting targets constrains
488 * the Transition to only listen for, and act on, these views.
489 * All other views will be ignored.
490 *
491 * <p>The target list is like the {@link #setTargetIds(int...) targetId}
492 * list except this list specifies the actual View instances, not the ids
493 * of the views. This is an important distinction when scene changes involve
494 * view hierarchies which have been inflated separately; different views may
495 * share the same id but not actually be the same instance. If the transition
496 * should treat those views as the same, then seTargetIds() should be used
497 * instead of setTargets(). If, on the other hand, scene changes involve
498 * changes all within the same view hierarchy, among views which do not
499 * necessary have ids set on them, then the target list may be more
500 * convenient.</p>
501 *
502 * @see #setTargetIds(int...)
503 * @param targets A list of Views on which the Transition will act.
504 * @return Transition The Transition on which the targets have been set.
505 * Returning the same object makes it easier to chain calls during
506 * construction, such as
507 * <code>transitionGroup.addTransitions(new Fade()).setTargets(someView);</code>
508 */
509 public Transition setTargets(View... targets) {
510 int numTargets = targets.length;
511 mTargets = new View[numTargets];
512 System.arraycopy(targets, 0, mTargets, 0, numTargets);
513 return this;
514 }
515
516 /**
517 * Returns the array of target IDs that this transition limits itself to
518 * tracking and animating. If the array is null for both this method and
519 * {@link #getTargets()}, then this transition is
520 * not limited to specific views, and will handle changes to any views
521 * in the hierarchy of a scene change.
522 *
523 * @return the list of target IDs
524 */
525 public int[] getTargetIds() {
526 return mTargetIds;
527 }
528
529 /**
530 * Returns the array of target views that this transition limits itself to
531 * tracking and animating. If the array is null for both this method and
532 * {@link #getTargetIds()}, then this transition is
533 * not limited to specific views, and will handle changes to any views
534 * in the hierarchy of a scene change.
535 *
536 * @return the list of target views
537 */
538 public View[] getTargets() {
539 return mTargets;
540 }
541
542 /**
543 * Recursive method that captures values for the given view and the
544 * hierarchy underneath it.
545 * @param sceneRoot The root of the view hierarchy being captured
546 * @param start true if this capture is happening before the scene change,
547 * false otherwise
548 */
549 void captureValues(ViewGroup sceneRoot, boolean start) {
Chet Haasec81a8492013-07-12 12:54:38 -0700550 if (start) {
551 mStartValues.viewValues.clear();
552 mStartValues.idValues.clear();
553 mStartValues.itemIdValues.clear();
554 } else {
555 mEndValues.viewValues.clear();
556 mEndValues.idValues.clear();
557 mEndValues.itemIdValues.clear();
558 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700559 if (mTargetIds != null && mTargetIds.length > 0 ||
560 mTargets != null && mTargets.length > 0) {
561 if (mTargetIds != null) {
562 for (int i = 0; i < mTargetIds.length; ++i) {
563 int id = mTargetIds[i];
564 View view = sceneRoot.findViewById(id);
565 if (view != null) {
566 TransitionValues values = new TransitionValues();
567 values.view = view;
568 captureValues(values, start);
569 if (start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700570 mStartValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700571 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700572 mStartValues.idValues.put(id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700573 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700574 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700575 mEndValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700576 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700577 mEndValues.idValues.put(id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700578 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700579 }
580 }
581 }
582 }
583 if (mTargets != null) {
584 for (int i = 0; i < mTargets.length; ++i) {
585 View view = mTargets[i];
586 if (view != null) {
587 TransitionValues values = new TransitionValues();
588 values.view = view;
589 captureValues(values, start);
590 if (start) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700591 mStartValues.viewValues.put(view, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700592 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700593 mEndValues.viewValues.put(view, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700594 }
595 }
596 }
597 }
598 } else {
599 captureHierarchy(sceneRoot, start);
600 }
601 }
602
603 /**
604 * Recursive method which captures values for an entire view hierarchy,
605 * starting at some root view. Transitions without targetIDs will use this
606 * method to capture values for all possible views.
607 *
608 * @param view The view for which to capture values. Children of this View
609 * will also be captured, recursively down to the leaf nodes.
610 * @param start true if values are being captured in the start scene, false
611 * otherwise.
612 */
613 private void captureHierarchy(View view, boolean start) {
614 if (view == null) {
615 return;
616 }
617 boolean isListViewItem = false;
618 if (view.getParent() instanceof ListView) {
619 isListViewItem = true;
620 }
621 if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
622 // ignore listview children unless we can track them with stable IDs
623 return;
624 }
625 long id;
626 if (!isListViewItem) {
627 id = view.getId();
628 } else {
629 ListView listview = (ListView) view.getParent();
630 int position = listview.getPositionForView(view);
631 id = listview.getItemIdAtPosition(position);
632 view.setHasTransientState(true);
633 }
634 TransitionValues values = new TransitionValues();
635 values.view = view;
636 captureValues(values, start);
637 if (start) {
638 if (!isListViewItem) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700639 mStartValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700640 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700641 mStartValues.idValues.put((int) id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700642 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700643 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700644 mStartValues.itemIdValues.put(id, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700645 }
646 } else {
647 if (!isListViewItem) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700648 mEndValues.viewValues.put(view, values);
Chet Haase867a8662013-06-03 07:30:21 -0700649 if (id >= 0) {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700650 mEndValues.idValues.put((int) id, values);
Chet Haase867a8662013-06-03 07:30:21 -0700651 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700652 } else {
Chet Haase6ebe3de2013-06-17 16:50:50 -0700653 mEndValues.itemIdValues.put(id, values);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700654 }
655 }
656 if (view instanceof ViewGroup) {
657 ViewGroup parent = (ViewGroup) view;
658 for (int i = 0; i < parent.getChildCount(); ++i) {
659 captureHierarchy(parent.getChildAt(i), start);
660 }
661 }
662 }
663
664 /**
Chet Haase6ebe3de2013-06-17 16:50:50 -0700665 * This method can be called by transitions to get the TransitionValues for
666 * any particular view during the transition-playing process. This might be
667 * necessary, for example, to query the before/after state of related views
668 * for a given transition.
669 */
670 protected TransitionValues getTransitionValues(View view, boolean start) {
671 if (mParent != null) {
672 return mParent.getTransitionValues(view, start);
673 }
674 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
675 TransitionValues values = valuesMaps.viewValues.get(view);
676 if (values == null) {
677 int id = view.getId();
678 if (id >= 0) {
679 values = valuesMaps.idValues.get(id);
680 }
681 if (values == null && view.getParent() instanceof ListView) {
682 ListView listview = (ListView) view.getParent();
683 int position = listview.getPositionForView(view);
684 long itemId = listview.getItemIdAtPosition(position);
685 values = valuesMaps.itemIdValues.get(itemId);
686 }
687 // TODO: Doesn't handle the case where a view was parented to a
688 // ListView (with an itemId), but no longer is
689 }
690 return values;
691 }
692
693 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -0700694 * Called by TransitionManager to play the transition. This calls
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700695 * play() to set things up and create all of the animations and then
696 * runAnimations() to actually start the animations.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700697 */
Chet Haase6ebe3de2013-06-17 16:50:50 -0700698 void playTransition(ViewGroup sceneRoot) {
699 // setup() must be called on entire transition hierarchy and set of views
Chet Haasefaebd8f2012-05-18 14:17:57 -0700700 // before calling play() on anything; every transition needs a chance to set up
701 // target views appropriately before transitions begin running
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700702 play(sceneRoot, mStartValues, mEndValues);
703 runAnimations();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700704 }
705
706 /**
707 * This is a utility method used by subclasses to handle standard parts of
708 * setting up and running an Animator: it sets the {@link #getDuration()
709 * duration} and the {@link #getStartDelay() startDelay}, starts the
710 * animation, and, when the animator ends, calls {@link #endTransition()}.
711 *
712 * @param animator The Animator to be run during this transition.
713 *
714 * @hide
715 */
716 protected void animate(Animator animator) {
717 // TODO: maybe pass auto-end as a boolean parameter?
718 if (animator == null) {
719 endTransition();
720 } else {
721 if (getDuration() >= 0) {
722 animator.setDuration(getDuration());
723 }
724 if (getStartDelay() >= 0) {
725 animator.setStartDelay(getStartDelay());
726 }
727 if (getInterpolator() != null) {
728 animator.setInterpolator(getInterpolator());
729 }
730 animator.addListener(new AnimatorListenerAdapter() {
731 @Override
Chet Haasefaebd8f2012-05-18 14:17:57 -0700732 public void onAnimationEnd(Animator animation) {
733 endTransition();
734 animation.removeListener(this);
735 }
736 });
737 animator.start();
738 }
739 }
740
741 /**
742 * Subclasses may override to receive notice of when the transition starts.
743 * This is equivalent to listening for the
744 * {@link TransitionListener#onTransitionStart(Transition)} callback.
745 */
746 protected void onTransitionStart() {
747 }
748
749 /**
750 * Subclasses may override to receive notice of when the transition is
751 * canceled. This is equivalent to listening for the
752 * {@link TransitionListener#onTransitionCancel(Transition)} callback.
753 */
754 protected void onTransitionCancel() {
755 }
756
757 /**
758 * Subclasses may override to receive notice of when the transition ends.
759 * This is equivalent to listening for the
760 * {@link TransitionListener#onTransitionEnd(Transition)} callback.
761 */
762 protected void onTransitionEnd() {
763 }
764
765 /**
766 * This method is called automatically by the transition and
767 * TransitionGroup classes prior to a Transition subclass starting;
768 * subclasses should not need to call it directly.
769 *
770 * @hide
771 */
772 protected void startTransition() {
773 if (mNumInstances == 0) {
774 onTransitionStart();
775 if (mListeners != null && mListeners.size() > 0) {
776 ArrayList<TransitionListener> tmpListeners =
777 (ArrayList<TransitionListener>) mListeners.clone();
778 int numListeners = tmpListeners.size();
779 for (int i = 0; i < numListeners; ++i) {
780 tmpListeners.get(i).onTransitionStart(this);
781 }
782 }
783 }
784 mNumInstances++;
785 }
786
787 /**
788 * This method is called automatically by the Transition and
789 * TransitionGroup classes when a transition finishes, either because
790 * a transition did nothing (returned a null Animator from
791 * {@link Transition#play(ViewGroup, TransitionValues,
792 * TransitionValues)}) or because the transition returned a valid
793 * Animator and endTransition() was called in the onAnimationEnd()
794 * callback of the AnimatorListener.
795 *
796 * @hide
797 */
798 protected void endTransition() {
799 --mNumInstances;
800 if (mNumInstances == 0) {
801 onTransitionEnd();
802 if (mListeners != null && mListeners.size() > 0) {
803 ArrayList<TransitionListener> tmpListeners =
804 (ArrayList<TransitionListener>) mListeners.clone();
805 int numListeners = tmpListeners.size();
806 for (int i = 0; i < numListeners; ++i) {
807 tmpListeners.get(i).onTransitionEnd(this);
808 }
809 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700810 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
811 TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700812 View v = tv.view;
813 if (v.hasTransientState()) {
814 v.setHasTransientState(false);
815 }
816 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700817 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
818 TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700819 View v = tv.view;
820 if (v.hasTransientState()) {
821 v.setHasTransientState(false);
822 }
823 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700824 }
825 }
826
827 /**
828 * This method cancels a transition that is currently running.
829 * Implementation TBD.
830 */
831 protected void cancelTransition() {
832 // TODO: how does this work with instances?
833 // TODO: this doesn't actually do *anything* yet
Chet Haasee9d32ea2013-06-04 08:46:42 -0700834 int numAnimators = mCurrentAnimators.size();
Chet Haase25a738f2013-06-04 16:35:14 -0700835 for (int i = numAnimators - 1; i >= 0; i--) {
Chet Haasee9d32ea2013-06-04 08:46:42 -0700836 Animator animator = mCurrentAnimators.get(i);
837 animator.cancel();
838 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700839 onTransitionCancel();
840 if (mListeners != null && mListeners.size() > 0) {
841 ArrayList<TransitionListener> tmpListeners =
842 (ArrayList<TransitionListener>) mListeners.clone();
843 int numListeners = tmpListeners.size();
844 for (int i = 0; i < numListeners; ++i) {
845 tmpListeners.get(i).onTransitionCancel(this);
846 }
847 }
848 }
849
850 /**
851 * Adds a listener to the set of listeners that are sent events through the
852 * life of an animation, such as start, repeat, and end.
853 *
854 * @param listener the listener to be added to the current set of listeners
855 * for this animation.
856 */
857 public void addListener(TransitionListener listener) {
858 if (mListeners == null) {
859 mListeners = new ArrayList<TransitionListener>();
860 }
861 mListeners.add(listener);
862 }
863
864 /**
865 * Removes a listener from the set listening to this animation.
866 *
867 * @param listener the listener to be removed from the current set of
868 * listeners for this transition.
869 */
870 public void removeListener(TransitionListener listener) {
871 if (mListeners == null) {
872 return;
873 }
874 mListeners.remove(listener);
875 if (mListeners.size() == 0) {
876 mListeners = null;
877 }
878 }
879
880 /**
881 * Gets the set of {@link TransitionListener} objects that are currently
882 * listening for events on this <code>Transition</code> object.
883 *
884 * @return ArrayList<TransitionListener> The set of listeners.
885 */
886 public ArrayList<TransitionListener> getListeners() {
887 return mListeners;
888 }
889
Chet Haase6ebe3de2013-06-17 16:50:50 -0700890 void setSceneRoot(ViewGroup sceneRoot) {
891 mSceneRoot = sceneRoot;
892 }
893
Chet Haasefaebd8f2012-05-18 14:17:57 -0700894 @Override
895 public String toString() {
896 return toString("");
897 }
898
Chet Haase6ebe3de2013-06-17 16:50:50 -0700899 @Override
900 public Transition clone() {
901 Transition clone = null;
902 try {
903 clone = (Transition) super.clone();
904 } catch (CloneNotSupportedException e) {}
905
906 return clone;
907 }
908
Chet Haasefaebd8f2012-05-18 14:17:57 -0700909 String toString(String indent) {
910 String result = indent + getClass().getSimpleName() + "@" +
911 Integer.toHexString(hashCode()) + ": ";
Chet Haasec43524f2013-07-16 14:40:11 -0700912 if (mDuration != -1) {
913 result += "dur(" + mDuration + ") ";
Chet Haasefaebd8f2012-05-18 14:17:57 -0700914 }
Chet Haasec43524f2013-07-16 14:40:11 -0700915 if (mStartDelay != -1) {
916 result += "dly(" + mStartDelay + ") ";
Chet Haasefaebd8f2012-05-18 14:17:57 -0700917 }
Chet Haasec43524f2013-07-16 14:40:11 -0700918 if (mInterpolator != null) {
919 result += "interp(" + mInterpolator + ") ";
920 }
921 if (mTargetIds != null || mTargets != null) {
922 result += "tgts(";
923 if (mTargetIds != null) {
924 for (int i = 0; i < mTargetIds.length; ++i) {
925 if (i > 0) {
926 result += ", ";
927 }
928 result += mTargetIds[i];
929 }
930 }
931 if (mTargets != null) {
932 for (int i = 0; i < mTargets.length; ++i) {
933 if (i > 0) {
934 result += ", ";
935 }
936 result += mTargets[i];
937 }
938 }
939 result += ")";
940 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700941 return result;
942 }
943
944 /**
945 * A transition listener receives notifications from a transition.
946 * Notifications indicate transition lifecycle events: when the transition
947 * begins, ends, or is canceled.
948 */
949 public static interface TransitionListener {
950 /**
951 * Notification about the start of the transition.
952 *
953 * @param transition The started transition.
954 */
955 void onTransitionStart(Transition transition);
956
957 /**
958 * Notification about the end of the transition. Canceled transitions
959 * will always notify listeners of both the cancellation and end
960 * events. That is, {@link #onTransitionEnd()} is always called,
961 * regardless of whether the transition was canceled or played
962 * through to completion.
963 *
964 * @param transition The transition which reached its end.
965 */
966 void onTransitionEnd(Transition transition);
967
968 /**
969 * Notification about the cancellation of the transition.
970 *
971 * @param transition The transition which was canceled.
972 */
973 void onTransitionCancel(Transition transition);
974 }
975
976 /**
977 * Utility adapter class to avoid having to override all three methods
978 * whenever someone just wants to listen for a single event.
979 *
980 * @hide
981 * */
982 public static class TransitionListenerAdapter implements TransitionListener {
983 @Override
984 public void onTransitionStart(Transition transition) {
985 }
986
987 @Override
988 public void onTransitionEnd(Transition transition) {
989 }
990
991 @Override
992 public void onTransitionCancel(Transition transition) {
993 }
994 }
995
996}