From 7b75062fb05a9981faf3c0c9f840dbdb9e97e7f7 Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 13 May 2014 18:08:38 -0700 Subject: [PATCH] Allow changing order of matching Views in Transitions. Bug 14899804 Change-Id: I13c080141fce6be8430d540e3c42d40a445bb224 --- api/current.txt | 6 + core/java/android/transition/Transition.java | 128 ++++++++++++++++-- .../transition/TransitionInflater.java | 37 +++++ core/res/res/values/attrs.xml | 9 ++ core/res/res/values/public.xml | 1 + 5 files changed, 167 insertions(+), 14 deletions(-) diff --git a/api/current.txt b/api/current.txt index 36aa4a013ce5d..b1e093ed83eaa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -798,6 +798,7 @@ package android { field public static final int manageSpaceActivity = 16842756; // 0x1010004 field public static final int mapViewStyle = 16842890; // 0x101008a field public static final int marqueeRepeatLimit = 16843293; // 0x101021d + field public static final int matchOrder = 16843861; // 0x1010455 field public static final int max = 16843062; // 0x1010136 field public static final int maxDate = 16843584; // 0x1010340 field public static final int maxEms = 16843095; // 0x1010157 @@ -29092,8 +29093,13 @@ package android.transition { method public android.transition.Transition setDuration(long); method public void setEpicenterCallback(android.transition.Transition.EpicenterCallback); method public android.transition.Transition setInterpolator(android.animation.TimeInterpolator); + method public void setMatchOrder(int...); method public void setPropagation(android.transition.TransitionPropagation); method public android.transition.Transition setStartDelay(long); + field public static final int MATCH_ID = 3; // 0x3 + field public static final int MATCH_INSTANCE = 1; // 0x1 + field public static final int MATCH_ITEM_ID = 4; // 0x4 + field public static final int MATCH_VIEW_NAME = 2; // 0x2 } public static abstract class Transition.EpicenterCallback { diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 5a432dcc473b5..9a70099281863 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -24,7 +24,6 @@ import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; -import android.util.SparseIntArray; import android.util.SparseLongArray; import android.view.SurfaceView; import android.view.TextureView; @@ -108,6 +107,40 @@ public abstract class Transition implements Cloneable { private static final String LOG_TAG = "Transition"; static final boolean DBG = false; + /** + * With {@link #setMatchOrder(int...)}, chooses to match by View instance. + */ + public static final int MATCH_INSTANCE = 0x1; + private static final int MATCH_FIRST = MATCH_INSTANCE; + + /** + * With {@link #setMatchOrder(int...)}, chooses to match by + * {@link android.view.View#getViewName()}. Null names will not be matched. + */ + public static final int MATCH_VIEW_NAME = 0x2; + + /** + * With {@link #setMatchOrder(int...)}, chooses to match by + * {@link android.view.View#getId()}. Negative IDs will not be matched. + */ + public static final int MATCH_ID = 0x3; + + /** + * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter} + * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match + * will be made for items. + */ + public static final int MATCH_ITEM_ID = 0x4; + + private static final int MATCH_LAST = MATCH_ITEM_ID; + + private static final int[] DEFAULT_MATCH_ORDER = { + MATCH_VIEW_NAME, + MATCH_INSTANCE, + MATCH_ID, + MATCH_ITEM_ID, + }; + private String mName = getClass().getName(); long mStartDelay = -1; @@ -127,6 +160,7 @@ public abstract class Transition implements Cloneable { private TransitionValuesMaps mStartValues = new TransitionValuesMaps(); private TransitionValuesMaps mEndValues = new TransitionValuesMaps(); TransitionSet mParent = null; + private int[] mMatchOrder = DEFAULT_MATCH_ORDER; // Per-animator information used for later canceling when future transitions overlap private static ThreadLocal> sRunningAnimators = @@ -337,6 +371,53 @@ public abstract class Transition implements Cloneable { return null; } + /** + * Sets the order in which Transition matches View start and end values. + *

+ * The default behavior is to match first by {@link android.view.View#getViewName()}, + * then by View instance, then by {@link android.view.View#getId()} and finally + * by its item ID if it is in a direct child of ListView. The caller can + * choose to have only some or all of the values of {@link #MATCH_INSTANCE}, + * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only + * the match algorithms supplied will be used to determine whether Views are the + * the same in both the start and end Scene. Views that do not match will be considered + * as entering or leaving the Scene. + *

+ * @param matches A list of zero or more of {@link #MATCH_INSTANCE}, + * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. + * If none are provided, then the default match order will be set. + */ + public void setMatchOrder(int... matches) { + if (matches == null || matches.length == 0) { + mMatchOrder = DEFAULT_MATCH_ORDER; + } else { + for (int i = 0; i < matches.length; i++) { + int match = matches[i]; + if (!isValidMatch(match)) { + throw new IllegalArgumentException("matches contains invalid value"); + } + if (alreadyContains(matches, i)) { + throw new IllegalArgumentException("matches contains a duplicate value"); + } + } + mMatchOrder = matches.clone(); + } + } + + private static boolean isValidMatch(int match) { + return (match >= MATCH_FIRST && match <= MATCH_LAST); + } + + private static boolean alreadyContains(int[] array, int searchIndex) { + int value = array[searchIndex]; + for (int i = 0; i < searchIndex; i++) { + if (array[i] == value) { + return true; + } + } + return false; + } + /** * Match start/end values by View instance. Adds matched values to startValuesList * and endValuesList and removes them from unmatchedStart and unmatchedEnd. @@ -464,6 +545,37 @@ public abstract class Transition implements Cloneable { } } + private void matchStartAndEnd(TransitionValuesMaps startValues, + TransitionValuesMaps endValues, + ArrayList startValuesList, + ArrayList endValuesList) { + ArrayMap unmatchedStart = + new ArrayMap(startValues.viewValues); + ArrayMap unmatchedEnd = + new ArrayMap(endValues.viewValues); + + for (int i = 0; i < mMatchOrder.length; i++) { + switch (mMatchOrder[i]) { + case MATCH_INSTANCE: + matchInstances(startValuesList, endValuesList, unmatchedStart, unmatchedEnd); + break; + case MATCH_VIEW_NAME: + matchNames(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, + startValues.nameValues, endValues.nameValues); + break; + case MATCH_ID: + matchIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, + startValues.idValues, endValues.idValues); + break; + case MATCH_ITEM_ID: + matchItemIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, + startValues.itemIdValues, endValues.itemIdValues); + break; + } + } + addUnmatched(startValuesList, endValuesList, unmatchedStart, unmatchedEnd); + } + /** * This method, essentially a wrapper around all calls to createAnimator for all * possible target views, is called with the entire set of start/end @@ -480,21 +592,9 @@ public abstract class Transition implements Cloneable { if (DBG) { Log.d(LOG_TAG, "createAnimators() for " + this); } - ArrayMap unmatchedStart = - new ArrayMap(startValues.viewValues); - ArrayMap unmatchedEnd = - new ArrayMap(endValues.viewValues); - ArrayList startValuesList = new ArrayList(); ArrayList endValuesList = new ArrayList(); - matchNames(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, - startValues.nameValues, endValues.nameValues); - matchInstances(startValuesList, endValuesList, unmatchedStart, unmatchedEnd); - matchIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, - startValues.idValues, endValues.idValues); - matchItemIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, - startValues.itemIdValues, endValues.itemIdValues); - addUnmatched(startValuesList, endValuesList, unmatchedStart, unmatchedEnd); + matchStartAndEnd(startValues, endValues, startValuesList, endValuesList); ArrayMap runningAnimators = getRunningAnimators(); long minStartDelay = Long.MAX_VALUE; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 04f8672f83aab..f4b562f58808d 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -30,6 +30,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.StringTokenizer; /** * This class inflates scenes and transitions from resource files. @@ -40,6 +41,10 @@ import java.util.ArrayList; * and {@link android.R.styleable#TransitionManager}. */ public class TransitionInflater { + private static final String MATCH_INSTANCE = "instance"; + private static final String MATCH_VIEW_NAME = "viewName"; + private static final String MATCH_ID = "id"; + private static final String MATCH_ITEM_ID = "itemId"; private Context mContext; @@ -266,6 +271,33 @@ public class TransitionInflater { } } + private int[] parseMatchOrder(String matchOrderString) { + StringTokenizer st = new StringTokenizer(matchOrderString, ","); + int matches[] = new int[st.countTokens()]; + int index = 0; + while (st.hasMoreTokens()) { + String token = st.nextToken().trim(); + if (MATCH_ID.equalsIgnoreCase(token)) { + matches[index] = Transition.MATCH_ID; + } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) { + matches[index] = Transition.MATCH_INSTANCE; + } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) { + matches[index] = Transition.MATCH_VIEW_NAME; + } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) { + matches[index] = Transition.MATCH_ITEM_ID; + } else if (token.isEmpty()) { + int[] smallerMatches = new int[matches.length - 1]; + System.arraycopy(matches, 0, smallerMatches, 0, index); + matches = smallerMatches; + index--; + } else { + throw new RuntimeException("Unknown match type in matchOrder: '" + token + "'"); + } + index++; + } + return matches; + } + private Transition loadTransition(Transition transition, AttributeSet attrs) throws Resources.NotFoundException { @@ -284,6 +316,11 @@ public class TransitionInflater { if (resID > 0) { transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID)); } + String matchOrder = + a.getString(com.android.internal.R.styleable.Transition_matchOrder); + if (matchOrder != null) { + transition.setMatchOrder(parseMatchOrder(matchOrder)); + } a.recycle(); return transition; } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 03435c5361df1..ad66d47e1777a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4973,6 +4973,15 @@ + +