From ff58f92a0a77ad849ba714b5adac96790eca0048 Mon Sep 17 00:00:00 2001 From: Chet Haase Date: Wed, 11 Sep 2013 13:08:18 -0700 Subject: [PATCH] Add exclude() methods to Transition It would be useful for a transition to declare not just which targets it wants to be run on, but also which targets it wants to avoid. For example, you may not want to animate the items of a ListView, or some other specific target in the view hierarchy. This change adds various exclude*() methods which make it possible to alter a transition to automatically ignore specific views, ids, or classes in the hierarchy. Issue #10692794 Transitions: Need API for excluding targets Change-Id: If38025cdbee537a545e5a4268cbbd763af4622c5 --- api/current.txt | 10 +- core/java/android/transition/Transition.java | 331 +++++++++++++++++- .../transition/TransitionInflater.java | 2 +- .../android/transition/TransitionSet.java | 24 +- core/res/res/values/attrs.xml | 2 +- .../transitiontests/ContactsExpansion.java | 4 +- .../transitiontests/CrossFadeDemo.java | 4 +- .../transitiontests/CrossfadeImage.java | 2 +- .../transitiontests/CrossfadeMultiple.java | 2 +- .../com/android/transitiontests/Demo1.java | 6 +- .../com/android/transitiontests/Demo2.java | 8 +- .../com/android/transitiontests/Demo4.java | 10 +- .../android/transitiontests/FadingTest.java | 4 +- .../transitiontests/InterruptionTest.java | 4 +- .../transitiontests/LoginActivity.java | 10 +- .../transitiontests/ScenesTestv21.java | 14 +- .../android/transitiontests/SequenceTest.java | 6 +- .../transitiontests/SequenceTestSimple.java | 8 +- .../SurfaceAndTextureViews.java | 4 +- 19 files changed, 384 insertions(+), 71 deletions(-) diff --git a/api/current.txt b/api/current.txt index b5930ec056d0b..abb86cd63af7f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -25507,12 +25507,18 @@ package android.transition { public abstract class Transition implements java.lang.Cloneable { ctor public Transition(); method public android.transition.Transition addListener(android.transition.Transition.TransitionListener); + method public android.transition.Transition addTarget(int); method public android.transition.Transition addTarget(android.view.View); - method public android.transition.Transition addTargetId(int); method public abstract void captureEndValues(android.transition.TransitionValues); method public abstract void captureStartValues(android.transition.TransitionValues); method public android.transition.Transition clone(); method public android.animation.Animator createAnimator(android.view.ViewGroup, android.transition.TransitionValues, android.transition.TransitionValues); + method public android.transition.Transition excludeChildren(int, boolean); + method public android.transition.Transition excludeChildren(android.view.View, boolean); + method public android.transition.Transition excludeChildren(java.lang.Class, boolean); + method public android.transition.Transition excludeTarget(int, boolean); + method public android.transition.Transition excludeTarget(android.view.View, boolean); + method public android.transition.Transition excludeTarget(java.lang.Class, boolean); method public long getDuration(); method public android.animation.TimeInterpolator getInterpolator(); method public java.lang.String getName(); @@ -25522,8 +25528,8 @@ package android.transition { method public java.lang.String[] getTransitionProperties(); method public android.transition.TransitionValues getTransitionValues(android.view.View, boolean); method public android.transition.Transition removeListener(android.transition.Transition.TransitionListener); + method public android.transition.Transition removeTarget(int); method public android.transition.Transition removeTarget(android.view.View); - method public android.transition.Transition removeTargetId(int); method public android.transition.Transition setDuration(long); method public android.transition.Transition setInterpolator(android.animation.TimeInterpolator); method public android.transition.Transition setStartDelay(long); diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 8ea9d4873ba01..c588c6bf4c7a0 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewOverlay; import android.widget.ListView; +import android.widget.Spinner; import java.util.ArrayList; import java.util.List; @@ -100,6 +101,12 @@ public abstract class Transition implements Cloneable { TimeInterpolator mInterpolator = null; ArrayList mTargetIds = new ArrayList(); ArrayList mTargets = new ArrayList(); + ArrayList mTargetIdExcludes = null; + ArrayList mTargetExcludes = null; + ArrayList mTargetTypeExcludes = null; + ArrayList mTargetIdChildExcludes = null; + ArrayList mTargetChildExcludes = null; + ArrayList mTargetTypeChildExcludes = null; private TransitionValuesMaps mStartValues = new TransitionValuesMaps(); private TransitionValuesMaps mEndValues = new TransitionValuesMaps(); TransitionSet mParent = null; @@ -430,10 +437,8 @@ public abstract class Transition implements Cloneable { Log.d(LOG_TAG, " differing start/end values for view " + view); if (start == null || end == null) { - if (start == null) { - Log.d(LOG_TAG, " " + ((start == null) ? - "start null, end non-null" : "start non-null, end null")); - } + Log.d(LOG_TAG, " " + ((start == null) ? + "start null, end non-null" : "start non-null, end null")); } else { for (String key : start.values.keySet()) { Object startValue = start.values.get(key); @@ -504,6 +509,21 @@ public abstract class Transition implements Cloneable { * views are ignored and only the ids are used). */ boolean isValidTarget(View target, long targetId) { + if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) { + return false; + } + if (mTargetExcludes != null && mTargetExcludes.contains(target)) { + return false; + } + if (mTargetTypeExcludes != null && target != null) { + int numTypes = mTargetTypeExcludes.size(); + for (int i = 0; i < numTypes; ++i) { + Class type = mTargetTypeExcludes.get(i); + if (type.isInstance(target)) { + return false; + } + } + } if (mTargetIds.size() == 0 && mTargets.size() == 0) { return true; } @@ -652,9 +672,9 @@ public abstract class Transition implements Cloneable { * @return The Transition to which the targetId is added. * Returning the same object makes it easier to chain calls during * construction, such as - * transitionSet.addTransitions(new Fade()).addTargetId(someId); + * transitionSet.addTransitions(new Fade()).addTarget(someId); */ - public Transition addTargetId(int targetId) { + public Transition addTarget(int targetId) { if (targetId > 0) { mTargetIds.add(targetId); } @@ -671,13 +691,219 @@ public abstract class Transition implements Cloneable { * construction, such as * transitionSet.addTransitions(new Fade()).removeTargetId(someId); */ - public Transition removeTargetId(int targetId) { + public Transition removeTarget(int targetId) { if (targetId > 0) { mTargetIds.remove(targetId); } return this; } + /** + * Whether to add the given id to the list of target ids to exclude from this + * transition. The exclude parameter specifies whether the target + * should be added to or removed from the excluded list. + * + *

Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

+ * + * @see #excludeChildren(int, boolean) + * @see #excludeTarget(View, boolean) + * @see #excludeTarget(Class, boolean) + * + * @param targetId The id of a target to ignore when running this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded targets. + * @return This transition object. + */ + public Transition excludeTarget(int targetId, boolean exclude) { + mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude); + return this; + } + + /** + * Whether to add the children of the given id to the list of targets to exclude + * from this transition. The exclude parameter specifies whether + * the children of the target should be added to or removed from the excluded list. + * Excluding children in this way provides a simple mechanism for excluding all + * children of specific targets, rather than individually excluding each + * child individually. + * + *

Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

+ * + * @see #excludeTarget(int, boolean) + * @see #excludeChildren(View, boolean) + * @see #excludeChildren(Class, boolean) + * + * @param targetId The id of a target whose children should be ignored when running + * this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded-child targets. + * @return This transition object. + */ + public Transition excludeChildren(int targetId, boolean exclude) { + mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude); + return this; + } + + /** + * Utility method to manage the boilerplate code that is the same whether we + * are excluding targets or their children. + */ + private ArrayList excludeId(ArrayList list, int targetId, boolean exclude) { + if (targetId > 0) { + if (exclude) { + list = ArrayListManager.add(list, targetId); + } else { + list = ArrayListManager.remove(list, targetId); + } + } + return list; + } + + /** + * Whether to add the given target to the list of targets to exclude from this + * transition. The exclude parameter specifies whether the target + * should be added to or removed from the excluded list. + * + *

Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

+ * + * @see #excludeChildren(View, boolean) + * @see #excludeTarget(int, boolean) + * @see #excludeTarget(Class, boolean) + * + * @param target The target to ignore when running this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded targets. + * @return This transition object. + */ + public Transition excludeTarget(View target, boolean exclude) { + mTargetExcludes = excludeView(mTargetExcludes, target, exclude); + return this; + } + + /** + * Whether to add the children of given target to the list of target children + * to exclude from this transition. The exclude parameter specifies + * whether the target should be added to or removed from the excluded list. + * + *

Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

+ * + * @see #excludeTarget(View, boolean) + * @see #excludeChildren(int, boolean) + * @see #excludeChildren(Class, boolean) + * + * @param target The target to ignore when running this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded targets. + * @return This transition object. + */ + public Transition excludeChildren(View target, boolean exclude) { + mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude); + return this; + } + + /** + * Utility method to manage the boilerplate code that is the same whether we + * are excluding targets or their children. + */ + private ArrayList excludeView(ArrayList list, View target, boolean exclude) { + if (target != null) { + if (exclude) { + list = ArrayListManager.add(list, target); + } else { + list = ArrayListManager.remove(list, target); + } + } + return list; + } + + /** + * Whether to add the given type to the list of types to exclude from this + * transition. The exclude parameter specifies whether the target + * type should be added to or removed from the excluded list. + * + *

Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

+ * + * @see #excludeChildren(Class, boolean) + * @see #excludeTarget(int, boolean) + * @see #excludeTarget(View, boolean) + * + * @param type The type to ignore when running this transition. + * @param exclude Whether to add the target type to or remove it from the + * current list of excluded target types. + * @return This transition object. + */ + public Transition excludeTarget(Class type, boolean exclude) { + mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude); + return this; + } + + /** + * Whether to add the given type to the list of types whose children should + * be excluded from this transition. The exclude parameter + * specifies whether the target type should be added to or removed from + * the excluded list. + * + *

Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

+ * + * @see #excludeTarget(Class, boolean) + * @see #excludeChildren(int, boolean) + * @see #excludeChildren(View, boolean) + * + * @param type The type to ignore when running this transition. + * @param exclude Whether to add the target type to or remove it from the + * current list of excluded target types. + * @return This transition object. + */ + public Transition excludeChildren(Class type, boolean exclude) { + mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude); + return this; + } + + /** + * Utility method to manage the boilerplate code that is the same whether we + * are excluding targets or their children. + */ + private ArrayList excludeType(ArrayList list, Class type, boolean exclude) { + if (type != null) { + if (exclude) { + list = ArrayListManager.add(list, type); + } else { + list = ArrayListManager.remove(list, type); + } + } + return list; + } + /** * Sets the target view instances that this Transition is interested in * animating. By default, there are no targets, and a Transition will @@ -686,18 +912,18 @@ public abstract class Transition implements Cloneable { * the Transition to only listen for, and act on, these views. * All other views will be ignored. * - *

The target list is like the {@link #addTargetId(int) targetId} + *

The target list is like the {@link #addTarget(int) targetId} * list except this list specifies the actual View instances, not the ids * of the views. This is an important distinction when scene changes involve * view hierarchies which have been inflated separately; different views may * share the same id but not actually be the same instance. If the transition - * should treat those views as the same, then {@link #addTargetId(int)} should be used + * should treat those views as the same, then {@link #addTarget(int)} should be used * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve * changes all within the same view hierarchy, among views which do not * necessarily have ids set on them, then the target list of views may be more * convenient.

* - * @see #addTargetId(int) + * @see #addTarget(int) * @param target A View on which the Transition will act, must be non-null. * @return The Transition to which the target is added. * Returning the same object makes it easier to chain calls during @@ -842,15 +1068,30 @@ public abstract class Transition implements Cloneable { // ignore listview children unless we can track them with stable IDs return; } - long id; + int id = View.NO_ID; + long itemId = View.NO_ID; if (!isListViewItem) { id = view.getId(); } else { ListView listview = (ListView) view.getParent(); int position = listview.getPositionForView(view); - id = listview.getItemIdAtPosition(position); + itemId = listview.getItemIdAtPosition(position); view.setHasTransientState(true); } + if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) { + return; + } + if (mTargetExcludes != null && mTargetExcludes.contains(view)) { + return; + } + if (mTargetTypeExcludes != null && view != null) { + int numTypes = mTargetTypeExcludes.size(); + for (int i = 0; i < numTypes; ++i) { + if (mTargetTypeExcludes.get(i).isInstance(view)) { + return; + } + } + } TransitionValues values = new TransitionValues(); values.view = view; captureStartValues(values); @@ -861,7 +1102,7 @@ public abstract class Transition implements Cloneable { mStartValues.idValues.put((int) id, values); } } else { - mStartValues.itemIdValues.put(id, values); + mStartValues.itemIdValues.put(itemId, values); } } else { if (!isListViewItem) { @@ -870,10 +1111,25 @@ public abstract class Transition implements Cloneable { mEndValues.idValues.put((int) id, values); } } else { - mEndValues.itemIdValues.put(id, values); + mEndValues.itemIdValues.put(itemId, values); } } if (view instanceof ViewGroup) { + // Don't traverse child hierarchy if there are any child-excludes on this view + if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) { + return; + } + if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) { + return; + } + if (mTargetTypeChildExcludes != null && view != null) { + int numTypes = mTargetTypeChildExcludes.size(); + for (int i = 0; i < numTypes; ++i) { + if (mTargetTypeChildExcludes.get(i).isInstance(view)) { + return; + } + } + } ViewGroup parent = (ViewGroup) view; for (int i = 0; i < parent.getChildCount(); ++i) { captureHierarchy(parent.getChildAt(i), start); @@ -1356,4 +1612,51 @@ public abstract class Transition implements Cloneable { this.values = values; } } + + /** + * Utility class for managing typed ArrayLists efficiently. In particular, this + * can be useful for lists that we don't expect to be used often (eg, the exclude + * lists), so we'd like to keep them nulled out by default. This causes the code to + * become tedious, with constant null checks, code to allocate when necessary, + * and code to null out the reference when the list is empty. This class encapsulates + * all of that functionality into simple add()/remove() methods which perform the + * necessary checks, allocation/null-out as appropriate, and return the + * resulting list. + */ + private static class ArrayListManager { + + /** + * Add the specified item to the list, returning the resulting list. + * The returned list can either the be same list passed in or, if that + * list was null, the new list that was created. + * + * Note that the list holds unique items; if the item already exists in the + * list, the list is not modified. + */ + static ArrayList add(ArrayList list, T item) { + if (list == null) { + list = new ArrayList(); + } + if (!list.contains(item)) { + list.add(item); + } + return list; + } + + /** + * Remove the specified item from the list, returning the resulting list. + * The returned list can either the be same list passed in or, if that + * list becomes empty as a result of the remove(), the new list was created. + */ + static ArrayList remove(ArrayList list, T item) { + if (list != null) { + list.remove(item); + if (list.isEmpty()) { + list = null; + } + } + return list; + } + } + } diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index ebedeeb4fa732..eeb6cba7c68e0 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -235,7 +235,7 @@ public class TransitionInflater { int numTargets = targetIds.size(); if (numTargets > 0) { for (int i = 0; i < numTargets; ++i) { - transition.addTargetId(targetIds.get(i)); + transition.addTarget(targetIds.get(i)); } } } diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 1972c2a56ae36..f72b36e3e243b 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -155,8 +155,8 @@ public class TransitionSet extends Transition { } @Override - public TransitionSet addTargetId(int targetId) { - return (TransitionSet) super.addTargetId(targetId); + public TransitionSet addTarget(int targetId) { + return (TransitionSet) super.addTarget(targetId); } @Override @@ -165,8 +165,8 @@ public class TransitionSet extends Transition { } @Override - public TransitionSet removeTargetId(int targetId) { - return (TransitionSet) super.removeTargetId(targetId); + public TransitionSet removeTarget(int targetId) { + return (TransitionSet) super.removeTarget(targetId); } @Override @@ -278,9 +278,11 @@ public class TransitionSet extends Transition { @Override public void captureStartValues(TransitionValues transitionValues) { int targetId = transitionValues.view.getId(); - for (Transition childTransition : mTransitions) { - if (childTransition.isValidTarget(transitionValues.view, targetId)) { - childTransition.captureStartValues(transitionValues); + if (isValidTarget(transitionValues.view, targetId)) { + for (Transition childTransition : mTransitions) { + if (childTransition.isValidTarget(transitionValues.view, targetId)) { + childTransition.captureStartValues(transitionValues); + } } } } @@ -288,9 +290,11 @@ public class TransitionSet extends Transition { @Override public void captureEndValues(TransitionValues transitionValues) { int targetId = transitionValues.view.getId(); - for (Transition childTransition : mTransitions) { - if (childTransition.isValidTarget(transitionValues.view, targetId)) { - childTransition.captureEndValues(transitionValues); + if (isValidTarget(transitionValues.view, targetId)) { + for (Transition childTransition : mTransitions) { + if (childTransition.isValidTarget(transitionValues.view, targetId)) { + childTransition.captureEndValues(transitionValues); + } } } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7ddf2e9fbb23c..4a26a05dd52d5 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4544,7 +4544,7 @@