diff --git a/api/current.txt b/api/current.txt index cd07828a03ca7..c28011c8289ce 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1632,6 +1632,7 @@ package android { field public static final int selectAll = 16908319; // 0x102001f field public static final int selectTextMode = 16908333; // 0x102002d field public static final int selectedIcon = 16908302; // 0x102000e + field public static final int shared_element = 16908355; // 0x1020043 field public static final int shared_element_name = 16908353; // 0x1020041 field public static final int startSelectingText = 16908328; // 0x1020028 field public static final int stopSelectingText = 16908329; // 0x1020029 @@ -3535,8 +3536,9 @@ package android.app { public static class ActivityOptions.ActivityTransitionListener { ctor public ActivityOptions.ActivityTransitionListener(); method public android.util.Pair[] getSharedElementsMapping(); - method public void onCaptureSharedElementEnd(); - method public void onCaptureSharedElementStart(); + method public boolean handleRejectedSharedElements(java.util.List); + method public void onCaptureSharedElementEnd(java.util.List, java.util.List, java.util.List); + method public void onCaptureSharedElementStart(java.util.List, java.util.List, java.util.List); method public void onEnterReady(); method public void onExitTransitionComplete(); method public void onRemoteExitComplete(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 85464c470b18e..a49359f6a6f8e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -649,13 +649,31 @@ public class ActivityOptions { /** * Called when the start state for shared elements is captured on enter. + * + * @param sharedElementNames The names of the shared elements that were accepted into + * the View hierarchy. + * @param sharedElements The shared elements that are part of the View hierarchy. + * @param sharedElementSnapshots The Views containing snap shots of the shared element + * from the launching Window. These elements will not + * be part of the scene, but will be positioned relative + * to the Window decor View. */ - public void onCaptureSharedElementStart() {} + public void onCaptureSharedElementStart(List sharedElementNames, + List sharedElements, List sharedElementSnapshots) {} /** * Called when the end state for shared elements is captured on enter. + * + * @param sharedElementNames The names of the shared elements that were accepted into + * the View hierarchy. + * @param sharedElements The shared elements that are part of the View hierarchy. + * @param sharedElementSnapshots The Views containing snap shots of the shared element + * from the launching Window. These elements will not + * be part of the scene, but will be positioned relative + * to the Window decor View. */ - public void onCaptureSharedElementEnd() {} + public void onCaptureSharedElementEnd(List sharedElementNames, + List sharedElements, List sharedElementSnapshots) {} /** * Called when the enter Transition has been started. @@ -700,6 +718,22 @@ public class ActivityOptions { * call. */ public Pair[] getSharedElementsMapping() { return null; } + + /** + * Returns true if the ActivityTransitionListener will handle removing + * rejected shared elements from the scene. If false is returned, a default + * animation will be used to remove the rejected shared elements from the scene. + * + * @param rejectedSharedElements Views containing visual information of shared elements + * that are not part of the entering scene. These Views + * are positioned relative to the Window decor View. + * @return false if the default animation should be used to remove the + * rejected shared elements from the scene or true if the listener provides + * custom handling. + */ + public boolean handleRejectedSharedElements(List rejectedSharedElements) { + return false; + } } private static class SharedElementMappingListener extends ActivityTransitionListener { diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index d8a356f5a6829..f96c3e3ab7b0f 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -15,6 +15,12 @@ */ package android.app; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -26,8 +32,10 @@ import android.util.ArrayMap; import android.util.Pair; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import android.view.Window; +import android.widget.ImageView; import java.util.ArrayList; import java.util.Collection; @@ -129,6 +137,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { private static final String KEY_WIDTH = "shared_element:width"; private static final String KEY_HEIGHT = "shared_element:height"; private static final String KEY_NAME = "shared_element:name"; + private static final String KEY_BITMAP = "shared_element:bitmap"; /** * Sent by the exiting coordinator (either EnterTransitionCoordinator @@ -305,16 +314,23 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { setSharedElements(); reconcileSharedElements(sharedElementNames); mEnteringViews.removeAll(mSharedElements); - setSharedElementState(state); + final ArrayList accepted = new ArrayList(); + final ArrayList rejected = new ArrayList(); + createSharedElementImages(accepted, rejected, sharedElementNames, state); + setSharedElementState(state, accepted); + handleRejected(rejected); + if (getViewsTransition() != null) { setViewVisibility(mEnteringViews, View.INVISIBLE); } setViewVisibility(mSharedElements, View.VISIBLE); Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(), true); + if (allowOverlappingTransitions()) { onStartEnterTransition(transition, mEnteringViews); } + mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); } @@ -513,35 +529,59 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { } private void reconcileSharedElements(ArrayList sharedElementNames) { - Rect epicenter = null; - for (int i = mTargetSharedNames.size() - 1; i >= 0; i--) { - if (!sharedElementNames.contains(mTargetSharedNames.get(i))) { - mTargetSharedNames.remove(i); - mSharedElements.remove(i); + // keep only those that are in sharedElementNames. + int numSharedElements = sharedElementNames.size(); + int targetIndex = 0; + for (int i = 0; i < numSharedElements; i++) { + String name = sharedElementNames.get(i); + int index = mTargetSharedNames.indexOf(name); + if (index >= 0) { + // Swap the items at the indexes if necessary. + if (index != targetIndex) { + View temp = mSharedElements.get(index); + mSharedElements.set(index, mSharedElements.get(targetIndex)); + mSharedElements.set(targetIndex, temp); + mTargetSharedNames.set(index, mTargetSharedNames.get(targetIndex)); + mTargetSharedNames.set(targetIndex, name); + } + targetIndex++; } } - if (!mSharedElements.isEmpty()) { + for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) { + mSharedElements.remove(i); + mTargetSharedNames.remove(i); + } + Rect epicenter = null; + if (!mTargetSharedNames.isEmpty() + && mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) { epicenter = calcEpicenter(mSharedElements.get(0)); } mEpicenterCallback.setEpicenter(epicenter); } - private void setSharedElementState(Bundle sharedElementState) { + private void setSharedElementState(Bundle sharedElementState, + final ArrayList acceptedOverlayViews) { + final int[] tempLoc = new int[2]; if (sharedElementState != null) { - int[] tempLoc = new int[2]; for (int i = 0; i < mSharedElements.size(); i++) { View sharedElement = mSharedElements.get(i); + View parent = (View) sharedElement.getParent(); + parent.getLocationOnScreen(tempLoc); String name = mTargetSharedNames.get(i); setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + sharedElement.requestLayout(); } } - mListener.onCaptureSharedElementStart(); + mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements, + acceptedOverlayViews); + getDecor().getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mListener.onCaptureSharedElementEnd(); + mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements, + acceptedOverlayViews); return true; } } @@ -555,10 +595,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * @param name The shared element name given from the source Activity. * @param transitionArgs A Bundle containing all placementinformation for named * shared elements in the scene. - * @param tempLoc A temporary int[2] for capturing the current location of views. + * @param parentLoc The x and y coordinates of the parent's screen position. */ private static void setSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { + int[] parentLoc) { Bundle sharedElementBundle = transitionArgs.getBundle(name); if (sharedElementBundle == null) { return; @@ -576,15 +616,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); view.measure(widthSpec, heightSpec); - ViewGroup parent = (ViewGroup) view.getParent(); - parent.getLocationOnScreen(tempLoc); - int left = x - tempLoc[0]; - int top = y - tempLoc[1]; + int left = x - parentLoc[0]; + int top = y - parentLoc[1]; int right = left + width; int bottom = top + height; view.layout(left, top, right, bottom); - - view.requestLayout(); } /** @@ -615,6 +651,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); + transitionArgs.putBundle(name, sharedElementBundle); } @@ -723,6 +764,61 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return transition; } + private void handleRejected(final ArrayList rejected) { + int numRejected = rejected.size(); + if (numRejected == 0) { + return; + } + boolean rejectionHandled = mListener.handleRejectedSharedElements(rejected); + if (rejectionHandled) { + return; + } + + ViewGroupOverlay overlay = getDecor().getOverlay(); + ObjectAnimator animator = null; + for (int i = 0; i < numRejected; i++) { + View view = rejected.get(i); + overlay.add(view); + animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0); + animator.start(); + } + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ViewGroupOverlay overlay = getDecor().getOverlay(); + for (int i = rejected.size() - 1; i >= 0; i--) { + overlay.remove(rejected.get(i)); + } + } + }); + } + + private void createSharedElementImages(ArrayList accepted, ArrayList rejected, + ArrayList sharedElementNames, Bundle state) { + int numSharedElements = sharedElementNames.size(); + Context context = getWindow().getContext(); + int[] parentLoc = new int[2]; + getDecor().getLocationOnScreen(parentLoc); + for (int i = 0; i < numSharedElements; i++) { + String name = sharedElementNames.get(i); + Bundle sharedElementBundle = state.getBundle(name); + if (sharedElementBundle != null) { + Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); + ImageView imageView = new ImageView(context); + imageView.setId(com.android.internal.R.id.shared_element); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setImageBitmap(bitmap); + imageView.setSharedElementName(name); + setSharedElementState(imageView, name, state, parentLoc); + if (mTargetSharedNames.contains(name)) { + accepted.add(imageView); + } else { + rejected.add(imageView); + } + } + } + } + private static class FixedEpicenterCallback extends Transition.EpicenterCallback { private Rect mEpicenter; diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index aa097e095b8d4..0798529803b24 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -264,7 +264,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator mExitTransitionComplete = true; exitAfterSharedElementTransition(); super.onExitTransitionEnd(); - clearConnections(); } @Override @@ -287,6 +286,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator mActivity.overridePendingTransition(0, 0); } notifyExitTransitionComplete(); + clearConnections(); } } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e35239fa43fd4..0a3ca2a1bccea 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -462,24 +462,24 @@ transitions between different window content. --> - - - -