From 333b8093eaab4b55d91a7a0b3b1484424f8ac975 Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 21 Oct 2014 15:09:11 -0700 Subject: [PATCH] Fixed: Nested shared elements now transition separately. Bug 18073470 Shared element ordering was based on the key ordering in an ArrayMap. This is normally fine, but when shared elements are nested, the child's layout can be overwritten by the parent's if it is laid out first. The only way to force the ordering of shared element layout was the change the transition name. To fix this, shared elements are now laid out parent first, then child. On return, nested shared elements were not transitioning to their final destination properly because the matrix used to calculate their position was not correct. This change recalculates the parent matrices when appropriate. Change-Id: I62333183cf03519e525587e4ea31fcf14bb83cdc --- .../app/ActivityTransitionCoordinator.java | 116 +++++++++++++++--- .../app/EnterTransitionCoordinator.java | 30 ----- 2 files changed, 102 insertions(+), 44 deletions(-) diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index d611058720612..ef7ef487dd34e 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -206,6 +206,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { private ArrayList mGhostViewListeners = new ArrayList(); private ArrayMap mOriginalAlphas = new ArrayMap(); + final private ArrayList mRootSharedElements = new ArrayList(); + private ArrayList mSharedElementParentMatrices; public ActivityTransitionCoordinator(Window window, ArrayList allSharedElementNames, @@ -222,15 +224,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (mListener != null) { mListener.onMapSharedElements(mAllSharedElementNames, sharedElements); } - int numSharedElements = sharedElements.size(); - for (int i = 0; i < numSharedElements; i++) { - View sharedElement = sharedElements.valueAt(i); - String name = sharedElements.keyAt(i); - if (sharedElement != null && sharedElement.isAttachedToWindow() && name != null) { - mSharedElements.add(sharedElement); - mSharedElementNames.add(name); - } - } + setSharedElements(sharedElements); if (getViewsTransition() != null && mTransitioningViews != null) { ViewGroup decorView = getDecor(); if (decorView != null) { @@ -241,6 +235,58 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { setEpicenter(); } + /** + * Iterates over the shared elements and adds them to the members in order. + * Shared elements that are nested in other shared elements are placed after the + * elements that they are nested in. This means that layout ordering can be done + * from first to last. + * + * @param sharedElements The map of transition names to shared elements to set into + * the member fields. + */ + private void setSharedElements(ArrayMap sharedElements) { + boolean isFirstRun = true; + while (!sharedElements.isEmpty()) { + final int numSharedElements = sharedElements.size(); + for (int i = numSharedElements - 1; i >= 0; i--) { + final View view = sharedElements.valueAt(i); + final String name = sharedElements.keyAt(i); + if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) { + sharedElements.removeAt(i); + } else { + if (!isNested(view, sharedElements)) { + mSharedElementNames.add(name); + mSharedElements.add(view); + sharedElements.removeAt(i); + if (isFirstRun) { + // We need to keep track which shared elements are roots + // and which are nested. + mRootSharedElements.add(view); + } + } + } + } + isFirstRun = false; + } + } + + /** + * Returns true when view is nested in any of the values of sharedElements. + */ + private static boolean isNested(View view, ArrayMap sharedElements) { + ViewParent parent = view.getParent(); + boolean isNested = false; + while (parent instanceof View) { + View parentView = (View) parent; + if (sharedElements.containsValue(parentView)) { + isNested = true; + break; + } + parent = parentView.getParent(); + } + return isNested; + } + protected void stripOffscreenViews() { if (mTransitioningViews == null) { return; @@ -456,11 +502,50 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { view.layout(x, y, x + width, y + height); } - protected void getSharedElementParentMatrix(View view, Matrix matrix) { - // Find the location in the view's parent - ViewGroup parent = (ViewGroup) view.getParent(); - matrix.reset(); - parent.transformMatrixToLocal(matrix); + private void setSharedElementMatrices() { + int numSharedElements = mSharedElements.size(); + if (numSharedElements > 0) { + mSharedElementParentMatrices = new ArrayList(numSharedElements); + } + for (int i = 0; i < numSharedElements; i++) { + View view = mSharedElements.get(i); + + // Find the location in the view's parent + ViewGroup parent = (ViewGroup) view.getParent(); + Matrix matrix = new Matrix(); + parent.transformMatrixToLocal(matrix); + + mSharedElementParentMatrices.add(matrix); + } + } + + private void getSharedElementParentMatrix(View view, Matrix matrix) { + final boolean isNestedInOtherSharedElement = !mRootSharedElements.contains(view); + final boolean useParentMatrix; + if (isNestedInOtherSharedElement) { + useParentMatrix = true; + } else { + final int index = mSharedElementParentMatrices == null ? -1 + : mSharedElements.indexOf(view); + if (index < 0) { + useParentMatrix = true; + } else { + // The indices of mSharedElementParentMatrices matches the + // mSharedElement matrices. + Matrix parentMatrix = mSharedElementParentMatrices.get(index); + matrix.set(parentMatrix); + useParentMatrix = false; + } + } + if (useParentMatrix) { + matrix.reset(); + ViewParent viewParent = view.getParent(); + if (viewParent instanceof ViewGroup) { + // Find the location in the view's parent + ViewGroup parent = (ViewGroup) viewParent; + parent.transformMatrixToLocal(matrix); + } + } } protected ArrayList setSharedElementState( @@ -614,6 +699,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { mResultReceiver = null; mPendingTransition = null; mListener = null; + mRootSharedElements.clear(); + mSharedElementParentMatrices = null; } protected long getFadeDuration() { @@ -714,6 +801,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (!mWindow.getSharedElementsUseOverlay()) { return; } + setSharedElementMatrices(); int numSharedElements = mSharedElements.size(); ViewGroup decor = getDecor(); if (decor != null) { diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index add67f2ec8a52..e785215559f7f 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -57,7 +57,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsViewsTransitionStarted; private boolean mIsViewsTransitionComplete; private boolean mIsSharedElementTransitionComplete; - private ArrayList mSharedElementParentMatrices; private Transition mEnterViewsTransition; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, @@ -122,7 +121,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (mIsReturning) { sendSharedElementDestination(); } else { - setSharedElementMatrices(); moveSharedElementsToOverlay(); } if (mSharedElementsBundle != null) { @@ -194,7 +192,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } if (allReady) { Bundle state = captureSharedElementState(); - setSharedElementMatrices(); moveSharedElementsToOverlay(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } else if (decorView != null) { @@ -205,7 +202,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { decorView.getViewTreeObserver().removeOnPreDrawListener(this); if (mResultReceiver != null) { Bundle state = captureSharedElementState(); - setSharedElementMatrices(); moveSharedElementsToOverlay(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } @@ -634,30 +630,4 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { }); } - private void setSharedElementMatrices() { - int numSharedElements = mSharedElements.size(); - if (numSharedElements > 0) { - mSharedElementParentMatrices = new ArrayList(numSharedElements); - } - for (int i = 0; i < numSharedElements; i++) { - View view = mSharedElements.get(i); - - // Find the location in the view's parent - ViewGroup parent = (ViewGroup) view.getParent(); - Matrix matrix = new Matrix(); - parent.transformMatrixToLocal(matrix); - - mSharedElementParentMatrices.add(matrix); - } - } - - @Override - protected void getSharedElementParentMatrix(View view, Matrix matrix) { - int index = mSharedElementParentMatrices == null ? -1 : mSharedElements.indexOf(view); - if (index < 0) { - super.getSharedElementParentMatrix(view, matrix); - } else { - matrix.set(mSharedElementParentMatrices.get(index)); - } - } }