From 31a217290cf376d0573fc36e21c8940987485019 Mon Sep 17 00:00:00 2001 From: George Mount Date: Mon, 24 Mar 2014 17:44:36 -0700 Subject: [PATCH] Split Activity Transitions out of PhoneWindow. Bug 13622834 Made it possible to use shared elements without making Views invisible. Change-Id: I1e85c6bc19e634a9af225ad7f0309b4f003ea462 --- api/current.txt | 48 +- core/java/android/app/ActionBar.java | 4 - core/java/android/app/Activity.java | 106 +-- core/java/android/app/ActivityOptions.java | 351 +++----- .../app/ActivityTransitionCoordinator.java | 736 ++++++++++++++++ .../app/EnterTransitionCoordinator.java | 292 ++++++ .../app/ExitTransitionCoordinator.java | 171 ++++ core/java/android/transition/Transition.java | 2 +- .../transition/TransitionInflater.java | 12 +- .../android/transition/TransitionManager.java | 47 - core/java/android/transition/Visibility.java | 4 +- core/java/android/view/ViewGroup.java | 4 +- core/java/android/view/Window.java | 174 ++-- .../internal/app/ToolbarActionBar.java | 5 - .../internal/app/WindowDecorActionBar.java | 4 - core/res/res/transition/no_transition.xml | 16 + core/res/res/values/attrs.xml | 75 +- core/res/res/values/public.xml | 7 + core/res/res/values/symbols.xml | 1 + .../internal/policy/impl/PhoneWindow.java | 832 +++--------------- 20 files changed, 1745 insertions(+), 1146 deletions(-) create mode 100644 core/java/android/app/ActivityTransitionCoordinator.java create mode 100644 core/java/android/app/EnterTransitionCoordinator.java create mode 100644 core/java/android/app/ExitTransitionCoordinator.java create mode 100644 core/res/res/transition/no_transition.xml diff --git a/api/current.txt b/api/current.txt index 7b09a85df7b9e..894dabf8e2350 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1284,6 +1284,8 @@ package android { field public static final int windowActionBar = 16843469; // 0x10102cd field public static final int windowActionBarOverlay = 16843492; // 0x10102e4 field public static final int windowActionModeOverlay = 16843485; // 0x10102dd + field public static final int windowAllowEnterTransitionOverlap = 16843848; // 0x1010448 + field public static final int windowAllowExitTransitionOverlap = 16843847; // 0x1010447 field public static final int windowAnimationStyle = 16842926; // 0x10100ae field public static final int windowBackground = 16842836; // 0x1010054 field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b @@ -1293,7 +1295,9 @@ package android { field public static final int windowDisablePreview = 16843298; // 0x1010222 field public static final int windowEnableSplitTouch = 16843543; // 0x1010317 field public static final int windowEnterAnimation = 16842932; // 0x10100b4 + field public static final int windowEnterTransition = 16843843; // 0x1010443 field public static final int windowExitAnimation = 16842933; // 0x10100b5 + field public static final int windowExitTransition = 16843844; // 0x1010444 field public static final int windowFrame = 16842837; // 0x1010055 field public static final int windowFullscreen = 16843277; // 0x101020d field public static final int windowHideAnimation = 16842935; // 0x10100b7 @@ -1304,6 +1308,8 @@ package android { field public static final int windowNoDisplay = 16843294; // 0x101021e field public static final int windowNoTitle = 16842838; // 0x1010056 field public static final int windowOverscan = 16843727; // 0x10103cf + field public static final int windowSharedElementEnterTransition = 16843845; // 0x1010445 + field public static final int windowSharedElementExitTransition = 16843846; // 0x1010446 field public static final int windowShowAnimation = 16842934; // 0x10100b6 field public static final int windowShowWallpaper = 16843410; // 0x1010292 field public static final int windowSoftInputMode = 16843307; // 0x101022b @@ -2454,6 +2460,11 @@ package android { field public static final int l_resource_pad9 = 16974328; // 0x10301f8 } + public static final class R.transition { + ctor public R.transition(); + field public static final int no_transition = 17760256; // 0x10f0000 + } + public static final class R.xml { ctor public R.xml(); } @@ -3209,8 +3220,6 @@ package android.app { method public void onAttachFragment(android.app.Fragment); method public void onAttachedToWindow(); method public void onBackPressed(); - method public void onCaptureSharedElementEnd(); - method public void onCaptureSharedElementStart(android.transition.Transition); method protected void onChildTitleChanged(android.app.Activity, java.lang.CharSequence); method public void onConfigurationChanged(android.content.res.Configuration); method public void onContentChanged(); @@ -3283,6 +3292,7 @@ package android.app { method public final void runOnUiThread(java.lang.Runnable); method public void setActionBar(android.widget.Toolbar); method public void setActivityLabelAndIcon(java.lang.CharSequence, android.graphics.Bitmap); + method public void setActivityTransitionListener(android.app.ActivityOptions.ActivityTransitionListener); method public void setContentTransitionManager(android.transition.TransitionManager); method public void setContentView(int); method public void setContentView(android.view.View); @@ -3500,13 +3510,27 @@ package android.app { public class ActivityOptions { method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int); - method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.View, java.lang.String); - method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.util.Pair...); + method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.Window, android.view.View, java.lang.String); + method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.Window, android.app.ActivityOptions.ActivityTransitionListener); method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); } + 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 void onEnterReady(); + method public void onExitTransitionComplete(); + method public void onRemoteExitComplete(); + method public void onSharedElementExitTransitionComplete(); + method public void onSharedElementTransferred(java.util.List, java.util.List); + method public void onStartEnterTransition(java.util.List, java.util.List); + method public void onStartExitTransition(java.util.List, java.util.List); + } + public class AlarmManager { method public void cancel(android.app.PendingIntent); method public void set(int, long, android.app.PendingIntent); @@ -27750,7 +27774,6 @@ package android.transition { method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition); method public static void go(android.transition.Scene); method public static void go(android.transition.Scene, android.transition.Transition); - method public void setExitTransition(android.transition.Scene, android.transition.Transition); method public void setTransition(android.transition.Scene, android.transition.Transition); method public void setTransition(android.transition.Scene, android.transition.Scene, android.transition.Transition); method public void transitionTo(android.transition.Scene); @@ -30787,6 +30810,8 @@ package android.view { method public abstract void closeAllPanels(); method public abstract void closePanel(int); method public android.view.View findViewById(int); + method public boolean getAllowEnterTransitionOverlap(); + method public boolean getAllowExitTransitionOverlap(); method public final android.view.WindowManager.LayoutParams getAttributes(); method public final android.view.Window.Callback getCallback(); method public final android.view.Window getContainer(); @@ -30794,10 +30819,14 @@ package android.view { method public final android.content.Context getContext(); method public abstract android.view.View getCurrentFocus(); method public abstract android.view.View getDecorView(); + method public android.transition.Transition getEnterTransition(); + method public android.transition.Transition getExitTransition(); method protected final int getFeatures(); method protected final int getForcedWindowFlags(); method public abstract android.view.LayoutInflater getLayoutInflater(); method protected final int getLocalFeatures(); + method public android.transition.Transition getSharedElementEnterTransition(); + method public android.transition.Transition getSharedElementExitTransition(); method public android.transition.TransitionManager getTransitionManager(); method public abstract int getVolumeControlStream(); method public android.view.WindowManager getWindowManager(); @@ -30811,7 +30840,6 @@ package android.view { method public abstract boolean isFloating(); method public abstract boolean isShortcutKey(int, android.view.KeyEvent); method public final void makeActive(); - method public void mapTransitionTargets(java.util.Map); method protected abstract void onActive(); method public abstract void onConfigurationChanged(android.content.res.Configuration); method public abstract void openPanel(int, android.view.KeyEvent); @@ -30822,8 +30850,8 @@ package android.view { method public boolean requestFeature(int); method public abstract void restoreHierarchyState(android.os.Bundle); method public abstract android.os.Bundle saveHierarchyState(); - method public void setAllowOverlappingEnterTransition(boolean); - method public void setAllowOverlappingExitTransition(boolean); + method public void setAllowEnterTransitionOverlap(boolean); + method public void setAllowExitTransitionOverlap(boolean); method public void setAttributes(android.view.WindowManager.LayoutParams); method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setBackgroundDrawableResource(int); @@ -30836,6 +30864,8 @@ package android.view { method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); method protected void setDefaultWindowFormat(int); method public void setDimAmount(float); + method public void setEnterTransition(android.transition.Transition); + method public void setExitTransition(android.transition.Transition); method public abstract void setFeatureDrawable(int, android.graphics.drawable.Drawable); method public abstract void setFeatureDrawableAlpha(int, int); method public abstract void setFeatureDrawableResource(int, int); @@ -30848,6 +30878,8 @@ package android.view { method public void setLayout(int, int); method public void setLocalFocus(boolean, boolean); method public void setLogo(int); + method public void setSharedElementEnterTransition(android.transition.Transition); + method public void setSharedElementExitTransition(android.transition.Transition); method public void setSoftInputMode(int); method public abstract void setTitle(java.lang.CharSequence); method public abstract deprecated void setTitleColor(int); diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 9818c333bcec5..04f62e3c45e71 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -948,10 +948,6 @@ public abstract class ActionBar { public void dispatchMenuVisibilityChanged(boolean visible) { } - /** @hide */ - public void captureSharedElements(Map sharedElements) { - } - /** @hide */ public ActionMode startActionMode(ActionMode.Callback callback) { return null; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b18eb983fcc25..a5a06e32882b8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -18,7 +18,6 @@ package android.app; import android.annotation.NonNull; import android.transition.Scene; -import android.transition.Transition; import android.transition.TransitionManager; import android.util.ArrayMap; import android.util.SuperNotCalledException; @@ -773,6 +772,8 @@ public class Activity extends ContextThemeWrapper private Thread mUiThread; final Handler mHandler = new Handler(); + private ActivityOptions mCalledActivityOptions; + private EnterTransitionCoordinator mEnterTransitionCoordinator; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -1026,6 +1027,9 @@ public class Activity extends ContextThemeWrapper mTitleReady = true; onTitleChanged(getTitle(), getTitleColor()); } + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.readyToEnter(); + } mCalled = true; } @@ -1106,6 +1110,7 @@ public class Activity extends ContextThemeWrapper protected void onResume() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); getApplication().dispatchActivityResumed(this); + mCalledActivityOptions = null; mCalled = true; } @@ -1398,8 +1403,9 @@ public class Activity extends ContextThemeWrapper protected void onStop() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); - if (mWindow != null) { - mWindow.restoreViewVisibilityAfterTransitionToCallee(); + if (mCalledActivityOptions != null) { + mCalledActivityOptions.dispatchActivityStopped(); + mCalledActivityOptions = null; } getApplication().dispatchActivityStopped(this); mTranslucentCallback = null; @@ -3484,7 +3490,7 @@ public class Activity extends ContextThemeWrapper public void startActivityForResult(Intent intent, int requestCode) { Bundle options = null; if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { - options = ActivityOptions.makeSceneTransitionAnimation().toBundle(); + options = ActivityOptions.makeSceneTransitionAnimation(mWindow, null).toBundle(); } startActivityForResult(intent, requestCode, options); } @@ -3526,14 +3532,8 @@ public class Activity extends ContextThemeWrapper public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (options != null) { ActivityOptions activityOptions = new ActivityOptions(options); - if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { - if (mActionBar != null) { - ArrayMap sharedElementMap = new ArrayMap(); - mActionBar.captureSharedElements(sharedElementMap); - activityOptions.addSharedElements(sharedElementMap); - } - options = mWindow.startExitTransitionToCallee(options); - } + activityOptions.dispatchStartExit(); + mCalledActivityOptions = activityOptions; } if (mParent == null) { Instrumentation.ActivityResult ar = @@ -4391,16 +4391,15 @@ public class Activity extends ContextThemeWrapper * to reverse its exit Transition. When the exit Transition completes, * {@link #finish()} is called. If no entry Transition was used, finish() is called * immediately and the Activity exit Transition is run. - * @see android.view.Window#setTriggerEarlySceneTransition(boolean, boolean) - * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String) + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener) */ public void finishWithTransition() { - mWindow.startExitTransitionToCaller(new Runnable() { - @Override - public void run() { - finish(); - } - }); + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.startExit(); + } else { + finish(); + } } /** @@ -5346,6 +5345,21 @@ public class Activity extends ContextThemeWrapper } } + /** + * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener)} was used to start an Activity, + * the Window will be triggered to enter with a Transition. listener allows + * The Activity to listen to events of the entering transition and control the mapping of + * shared elements. This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * @param listener Used to listen to events in the entering transition. + */ + public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) { + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.setActivityTransitionListener(listener); + } + } + // ------------------ Internal API ------------------ final void setParent(Activity parent) { @@ -5413,34 +5427,12 @@ public class Activity extends ContextThemeWrapper } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; - Window.SceneTransitionListener sceneTransitionListener - = new Window.SceneTransitionListener() { - @Override - public void nullPendingTransition() { - overridePendingTransition(0, 0); + if (options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this); } - - @Override - public void convertFromTranslucent() { - Activity.this.convertFromTranslucent(); - } - - @Override - public void convertToTranslucent() { - Activity.this.convertToTranslucent(null); - } - - @Override - public void sharedElementStart(Transition transition) { - Activity.this.onCaptureSharedElementStart(transition); - } - - @Override - public void sharedElementEnd() { - Activity.this.onCaptureSharedElementEnd(); - } - }; - mWindow.setTransitionOptions(options, sceneTransitionListener); + } } /** @hide */ @@ -5627,26 +5619,6 @@ public class Activity extends ContextThemeWrapper } } - /** - * Called when setting up Activity Scene transitions when the start state for shared - * elements has been captured. Override this method to modify the start position of shared - * elements for the entry Transition. - * - * @param transition The Transition being used to change - * bounds of shared elements in the source Activity to - * the bounds defined by the entering Scene. - */ - public void onCaptureSharedElementStart(Transition transition) { - } - - /** - * Called when setting up Activity Scene transitions when the final state for - * shared elements state has been captured. Override this method to modify the destination - * position of shared elements for the entry Transition. - */ - public void onCaptureSharedElementEnd() { - } - /** * @hide */ diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 438458083a491..85464c470b18e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,16 +20,16 @@ import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.transition.Transition; -import android.util.Log; +import android.util.ArrayMap; import android.util.Pair; import android.view.View; +import android.view.Window; -import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -108,23 +108,6 @@ public class ActivityOptions { private static final String KEY_TRANSITION_COMPLETE_LISTENER = "android:transitionCompleteListener"; - /** - * For Activity transitions, the called Activity's listener to receive calls - * when transitions complete. - */ - private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener"; - - /** - * The names of shared elements that are transitioned to the started Activity. - * This is also the name of shared elements that the started Activity accepted. - */ - private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names"; - - /** - * The shared elements names of the views in the calling Activity. - */ - private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names"; - /** @hide */ public static final int ANIM_NONE = 0; /** @hide */ @@ -138,11 +121,6 @@ public class ActivityOptions { /** @hide */ public static final int ANIM_SCENE_TRANSITION = 5; - private static final int MSG_SET_LISTENER = 100; - private static final int MSG_HIDE_SHARED_ELEMENTS = 101; - private static final int MSG_PREPARE_RESTORE = 102; - private static final int MSG_RESTORE = 103; - private String mPackageName; private int mAnimationType = ANIM_NONE; private int mCustomEnterResId; @@ -153,9 +131,7 @@ public class ActivityOptions { private int mStartWidth; private int mStartHeight; private IRemoteCallback mAnimationStartedListener; - private ResultReceiver mTransitionCompleteListener; - private ArrayList mSharedElementNames; - private ArrayList mLocalElementNames; + private ResultReceiver mExitReceiver; /** * Create an ActivityOptions specifying a custom animation to run when @@ -231,12 +207,6 @@ public class ActivityOptions { void onAnimationStarted(); } - /** @hide */ - public interface ActivityTransitionTarget { - void sharedElementTransitionComplete(Bundle transitionArgs); - void exitTransitionComplete(); - } - /** * Create an ActivityOptions specifying an animation where the new * activity is scaled from a small originating area of the screen to @@ -357,49 +327,53 @@ public class ActivityOptions { /** * Create an ActivityOptions to transition between Activities using cross-Activity scene * animations. This method carries the position of one shared element to the started Activity. + * The position of sharedElement will be used as the epicenter for the + * exit Transition. The position of the shared element in the launched Activity will be the + * epicenter of its entering Transition. * *

This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be * enabled on the calling Activity to cause an exit transition. The same must be in * the called Activity to get an entering transition.

+ * @param window The window containing shared elements. * @param sharedElement The View to transition to the started Activity. sharedElement must * have a non-null sharedElementName. * @param sharedElementName The shared element name as used in the target Activity. This may * be null if it has the same name as sharedElement. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. + * @see android.transition.Transition#setEpicenterCallback( + * android.transition.Transition.EpicenterCallback) */ - public static ActivityOptions makeSceneTransitionAnimation(View sharedElement, - String sharedElementName) { - return makeSceneTransitionAnimation( - new Pair(sharedElement, sharedElementName)); + public static ActivityOptions makeSceneTransitionAnimation(Window window, + View sharedElement, String sharedElementName) { + return makeSceneTransitionAnimation(window, + new SharedElementMappingListener(sharedElement, sharedElementName)); } /** * Create an ActivityOptions to transition between Activities using cross-Activity scene * animations. This method carries the position of multiple shared elements to the started - * Activity. + * Activity. The position of the first element in the value returned from + * {@link android.app.ActivityOptions.ActivityTransitionListener#getSharedElementsMapping()} + * will be used as the epicenter for the exit Transition. The position of the associated + * shared element in the launched Activity will be the epicenter of its entering Transition. * *

This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be * enabled on the calling Activity to cause an exit transition. The same must be in * the called Activity to get an entering transition.

- * @param sharedElements The View to transition to the started Activity along with the - * shared element name as used in the started Activity. The view - * must have a non-null sharedElementName. + * @param window The window containing shared elements. + * @param listener The listener to use to monitor activity transition events. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. + * @see android.transition.Transition#setEpicenterCallback( + * android.transition.Transition.EpicenterCallback) */ - public static ActivityOptions makeSceneTransitionAnimation( - Pair... sharedElements) { + public static ActivityOptions makeSceneTransitionAnimation(Window window, + ActivityTransitionListener listener) { ActivityOptions opts = new ActivityOptions(); opts.mAnimationType = ANIM_SCENE_TRANSITION; - opts.mSharedElementNames = new ArrayList(); - opts.mLocalElementNames = new ArrayList(); - - if (sharedElements != null) { - for (Pair sharedElement : sharedElements) { - opts.addSharedElement(sharedElement.first, sharedElement.second); - } - } + ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener); + opts.mExitReceiver = exit; return opts; } @@ -435,9 +409,7 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: - mTransitionCompleteListener = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER); - mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); - mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES); + mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER); break; } } @@ -493,50 +465,16 @@ public class ActivityOptions { } /** @hide */ - public ArrayList getSharedElementNames() { return mSharedElementNames; } - - /** @hide */ - public ArrayList getLocalElementNames() { return mLocalElementNames; } - - /** @hide */ - public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target, - ArrayList sharedElementNames) { - if (mTransitionCompleteListener != null) { - IRemoteCallback callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - if (data == null) { - target.exitTransitionComplete(); - } else { - target.sharedElementTransitionComplete(data); - } - } - }; - Bundle bundle = new Bundle(); - bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder()); - bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames); - mTransitionCompleteListener.send(MSG_SET_LISTENER, bundle); + public void dispatchActivityStopped() { + if (mExitReceiver != null) { + mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null); } } /** @hide */ - public void dispatchSharedElementsReady() { - if (mTransitionCompleteListener != null) { - mTransitionCompleteListener.send(MSG_HIDE_SHARED_ELEMENTS, null); - } - } - - /** @hide */ - public void dispatchPrepareRestore() { - if (mTransitionCompleteListener != null) { - mTransitionCompleteListener.send(MSG_PREPARE_RESTORE, null); - } - } - - /** @hide */ - public void dispatchRestore(Bundle sharedElementsArgs) { - if (mTransitionCompleteListener != null) { - mTransitionCompleteListener.send(MSG_RESTORE, sharedElementsArgs); + public void dispatchStartExit() { + if (mExitReceiver != null) { + mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null); } } @@ -557,6 +495,15 @@ public class ActivityOptions { } } + /** @hide */ + public EnterTransitionCoordinator createEnterActivityTransition(Activity activity) { + EnterTransitionCoordinator coordinator = null; + if (mAnimationType == ANIM_SCENE_TRANSITION) { + coordinator = new EnterTransitionCoordinator(activity, mExitReceiver); + } + return coordinator; + } + /** * Update the current values in this ActivityOptions from those supplied * in otherOptions. Any values @@ -566,8 +513,7 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } - mSharedElementNames = null; - mLocalElementNames = null; + mExitReceiver = null; switch (otherOptions.mAnimationType) { case ANIM_CUSTOM: mAnimationType = otherOptions.mAnimationType; @@ -581,7 +527,6 @@ public class ActivityOptions { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; - mTransitionCompleteListener = null; break; case ANIM_SCALE_UP: mAnimationType = otherOptions.mAnimationType; @@ -596,7 +541,6 @@ public class ActivityOptions { } } mAnimationStartedListener = null; - mTransitionCompleteListener = null; break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: @@ -611,15 +555,12 @@ public class ActivityOptions { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; - mTransitionCompleteListener = null; break; case ANIM_SCENE_TRANSITION: mAnimationType = otherOptions.mAnimationType; - mTransitionCompleteListener = otherOptions.mTransitionCompleteListener; + mExitReceiver = otherOptions.mExitReceiver; mThumbnail = null; mAnimationStartedListener = null; - mSharedElementNames = otherOptions.mSharedElementNames; - mLocalElementNames = otherOptions.mLocalElementNames; break; } } @@ -663,11 +604,9 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: b.putInt(KEY_ANIM_TYPE, mAnimationType); - if (mTransitionCompleteListener != null) { - b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener); + if (mExitReceiver != null) { + b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver); } - b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames); - b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames); break; } return b; @@ -687,130 +626,92 @@ public class ActivityOptions { return null; } - /** @hide */ - public void addSharedElements(Map sharedElements) { - for (Map.Entry entry : sharedElements.entrySet()) { - addSharedElement(entry.getValue(), entry.getKey()); - } + /** + * Listener provided in + * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener)} or in + * {@link android.app.Activity#setActivityTransitionListener( + * android.app.ActivityOptions.ActivityTransitionListener)} to monitor the Activity transitions. + * The events can be used to customize or override Activity Transition behavior. + */ + public static class ActivityTransitionListener { + /** + * Called when the enter Transition is ready to start, but hasn't started yet. If + * {@link android.view.Window#getEnterTransition()} is non-null, + * The entering views will be {@link View#INVISIBLE}. + */ + public void onEnterReady() {} + + /** + * Called when the remote exiting transition completes. + */ + public void onRemoteExitComplete() {} + + /** + * Called when the start state for shared elements is captured on enter. + */ + public void onCaptureSharedElementStart() {} + + /** + * Called when the end state for shared elements is captured on enter. + */ + public void onCaptureSharedElementEnd() {} + + /** + * Called when the enter Transition has been started. + * @param sharedElementNames The names of shared elements that were transferred. + * @param sharedElements The shared elements that were transferred. + */ + public void onStartEnterTransition(List sharedElementNames, + List sharedElements) {} + + /** + * Called when the exit Transition has been started. + * @param sharedElementNames The names of all shared elements that will be transferred. + * @param sharedElements All shared elements that will be transferred. + */ + public void onStartExitTransition(List sharedElementNames, + List sharedElements) {} + + /** + * Called when the exiting shared element transition completes. + */ + public void onSharedElementExitTransitionComplete() {} + + /** + * Called on exit when the shared element has been transferred. + * @param sharedElementNames The names of all shared elements that were transferred. + * @param sharedElements All shared elements that will were transferred. + */ + public void onSharedElementTransferred(List sharedElementNames, + List sharedElements) {} + + /** + * Called when the exit transition has completed. + */ + public void onExitTransitionComplete() {} + + /** + * Returns a mapping from a View in the View hierarchy to the shared element name used + * in the call. This is called twice -- once when the view is + * entering and again when it exits. A null return value indicates that the + * View hierachy can be trusted without any remapping. + * @return A map from a View in the hierarchy to the shared element name used in the + * call. + */ + public Pair[] getSharedElementsMapping() { return null; } } - /** @hide */ - public void updateSceneTransitionAnimation(Transition exitTransition, - Transition sharedElementTransition, SharedElementSource sharedElementSource) { - mTransitionCompleteListener = new ExitTransitionListener(exitTransition, - sharedElementTransition, sharedElementSource); - } + private static class SharedElementMappingListener extends ActivityTransitionListener { + Pair[] mSharedElementsMapping = new Pair[1]; - private void addSharedElement(View view, String name) { - String sharedElementName = view.getSharedElementName(); - if (name == null) { - name = sharedElementName; - } - mSharedElementNames.add(name); - mLocalElementNames.add(sharedElementName); - } - - /** @hide */ - public interface SharedElementSource { - Bundle getSharedElementExitState(); - void acceptedSharedElements(ArrayList sharedElementNames); - void hideSharedElements(); - void restore(Bundle sharedElementState); - void prepareForRestore(); - } - - private static class ExitTransitionListener extends ResultReceiver - implements Transition.TransitionListener { - private boolean mSharedElementNotified; - private IRemoteCallback mTransitionCompleteCallback; - private boolean mExitComplete; - private boolean mSharedElementComplete; - private SharedElementSource mSharedElementSource; - - public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition, - SharedElementSource sharedElementSource) { - super(null); - mSharedElementSource = sharedElementSource; - exitTransition.addListener(this); - sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { - @Override - public void onTransitionEnd(Transition transition) { - mSharedElementComplete = true; - notifySharedElement(); - transition.removeListener(this); - } - }); + public SharedElementMappingListener(View view, String name) { + mSharedElementsMapping[0] = Pair.create(view, name); } @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - switch (resultCode) { - case MSG_SET_LISTENER: - IBinder listener = resultData.getBinder(KEY_TRANSITION_TARGET_LISTENER); - mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(listener); - ArrayList sharedElementNames - = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); - mSharedElementSource.acceptedSharedElements(sharedElementNames); - notifySharedElement(); - notifyExit(); - break; - case MSG_HIDE_SHARED_ELEMENTS: - mSharedElementSource.hideSharedElements(); - break; - case MSG_PREPARE_RESTORE: - mSharedElementSource.prepareForRestore(); - break; - case MSG_RESTORE: - mSharedElementSource.restore(resultData); - break; - } - } - - @Override - public void onTransitionStart(Transition transition) { - } - - @Override - public void onTransitionEnd(Transition transition) { - mExitComplete = true; - notifyExit(); - transition.removeListener(this); - } - - @Override - public void onTransitionCancel(Transition transition) { - onTransitionEnd(transition); - } - - @Override - public void onTransitionPause(Transition transition) { - } - - @Override - public void onTransitionResume(Transition transition) { - } - - private void notifySharedElement() { - if (!mSharedElementNotified && mSharedElementComplete - && mTransitionCompleteCallback != null) { - mSharedElementNotified = true; - try { - Bundle sharedElementState = mSharedElementSource.getSharedElementExitState(); - mTransitionCompleteCallback.sendResult(sharedElementState); - } catch (RemoteException e) { - Log.w(TAG, "Couldn't notify that the transition ended", e); - } - } - } - - private void notifyExit() { - if (mExitComplete && mTransitionCompleteCallback != null) { - try { - mTransitionCompleteCallback.sendResult(null); - } catch (RemoteException e) { - Log.w(TAG, "Couldn't notify that the transition ended", e); - } - } + public Pair[] getSharedElementsMapping() { + return mSharedElementsMapping; } } } diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java new file mode 100644 index 0000000000000..d8a356f5a6829 --- /dev/null +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.transition.Transition; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.ArrayMap; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes + * that manage activity transitions and the communications coordinating them between + * Activities. The ExitTransitionCoordinator is created in the + * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator + * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is + * attached. + * + * Typical startActivity goes like this: + * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation + * 2) Activity#startActivity called and that calls startExit() through + * ActivityOptions#dispatchStartExit + * - Exit transition starts by setting transitioning Views to INVISIBLE + * 3) Launched Activity starts, creating an EnterTransitionCoordinator. + * - The Window is made translucent + * - The Window background alpha is set to 0 + * - The transitioning views are made INVISIBLE + * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator. + * 4) The shared element transition completes. + * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator + * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator. + * - Shared elements are made VISIBLE + * - Shared elements positions and size are set to match the end state of the calling + * Activity. + * - The shared element transition is started + * - If the window allows overlapping transitions, the views transition is started by setting + * the entering Views to VISIBLE and the background alpha is animated to opaque. + * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator + * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator + * - The shared elements are made INVISIBLE + * 7) The exit transition completes in the calling Activity. + * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. + * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. + * - If the window doesn't allow overlapping enter transitions, the enter transition is started + * by setting entering views to VISIBLE and the background is animated to opaque. + * 9) The background opacity animation completes. + * - The window is made opaque + * 10) The calling Activity gets an onStop() call + * - onActivityStopped() is called and all exited Views are made VISIBLE. + * + * Typical finishWithTransition goes like this: + * 1) finishWithTransition() calls startExit() + * - The Window start transitioning to Translucent + * - If no background exists, a black background is substituted + * - MSG_PREPARE_RESTORE is sent to the ExitTransitionCoordinator + * - The shared elements in the scene are matched against those shared elements + * that were sent by comparing the names. + * - The exit transition is started by setting Views to INVISIBLE. + * 2) MSG_PREPARE_RESTORE is received by the EnterTransitionCoordinator + * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped() + * was called + * 3) The Window is made translucent and a callback is received + * - The background alpha is animated to 0 + * 4) The background alpha animation completes + * 5) The shared element transition completes + * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the + * ExitTransitionCoordinator + * 6) MSG_TAKE_SHARED_ELEMENTS is received by ExitTransitionCoordinator + * - Shared elements are made VISIBLE + * - Shared elements positions and size are set to match the end state of the calling + * Activity. + * - The shared element transition is started + * - If the window allows overlapping transitions, the views transition is started by setting + * the entering Views to VISIBLE. + * - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator + * 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator + * - The shared elements are made INVISIBLE + * 8) The exit transition completes in the finishing Activity. + * - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator. + * - finish() is called on the exiting Activity + * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator. + * - If the window doesn't allow overlapping enter transitions, the enter transition is started + * by setting entering views to VISIBLE. + */ +abstract class ActivityTransitionCoordinator extends ResultReceiver { + private static final String TAG = "ActivityTransitionCoordinator"; + + /** + * The names of shared elements that are transitioned to the started Activity. + * This is also the name of shared elements that the started Activity accepted. + */ + public static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names"; + + public static final String KEY_SHARED_ELEMENT_STATE = "android:shared_element_state"; + + /** + * For Activity transitions, the called Activity's listener to receive calls + * when transitions complete. + */ + static final String KEY_TRANSITION_RESULTS_RECEIVER = "android:transitionTargetListener"; + + private static final String KEY_SCREEN_X = "shared_element:screenX"; + private static final String KEY_SCREEN_Y = "shared_element:screenY"; + private static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; + 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"; + + /** + * Sent by the exiting coordinator (either EnterTransitionCoordinator + * or ExitTransitionCoordinator) after the shared elements have + * become stationary (shared element transition completes). This tells + * the remote coordinator to take control of the shared elements and + * that animations may begin. The remote Activity won't start entering + * until this message is received, but may wait for + * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. + */ + public static final int MSG_SET_LISTENER = 100; + + /** + * Sent by the entering coordinator to tell the exiting coordinator + * to hide its shared elements after it has started its shared + * element transition. This is temporary until the + * interlock of shared elements is figured out. + */ + public static final int MSG_HIDE_SHARED_ELEMENTS = 101; + + /** + * Sent by the EnterTransitionCoordinator to tell the + * ExitTransitionCoordinator to hide all of its exited views after + * MSG_ACTIVITY_STOPPED has caused them all to show. + */ + public static final int MSG_PREPARE_RESTORE = 102; + + /** + * Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped + * to leave the Activity in a good state after it has been hidden. + */ + public static final int MSG_ACTIVITY_STOPPED = 103; + + /** + * Sent by the exiting coordinator (either EnterTransitionCoordinator + * or ExitTransitionCoordinator) after the shared elements have + * become stationary (shared element transition completes). This tells + * the remote coordinator to take control of the shared elements and + * that animations may begin. The remote Activity won't start entering + * until this message is received, but may wait for + * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. + */ + public static final int MSG_TAKE_SHARED_ELEMENTS = 104; + + /** + * Sent by the exiting coordinator (either + * EnterTransitionCoordinator or ExitTransitionCoordinator) after + * the exiting Views have finished leaving the scene. This will + * be ignored if allowOverlappingTransitions() is true on the + * remote coordinator. If it is false, it will trigger the enter + * transition to start. + */ + public static final int MSG_EXIT_TRANSITION_COMPLETE = 105; + + /** + * Sent by Activity#startActivity to begin the exit transition. + */ + public static final int MSG_START_EXIT_TRANSITION = 106; + + private Window mWindow; + private ArrayList mSharedElements = new ArrayList(); + private ArrayList mTargetSharedNames = new ArrayList(); + private ActivityOptions.ActivityTransitionListener mListener = + new ActivityOptions.ActivityTransitionListener(); + private ArrayList mEnteringViews; + private ResultReceiver mRemoteResultReceiver; + private boolean mNotifiedSharedElementTransitionComplete; + private boolean mNotifiedExitTransitionComplete; + + private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); + + private Transition.TransitionListener mSharedElementListener = + new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + onSharedElementTransitionEnd(); + } + }; + + private Transition.TransitionListener mExitListener = + new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + onExitTransitionEnd(); + } + }; + + public ActivityTransitionCoordinator(Window window) + { + super(new Handler()); + mWindow = window; + } + + // -------------------- ResultsReceiver Overrides ---------------------- + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case MSG_SET_LISTENER: + ResultReceiver resultReceiver + = resultData.getParcelable(KEY_TRANSITION_RESULTS_RECEIVER); + setRemoteResultReceiver(resultReceiver); + onSetResultReceiver(); + break; + case MSG_HIDE_SHARED_ELEMENTS: + onHideSharedElements(); + break; + case MSG_PREPARE_RESTORE: + onPrepareRestore(); + break; + case MSG_EXIT_TRANSITION_COMPLETE: + onRemoteSceneExitComplete(); + break; + case MSG_TAKE_SHARED_ELEMENTS: + ArrayList sharedElementNames + = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); + Bundle sharedElementState = resultData.getBundle(KEY_SHARED_ELEMENT_STATE); + onTakeSharedElements(sharedElementNames, sharedElementState); + break; + case MSG_ACTIVITY_STOPPED: + onActivityStopped(); + break; + case MSG_START_EXIT_TRANSITION: + startExit(); + break; + } + } + + // -------------------- calls that can be overridden by subclasses -------------------- + + /** + * Called when MSG_SET_LISTENER is received. This will only be received by + * ExitTransitionCoordinator. + */ + protected void onSetResultReceiver() {} + + /** + * Called when MSG_HIDE_SHARED_ELEMENTS is received + */ + protected void onHideSharedElements() { + setViewVisibility(getSharedElements(), View.INVISIBLE); + mListener.onSharedElementTransferred(getSharedElementNames(), getSharedElements()); + } + + /** + * Called when MSG_PREPARE_RESTORE is called. This will only be received by + * ExitTransitionCoordinator. + */ + protected void onPrepareRestore() { + mListener.onEnterReady(); + } + + /** + * Called when MSG_EXIT_TRANSITION_COMPLETE is received -- the remote coordinator has + * completed its exit transition. This can be called by the ExitTransitionCoordinator when + * starting an Activity or EnterTransitionCoordinator when called with finishWithTransition. + */ + protected void onRemoteSceneExitComplete() { + if (!allowOverlappingTransitions()) { + Transition transition = beginTransition(mEnteringViews, false, true, true); + onStartEnterTransition(transition, mEnteringViews); + } + mListener.onRemoteExitComplete(); + } + + /** + * Called when MSG_TAKE_SHARED_ELEMENTS is received. This means that the shared elements are + * in a stable state and ready to move to the Window. + * @param sharedElementNames The names of the shared elements to move. + * @param state Contains the shared element states (size & position) + */ + protected void onTakeSharedElements(ArrayList sharedElementNames, Bundle state) { + setSharedElements(); + reconcileSharedElements(sharedElementNames); + mEnteringViews.removeAll(mSharedElements); + setSharedElementState(state); + 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); + } + + /** + * Called when MSG_ACTIVITY_STOPPED is received. This is received when Activity.onStop is + * called after running startActivity* is called using an Activity Transition. + */ + protected void onActivityStopped() {} + + /** + * Called when the start transition is ready to run. This may be immediately after + * MSG_TAKE_SHARED_ELEMENTS or MSG_EXIT_TRANSITION_COMPLETE, depending on whether + * overlapping transitions are allowed. + * @param transition The transition currently started. + * @param enteringViews The views entering the scene. This won't include shared elements. + */ + protected void onStartEnterTransition(Transition transition, ArrayList enteringViews) { + if (getViewsTransition() != null) { + setViewVisibility(enteringViews, View.VISIBLE); + } + mEnteringViews = null; + mListener.onStartEnterTransition(getSharedElementNames(), getSharedElements()); + } + + /** + * Called when the exit transition has started. + * @param exitingViews The views leaving the scene. This won't include shared elements. + */ + protected void onStartExitTransition(ArrayList exitingViews) {} + + /** + * Called during the exit when the shared element transition has completed. + */ + protected void onSharedElementTransitionEnd() { + Bundle bundle = new Bundle(); + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElements.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mTargetSharedNames.get(i); + captureSharedElementState(sharedElement, name, bundle, tempLoc); + } + Bundle allValues = new Bundle(); + allValues.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, getSharedElementNames()); + allValues.putBundle(KEY_SHARED_ELEMENT_STATE, bundle); + sharedElementTransitionComplete(allValues); + mListener.onSharedElementExitTransitionComplete(); + } + + /** + * Called after the shared element transition is complete to pass the shared element state + * to the remote coordinator. + * @param bundle The Bundle to send to the coordinator containing the shared element state. + */ + protected abstract void sharedElementTransitionComplete(Bundle bundle); + + /** + * Called when the exit transition finishes. + */ + protected void onExitTransitionEnd() { + mListener.onExitTransitionComplete(); + } + + /** + * Called to start the exit transition. Launched from ActivityOptions#dispatchStartExit + */ + protected abstract void startExit(); + + /** + * A non-null transition indicates that the Views of the Window should be made INVISIBLE. + * @return The Transition used to cause transitioning views to either enter or exit the scene. + */ + protected abstract Transition getViewsTransition(); + + /** + * @return The Transition used to move the shared elements from the start position and size + * to the end position and size. + */ + protected abstract Transition getSharedElementTransition(); + + /** + * @return When the enter transition should overlap with the exit transition of the + * remote controller. + */ + protected abstract boolean allowOverlappingTransitions(); + + // called by subclasses + + protected void notifySharedElementTransitionComplete(Bundle sharedElements) { + if (!mNotifiedSharedElementTransitionComplete) { + mNotifiedSharedElementTransitionComplete = true; + mRemoteResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElements); + } + } + + protected void notifyExitTransitionComplete() { + if (!mNotifiedExitTransitionComplete) { + mNotifiedExitTransitionComplete = true; + mRemoteResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); + } + } + + protected void notifyPrepareRestore() { + mRemoteResultReceiver.send(MSG_PREPARE_RESTORE, null); + } + + protected void setRemoteResultReceiver(ResultReceiver resultReceiver) { + mRemoteResultReceiver = resultReceiver; + } + + protected void notifySetListener() { + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_TRANSITION_RESULTS_RECEIVER, this); + mRemoteResultReceiver.send(MSG_SET_LISTENER, bundle); + } + + protected void setEnteringViews(ArrayList views) { + mEnteringViews = views; + } + + protected void setSharedElements() { + Pair[] sharedElements = mListener.getSharedElementsMapping(); + mSharedElements.clear(); + mTargetSharedNames.clear(); + if (sharedElements == null) { + ArrayMap map = new ArrayMap(); + setViewVisibility(mEnteringViews, View.VISIBLE); + getDecor().findSharedElements(map); + setViewVisibility(mEnteringViews, View.INVISIBLE); + for (int i = 0; i < map.size(); i++) { + View view = map.valueAt(i); + String name = map.keyAt(i); + mSharedElements.add(view); + mTargetSharedNames.add(name); + } + } else { + for (int i = 0; i < sharedElements.length; i++) { + Pair viewStringPair = sharedElements[i]; + View view = viewStringPair.first; + String name = viewStringPair.second; + mSharedElements.add(view); + mTargetSharedNames.add(name); + } + } + } + + protected ArrayList getSharedElements() { + return mSharedElements; + } + + protected ArrayList getSharedElementNames() { + return mTargetSharedNames; + } + + protected Window getWindow() { + return mWindow; + } + + protected ViewGroup getDecor() { + return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); + } + + protected void startExitTransition(ArrayList sharedElements) { + setSharedElements(); + reconcileSharedElements(sharedElements); + ArrayList transitioningViews = captureTransitioningViews(); + beginTransition(transitioningViews, true, true, false); + onStartExitTransition(transitioningViews); + if (getViewsTransition() != null) { + setViewVisibility(transitioningViews, View.INVISIBLE); + } + mListener.onStartExitTransition(getSharedElementNames(), getSharedElements()); + } + + protected void clearConnections() { + mRemoteResultReceiver = null; + } + + // public API + + public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) { + if (listener == null) { + mListener = new ActivityOptions.ActivityTransitionListener(); + } else { + mListener = listener; + } + } + + // private methods + + private Transition configureTransition(Transition transition) { + if (transition != null) { + transition = transition.clone(); + transition.setEpicenterCallback(mEpicenterCallback); + } + return transition; + } + + 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); + } + } + if (!mSharedElements.isEmpty()) { + epicenter = calcEpicenter(mSharedElements.get(0)); + } + mEpicenterCallback.setEpicenter(epicenter); + } + + private void setSharedElementState(Bundle sharedElementState) { + if (sharedElementState != null) { + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElements.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mTargetSharedNames.get(i); + setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + } + } + mListener.onCaptureSharedElementStart(); + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mListener.onCaptureSharedElementEnd(); + return true; + } + } + ); + } + + /** + * Sets the captured values from a previous + * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])} + * @param view The View to apply placement changes to. + * @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. + */ + private static void setSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = transitionArgs.getBundle(name); + if (sharedElementBundle == null) { + return; + } + + float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); + view.setTranslationZ(z); + + int x = sharedElementBundle.getInt(KEY_SCREEN_X); + int y = sharedElementBundle.getInt(KEY_SCREEN_Y); + int width = sharedElementBundle.getInt(KEY_WIDTH); + int height = sharedElementBundle.getInt(KEY_HEIGHT); + + int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + 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 right = left + width; + int bottom = top + height; + view.layout(left, top, right, bottom); + + view.requestLayout(); + } + + /** + * Captures placement information for Views with a shared element name for + * Activity Transitions. + * @param view The View to capture the placement information for. + * @param name The shared element name in the target Activity to apply the placement + * information for. + * @param transitionArgs Bundle to store shared element placement information. + * @param tempLoc A temporary int[2] for capturing the current location of views. + * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[]) + */ + private static void captureSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = new Bundle(); + view.getLocationOnScreen(tempLoc); + float scaleX = view.getScaleX(); + sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); + int width = Math.round(view.getWidth() * scaleX); + sharedElementBundle.putInt(KEY_WIDTH, width); + + float scaleY = view.getScaleY(); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); + int height= Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_HEIGHT, height); + + sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); + + sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); + + transitionArgs.putBundle(name, sharedElementBundle); + } + + private static Rect calcEpicenter(View view) { + int[] loc = new int[2]; + view.getLocationOnScreen(loc); + int left = loc[0] + Math.round(view.getTranslationX()); + int top = loc[1] + Math.round(view.getTranslationY()); + int right = left + view.getWidth(); + int bottom = top + view.getHeight(); + return new Rect(left, top, right, bottom); + } + + public static void setViewVisibility(Collection views, int visibility) { + if (views != null) { + for (View view : views) { + view.setVisibility(visibility); + } + } + } + + private static Transition addTransitionTargets(Transition transition, Collection views) { + if (transition == null || views == null || views.isEmpty()) { + return null; + } + TransitionSet set = new TransitionSet(); + set.addTransition(transition.clone()); + if (views != null) { + for (View view: views) { + set.addTarget(view); + } + } + return set; + } + + private ArrayList captureTransitioningViews() { + if (getViewsTransition() == null) { + return null; + } + ArrayList transitioningViews = new ArrayList(); + getDecor().captureTransitioningViews(transitioningViews); + transitioningViews.removeAll(getSharedElements()); + return transitioningViews; + } + + private Transition getSharedElementTransition(boolean isEnter) { + Transition transition = getSharedElementTransition(); + if (transition == null) { + return null; + } + transition = configureTransition(transition); + if (!isEnter) { + transition.addListener(mSharedElementListener); + } + return transition; + } + + private Transition getViewsTransition(ArrayList transitioningViews, boolean isEnter) { + Transition transition = getViewsTransition(); + if (transition == null) { + return null; + } + transition = configureTransition(transition); + if (!isEnter) { + transition.addListener(mExitListener); + } + return addTransitionTargets(transition, transitioningViews); + } + + private Transition beginTransition(ArrayList transitioningViews, + boolean transitionSharedElement, boolean transitionViews, boolean isEnter) { + Transition sharedElementTransition = null; + if (transitionSharedElement) { + sharedElementTransition = getSharedElementTransition(isEnter); + if (!isEnter && sharedElementTransition == null) { + onSharedElementTransitionEnd(); + } + } + Transition viewsTransition = null; + if (transitionViews) { + viewsTransition = getViewsTransition(transitioningViews, isEnter); + if (!isEnter && viewsTransition == null) { + onExitTransitionEnd(); + } + } + + Transition transition = null; + if (sharedElementTransition == null) { + transition = viewsTransition; + } else if (viewsTransition == null) { + transition = sharedElementTransition; + } else { + TransitionSet set = new TransitionSet(); + set.addTransition(sharedElementTransition); + set.addTransition(viewsTransition); + transition = set; + } + if (transition != null) { + TransitionManager.beginDelayedTransition(getDecor(), transition); + if (transitionSharedElement && !mSharedElements.isEmpty()) { + mSharedElements.get(0).invalidate(); + } else if (transitionViews && !transitioningViews.isEmpty()) { + transitioningViews.get(0).invalidate(); + } + } + return transition; + } + + private static class FixedEpicenterCallback extends Transition.EpicenterCallback { + private Rect mEpicenter; + + public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; } + + @Override + public Rect getEpicenter(Transition transition) { + return mEpicenter; + } + } +} diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java new file mode 100644 index 0000000000000..aa097e095b8d4 --- /dev/null +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.transition.Transition; +import android.util.ArrayMap; +import android.util.Pair; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.Window; + +import java.util.ArrayList; + +/** + * This ActivityTransitionCoordinator is created by the Activity to manage + * the enter scene and shared element transfer as well as Activity#finishWithTransition + * exiting the Scene and transferring shared elements back to the called Activity. + */ +class EnterTransitionCoordinator extends ActivityTransitionCoordinator + implements ViewTreeObserver.OnPreDrawListener { + private static final String TAG = "EnterTransitionCoordinator"; + + // The background fade in/out duration. 150ms is pretty quick, but not abrupt. + private static final int FADE_BACKGROUND_DURATION_MS = 150; + + /** + * The shared element names sent by the ExitTransitionCoordinator and may be + * shared when exiting back. + */ + private ArrayList mEnteringSharedElementNames; + + /** + * The Activity that has created this coordinator. This is used solely to make the + * Window translucent/opaque. + */ + private Activity mActivity; + + /** + * True if the Window was opaque at the start and we should make it opaque again after + * enter transitions have completed. + */ + private boolean mWasOpaque; + + /** + * During exit, is the background alpha == 0? + */ + private boolean mBackgroundFadedOut; + + /** + * During exit, has the shared element transition completed? + */ + private boolean mSharedElementTransitionComplete; + + /** + * Has the exit started? We don't want to accidentally exit multiple times. e.g. when + * back is hit twice during the exit animation. + */ + private boolean mExitTransitionStarted; + + /** + * Has the exit transition ended? + */ + private boolean mExitTransitionComplete; + + /** + * We only want to make the Window transparent and set the background alpha once. After that, + * the Activity won't want the same enter transition. + */ + private boolean mMadeReady; + + /** + * True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that + * enter and exit transitions should be active. + */ + private boolean mSupportsTransition; + + /** + * Background alpha animations may complete prior to receiving the callback for + * onTranslucentConversionComplete. If so, we need to immediately call to make the Window + * opaque. + */ + private boolean mMakeOpaque; + + public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) { + super(activity.getWindow()); + mActivity = activity; + setRemoteResultReceiver(resultReceiver); + } + + public void readyToEnter() { + if (!mMadeReady) { + mMadeReady = true; + mSupportsTransition = getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS); + if (mSupportsTransition) { + Window window = getWindow(); + window.getDecorView().getViewTreeObserver().addOnPreDrawListener(this); + mActivity.overridePendingTransition(0, 0); + mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { + @Override + public void onTranslucentConversionComplete(boolean drawComplete) { + mWasOpaque = true; + if (mMakeOpaque) { + mActivity.convertFromTranslucent(); + } + } + }); + Drawable background = getDecor().getBackground(); + if (background != null) { + window.setBackgroundDrawable(null); + background.setAlpha(0); + window.setBackgroundDrawable(background); + } + } + } + } + + @Override + protected void onRemoteSceneExitComplete() { + super.onRemoteSceneExitComplete(); + } + + @Override + protected void onTakeSharedElements(ArrayList sharedElementNames, Bundle state) { + mEnteringSharedElementNames = new ArrayList(); + mEnteringSharedElementNames.addAll(sharedElementNames); + super.onTakeSharedElements(sharedElementNames, state); + } + + @Override + protected void sharedElementTransitionComplete(Bundle bundle) { + notifySharedElementTransitionComplete(bundle); + } + + @Override + public boolean onPreDraw() { + getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this); + setEnteringViews(readyEnteringViews()); + notifySetListener(); + onPrepareRestore(); + return false; + } + + @Override + public void startExit() { + if (!mExitTransitionStarted) { + mExitTransitionStarted = true; + startExitTransition(mEnteringSharedElementNames); + } + } + + @Override + protected Transition getViewsTransition() { + if (!mSupportsTransition) { + return null; + } + return getWindow().getEnterTransition(); + } + + @Override + protected Transition getSharedElementTransition() { + if (!mSupportsTransition) { + return null; + } + return getWindow().getSharedElementEnterTransition(); + } + + @Override + protected void onStartEnterTransition(Transition transition, ArrayList enteringViews) { + Drawable background = getDecor().getBackground(); + if (background != null) { + ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255); + animator.setDuration(FADE_BACKGROUND_DURATION_MS); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mMakeOpaque = true; + if (mWasOpaque) { + mActivity.convertFromTranslucent(); + } + } + }); + animator.start(); + } else if (mWasOpaque) { + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + mMakeOpaque = true; + mActivity.convertFromTranslucent(); + } + }); + } + super.onStartEnterTransition(transition, enteringViews); + } + + public ArrayList readyEnteringViews() { + ArrayList enteringViews = new ArrayList(); + getDecor().captureTransitioningViews(enteringViews); + if (getViewsTransition() != null) { + setViewVisibility(enteringViews, View.INVISIBLE); + } + return enteringViews; + } + + @Override + protected void startExitTransition(ArrayList sharedElements) { + notifyPrepareRestore(); + + if (getDecor().getBackground() == null) { + ColorDrawable black = new ColorDrawable(0xFF000000); + getWindow().setBackgroundDrawable(black); + } + if (mWasOpaque) { + mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { + @Override + public void onTranslucentConversionComplete(boolean drawComplete) { + fadeOutBackground(); + } + }); + } else { + fadeOutBackground(); + } + + super.startExitTransition(sharedElements); + } + + private void fadeOutBackground() { + ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(), + "alpha", 0); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundFadedOut = true; + if (mSharedElementTransitionComplete) { + EnterTransitionCoordinator.super.onSharedElementTransitionEnd(); + } + } + }); + animator.setDuration(FADE_BACKGROUND_DURATION_MS); + animator.start(); + } + + @Override + protected void onExitTransitionEnd() { + mExitTransitionComplete = true; + exitAfterSharedElementTransition(); + super.onExitTransitionEnd(); + clearConnections(); + } + + @Override + protected void onSharedElementTransitionEnd() { + mSharedElementTransitionComplete = true; + if (mBackgroundFadedOut) { + super.onSharedElementTransitionEnd(); + } + } + + @Override + protected boolean allowOverlappingTransitions() { + return getWindow().getAllowEnterTransitionOverlap(); + } + + private void exitAfterSharedElementTransition() { + if (mSharedElementTransitionComplete && mExitTransitionComplete) { + mActivity.finish(); + if (mSupportsTransition) { + mActivity.overridePendingTransition(0, 0); + } + notifyExitTransitionComplete(); + } + } +} diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java new file mode 100644 index 0000000000000..d920787376325 --- /dev/null +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.os.Bundle; +import android.transition.Transition; +import android.util.Pair; +import android.view.View; +import android.view.Window; + +import java.util.ArrayList; + +/** + * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation + * to govern the exit of the Scene and the shared elements when calling an Activity as well as + * the reentry of the Scene when coming back from the called Activity. + */ +class ExitTransitionCoordinator extends ActivityTransitionCoordinator { + private static final String TAG = "ExitTransitionCoordinator"; + + /** + * The Views that have exited and need to be restored to VISIBLE when returning to the + * normal state. + */ + private ArrayList mTransitioningViews; + + /** + * Has the exit started? We don't want to accidentally exit multiple times. + */ + private boolean mExitStarted; + + /** + * Has the called Activity's ResultReceiver been set? + */ + private boolean mIsResultReceiverSet; + + /** + * Has the exit transition completed? If so, we can notify as soon as the ResultReceiver + * has been set. + */ + private boolean mExitComplete; + + /** + * Has the shared element transition completed? If so, we can notify as soon as the + * ResultReceiver has been set. + */ + private Bundle mSharedElements; + + /** + * Has the shared element transition completed? + */ + private boolean mSharedElementsComplete; + + public ExitTransitionCoordinator(Window window, + ActivityOptions.ActivityTransitionListener listener) { + super(window); + setActivityTransitionListener(listener); + } + + @Override + protected void onSetResultReceiver() { + mIsResultReceiverSet = true; + notifyCompletions(); + } + + @Override + protected void onPrepareRestore() { + makeTransitioningViewsInvisible(); + setEnteringViews(mTransitioningViews); + mTransitioningViews = null; + super.onPrepareRestore(); + } + + @Override + protected void onTakeSharedElements(ArrayList sharedElementNames, Bundle state) { + super.onTakeSharedElements(sharedElementNames, state); + clearConnections(); + } + + @Override + protected void onActivityStopped() { + if (getViewsTransition() != null) { + setViewVisibility(mTransitioningViews, View.VISIBLE); + } + super.onActivityStopped(); + } + + @Override + protected void sharedElementTransitionComplete(Bundle bundle) { + mSharedElements = bundle; + mSharedElementsComplete = true; + notifyCompletions(); + } + + @Override + protected void onExitTransitionEnd() { + mExitComplete = true; + notifyCompletions(); + super.onExitTransitionEnd(); + } + + private void notifyCompletions() { + if (mIsResultReceiverSet && mSharedElementsComplete) { + if (mSharedElements != null) { + notifySharedElementTransitionComplete(mSharedElements); + mSharedElements = null; + } + if (mExitComplete) { + notifyExitTransitionComplete(); + } + } + } + + @Override + public void startExit() { + if (!mExitStarted) { + mExitStarted = true; + setSharedElements(); + startExitTransition(getSharedElementNames()); + } + } + + @Override + protected Transition getViewsTransition() { + if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + return null; + } + return getWindow().getExitTransition(); + } + + @Override + protected Transition getSharedElementTransition() { + if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + return null; + } + return getWindow().getSharedElementExitTransition(); + } + + private void makeTransitioningViewsInvisible() { + if (getViewsTransition() != null) { + setViewVisibility(mTransitioningViews, View.INVISIBLE); + } + } + + @Override + protected void onStartExitTransition(ArrayList exitingViews) { + mTransitioningViews = new ArrayList(); + if (exitingViews != null) { + mTransitioningViews.addAll(exitingViews); + } + mTransitioningViews.addAll(getSharedElements()); + } + + @Override + protected boolean allowOverlappingTransitions() { + return getWindow().getAllowExitTransitionOverlap(); + } +} diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index b7ae31e4e5fbc..c67d6fa9cbd45 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1585,7 +1585,7 @@ public abstract class Transition implements Cloneable { * hierarchy underneath it. */ void capturePropagationValues(TransitionValues transitionValues) { - if (mPropagation != null) { + if (mPropagation != null && !transitionValues.values.isEmpty()) { String[] propertyNames = mPropagation.getPropagationProperties(); if (propertyNames == null) { return; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index f675c6a3f1ae1..14ecc15325818 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -309,15 +309,11 @@ public class TransitionInflater { if (transitionId >= 0) { Transition transition = inflateTransition(transitionId); if (transition != null) { + if (toScene == null) { + throw new RuntimeException("No toScene for transition ID " + transitionId); + } if (fromScene == null) { - if (toScene == null) { - throw new RuntimeException("No matching fromScene or toScene " + - "for transition ID " + transitionId); - } else { - transitionManager.setTransition(toScene, transition); - } - } else if (toScene == null) { - transitionManager.setExitTransition(fromScene, transition); + transitionManager.setTransition(toScene, transition); } else { transitionManager.setTransition(fromScene, toScene, transition); } diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 1614d34da09a1..ce3cc2f4631a9 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -70,7 +70,6 @@ public class TransitionManager { private static final String[] EMPTY_STRINGS = new String[0]; ArrayMap mSceneTransitions = new ArrayMap(); - ArrayMap mExitSceneTransitions = new ArrayMap(); ArrayMap> mScenePairTransitions = new ArrayMap>(); private static ThreadLocal>>> @@ -118,21 +117,6 @@ public class TransitionManager { mSceneTransitions.put(scene, transition); } - /** - * Sets a specific transition to occur when the given scene is exited. This - * has the lowest priority -- if a Scene-to-Scene transition or - * Scene enter transition can be applied, it will. - * - * @param scene The scene which, when exited, will cause the given - * transition to run. - * @param transition The transition that will play when the given scene is - * exited. A value of null will result in the default behavior of - * using the default transition instead. - */ - public void setExitTransition(Scene scene, Transition transition) { - mExitSceneTransitions.put(scene, transition); - } - /** * Sets a specific transition to occur when the given pair of scenes is * exited/entered. @@ -181,9 +165,6 @@ public class TransitionManager { } } transition = mSceneTransitions.get(scene); - if (transition == null && sceneRoot != null) { - transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot)); - } return (transition != null) ? transition : sDefaultTransition; } @@ -238,34 +219,6 @@ public class TransitionManager { } } - /** - * Retrieve the transition to a target defined scene if one has been - * associated with this TransitionManager. - * - * @param toScene Target scene that this transition will move to - * @return Transition corresponding to the given toScene or null - * if no association exists in this TransitionManager - * - * @see #setTransition(Scene, Transition) - * @hide - */ - public Transition getEnterTransition(Scene toScene) { - return mSceneTransitions.get(toScene); - } - - /** - * Retrieve the transition from a defined scene to a target named scene if one has been - * associated with this TransitionManager. - * - * @param fromScene Scene that this transition starts from - * @return Transition corresponding to the given fromScene or null - * if no association exists in this TransitionManager - * @hide - */ - public Transition getExitTransition(Scene fromScene) { - return mExitSceneTransitions.get(fromScene); - } - /** * This private utility class is used to listen for both OnPreDraw and * OnAttachStateChange events. OnPreDraw events are the main ones we care diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 7783b6f0ff1c4..526803a6e5543 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -109,14 +109,14 @@ public abstract class Visibility extends Transition { final VisibilityInfo visInfo = new VisibilityInfo(); visInfo.visibilityChange = false; visInfo.fadeIn = false; - if (startValues != null) { + if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) { visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); } else { visInfo.startVisibility = -1; visInfo.startParent = null; } - if (endValues != null) { + if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) { visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); } else { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d2c6302b9131e..a64bdc74c75db 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2305,11 +2305,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Changes whether or not this ViewGroup should be treated as a single entity during - * ActivityTransitions. + * Activity Transitions. * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit * in Activity transitions. If false, the ViewGroup won't transition, * only its children. If true, the entire ViewGroup will transition * together. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + * android.app.ActivityOptions.ActivityTransitionListener) */ public void setTransitionGroup(boolean isTransitionGroup) { mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7bd1f56cd6c23..9c44bd1a77b18 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -32,8 +32,6 @@ import android.transition.Transition; import android.transition.TransitionManager; import android.view.accessibility.AccessibilityEvent; -import java.util.Map; - /** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the @@ -1385,86 +1383,132 @@ public abstract class Window { } /** - * Set options that can affect the transition behavior within this window. - * @param options Options to set or null for none - * @hide + * Sets the Transition that will be used to move Views into the initial scene. The entering + * Views will be those that are regular Views or ViewGroups that have + * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as entering is governed by changing visibility from + * {@link View#INVISIBLE} to {@link View#VISIBLE}. If transition is null, + * entering Views will remain unaffected. + * @param transition The Transition to use to move Views into the initial Scene. */ - public void setTransitionOptions(Bundle options, SceneTransitionListener listener) { - } + public void setEnterTransition(Transition transition) {} /** - * A callback for Window transitions to be told when the shared element is ready to be shown - * and start the transition to its target location. - * @hide + * Sets the Transition that will be used to move Views out of the scene when starting a + * new Activity. The exiting Views will be those that are regular Views or ViewGroups that + * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as exiting is governed by changing visibility + * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will + * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @param transition The Transition to use to move Views out of the scene when calling a + * new Activity. */ - public interface SceneTransitionListener { - void nullPendingTransition(); - void convertFromTranslucent(); - void convertToTranslucent(); - void sharedElementStart(Transition transition); - void sharedElementEnd(); - } + public void setExitTransition(Transition transition) {} /** - * Controls how the Activity's start Scene is faded in and when the enter scene - * is triggered to start. - *

When allow is true, the enter Scene will begin as soon as possible - * and the background will fade in when all shared elements are ready to begin - * transitioning. If allow is false, the Activity enter Scene and - * background fade will be triggered when the calling Activity's exit transition - * completes.

- * @param allow Set to true to have the Activity enter scene transition in - * as early as possible or set to false to wait for the calling - * Activity to exit first. The default value is true. + * Returns the transition used to move Views into the initial scene. The entering + * Views will be those that are regular Views or ViewGroups that have + * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as entering is governed by changing visibility from + * {@link View#INVISIBLE} to {@link View#VISIBLE}. If transition is null, + * entering Views will remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * @return the Transition to use to move Views into the initial Scene. */ - public void setAllowOverlappingEnterTransition(boolean allow) { - } + public Transition getEnterTransition() { return null; } /** - * Controls how the Activity's Scene fades out and when the calling Activity's - * enter scene is triggered when finishing to return to a calling Activity. - *

When allow is true, the Scene will fade out quickly - * and inform the calling Activity to transition in when the fade completes. - * When allow is false, the calling Activity will transition in after - * the Activity's Scene has exited. - *

- * @param allow Set to true to have the Activity fade out as soon as possible - * and transition in the calling Activity. The default value is - * true. + * Returns the Transition that will be used to move Views out of the scene when starting a + * new Activity. The exiting Views will be those that are regular Views or ViewGroups that + * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend + * {@link android.transition.Visibility} as exiting is governed by changing visibility + * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will + * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @return the Transition to use to move Views out of the scene when calling a + * new Activity. */ - public void setAllowOverlappingExitTransition(boolean allow) { - } + public Transition getExitTransition() { return null; } /** - * Start the exit transition. - * @hide + * Sets the Transition that will be used for shared elements transferred into the content + * Scene. Typical Transitions will affect size and location, such as + * {@link android.transition.MoveImage} and {@link android.transition.ChangeBounds}. A null + * value will cause transferred shared elements to blink to the final position. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @param transition The Transition to use for shared elements transferred into the content + * Scene. */ - public Bundle startExitTransitionToCallee(Bundle options) { - return null; - } + public void setSharedElementEnterTransition(Transition transition) {} /** - * Starts the transition back to the calling Activity. - * onTransitionEnd will be called on the current thread if there is no exit transition. - * @hide + * Returns the Transition that will be used for shared elements transferred into the content + * Scene. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @return Transition to use for sharend elements transferred into the content Scene. */ - public void startExitTransitionToCaller(Runnable onTransitionEnd) { - onTransitionEnd.run(); - } - - /** @hide */ - public void restoreViewVisibilityAfterTransitionToCallee() { - } + public Transition getSharedElementEnterTransition() { return null; } /** - * On entering Activity Scene transitions, shared element names may be mapped from a - * source Activity's specified name to a unique shared element name in the View hierarchy. - * Under most circumstances, mapping is not necessary - a single View will have the - * shared element name given by the calling Activity. However, if there are several similar - * Views (e.g. in a ListView), the correct shared element must be mapped. - * @param sharedElementNames A mapping from the calling Activity's assigned shared element - * name to a unique shared element name in the View hierarchy. + * Sets the Transition that will be used for shared elements after starting a new Activity + * before the shared elements are transferred to the called Activity. If the shared elements + * must animate during the exit transition, this Transition should be used. Upon completion, + * the shared elements may be transferred to the started Activity. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * @param transition The Transition to use for shared elements in the launching Window + * prior to transferring to the launched Activity's Window. */ - public void mapTransitionTargets(Map sharedElementNames) { - } + public void setSharedElementExitTransition(Transition transition) {} + + /** + * Returns the Transition to use for shared elements in the launching Window prior + * to transferring to the launched Activity's Window. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * @return the Transition to use for shared elements in the launching Window prior + * to transferring to the launched Activity's Window. + */ + public Transition getSharedElementExitTransition() { return null; } + + /** + * Controls how the transition set in + * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit + * transition of the calling Activity. When true, the transition will start as soon as possible. + * When false, the transition will wait until the remote exiting transition completes before + * starting. + * @param allow true to start the enter transition when possible or false to + * wait until the exiting transition completes. + */ + public void setAllowEnterTransitionOverlap(boolean allow) {} + + /** + * Returns how the transition set in + * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit + * transition of the calling Activity. When true, the transition will start as soon as possible. + * When false, the transition will wait until the remote exiting transition completes before + * starting. + * @return true when the enter transition should start as soon as possible or false to + * when it should wait until the exiting transition completes. + */ + public boolean getAllowEnterTransitionOverlap() { return true; } + + /** + * Controls how the transition set in + * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit + * transition of the called Activity when reentering after if finishes. When true, + * the transition will start as soon as possible. When false, the transition will wait + * until the called Activity's exiting transition completes before starting. + * @param allow true to start the transition when possible or false to wait until the + * called Activity's exiting transition completes. + */ + public void setAllowExitTransitionOverlap(boolean allow) {} + + /** + * Returns how the transition set in + * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit + * transition of the called Activity when reentering after if finishes. When true, + * the transition will start as soon as possible. When false, the transition will wait + * until the called Activity's exiting transition completes before starting. + * @return true when the transition should start when possible or false when it should wait + * until the called Activity's exiting transition completes. + */ + public boolean getAllowExitTransitionOverlap() { return true; } } diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index 34156e5ec0275..afb6f7ce521be 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -444,9 +444,4 @@ public class ToolbarActionBar extends ActionBar { mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); } } - - @Override - public void captureSharedElements(Map sharedElements) { - mToolbar.findSharedElements(sharedElements); - } } diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index fb93ddda7c2c2..131f82814aa02 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -362,10 +362,6 @@ public class WindowDecorActionBar extends ActionBar { setSubtitle(mContext.getString(resId)); } - public void captureSharedElements(Map sharedElements) { - mContainerView.findSharedElements(sharedElements); - } - public void setSelectedNavigationItem(int position) { switch (mActionView.getNavigationMode()) { case NAVIGATION_MODE_TABS: diff --git a/core/res/res/transition/no_transition.xml b/core/res/res/transition/no_transition.xml new file mode 100644 index 0000000000000..56797388d0001 --- /dev/null +++ b/core/res/res/transition/no_transition.xml @@ -0,0 +1,16 @@ + + + diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index f364bd02b358e..53fed98cd5fe4 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -462,6 +462,41 @@ transitions between different window content. --> + + + + + + + + + + + + + + + + + + @@ -1690,6 +1725,41 @@ or a fraction of the screen size in that dimension. --> + + + + + + + + + + + + + + + + + + @@ -2383,8 +2453,9 @@ when doing an Activity transition. Typically, the elements inside a ViewGroup are each transitioned from the scene individually. The default for a ViewGroup is false unless it has a background. See - {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String)} - for more information. --> + {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, + android.view.View, String)} for more information. Corresponds to + {@link android.view.ViewGroup#setTransitionGroup(boolean)}.--> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 32b674ef02b5f..9712c03685656 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2389,4 +2389,11 @@ + + + + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 563272b94c165..b0f19ecc4d5aa 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1839,5 +1839,6 @@ + diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 79ed8667af99d..2cf94d0ee3e66 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -22,19 +22,6 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.*; -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.app.ActivityOptions; -import android.os.Looper; -import android.transition.Fade; -import android.transition.Scene; -import android.transition.Transition; -import android.transition.TransitionInflater; -import android.transition.TransitionManager; -import android.transition.TransitionSet; -import android.util.ArrayMap; -import android.view.ViewConfiguration; - import com.android.internal.R; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.StandaloneActionMode; @@ -51,6 +38,9 @@ import com.android.internal.widget.ActionBarOverlayLayout; import com.android.internal.widget.ActionBarView; import com.android.internal.widget.SwipeDismissLayout; +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.app.ActivityOptions; import android.app.KeyguardManager; import android.content.Context; import android.content.pm.ActivityInfo; @@ -65,11 +55,22 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; +import android.transition.ChangeBounds; +import android.transition.Explode; +import android.transition.Fade; +import android.transition.MoveImage; +import android.transition.Scene; +import android.transition.Transition; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; +import android.transition.TransitionSet; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -90,6 +91,7 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewManager; import android.view.ViewParent; @@ -124,16 +126,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private final static String TAG = "PhoneWindow"; private final static boolean SWEEP_OPEN_MENU = false; - private static final long MAX_TRANSITION_START_WAIT = 500; - private static final long MAX_TRANSITION_FINISH_WAIT = 1000; - - private static final String KEY_SCREEN_X = "shared_element:screenX"; - private static final String KEY_SCREEN_Y = "shared_element:screenY"; - private static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; - 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"; - /** * Simple callback used by the context menu and its submenus. The options * menu submenus do not use this (their behavior is more complex). @@ -252,12 +244,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } }; - private ActivityOptions mActivityOptions; - private SceneTransitionListener mSceneTransitionListener; - private boolean mAllowEnterOverlap = true; - private boolean mAllowExitOverlap = true; - private Map mSharedElementsMap; - private ArrayList mTransitioningViews; + private Transition mEnterTransition; + private Transition mExitTransition; + private Transition mSharedElementEnterTransition; + private Transition mSharedElementExitTransition; + private Boolean mAllowExitTransitionOverlap; + private Boolean mAllowEnterTransitionOverlap; static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( @@ -410,9 +402,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private void transitionTo(Scene scene) { if (mContentScene == null) { scene.enter(); - if (mActivityOptions != null) { - new EnterScene().start(); - } } else { mTransitionManager.transitionTo(scene); } @@ -3307,20 +3296,55 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Only inflate or create a new TransitionManager if the caller hasn't // already set a custom one. - if (hasFeature(FEATURE_CONTENT_TRANSITIONS) && mTransitionManager == null) { - final int transitionRes = getWindowStyle().getResourceId( - com.android.internal.R.styleable.Window_windowContentTransitionManager, 0); - if (transitionRes != 0) { - final TransitionInflater inflater = TransitionInflater.from(getContext()); - mTransitionManager = inflater.inflateTransitionManager(transitionRes, - mContentParent); - } else { - mTransitionManager = new TransitionManager(); + if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { + if (mTransitionManager == null) { + final int transitionRes = getWindowStyle().getResourceId( + com.android.internal.R.styleable.Window_windowContentTransitionManager, + 0); + if (transitionRes != 0) { + final TransitionInflater inflater = TransitionInflater.from(getContext()); + mTransitionManager = inflater.inflateTransitionManager(transitionRes, + mContentParent); + } else { + mTransitionManager = new TransitionManager(); + } + } + + mEnterTransition = getTransition(mEnterTransition, + com.android.internal.R.styleable.Window_windowEnterTransition); + mExitTransition = getTransition(mExitTransition, + com.android.internal.R.styleable.Window_windowExitTransition); + mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, + com.android.internal.R.styleable.Window_windowSharedElementEnterTransition); + mSharedElementExitTransition = getTransition(mSharedElementExitTransition, + com.android.internal.R.styleable.Window_windowSharedElementExitTransition); + if (mAllowEnterTransitionOverlap == null) { + mAllowEnterTransitionOverlap = getWindowStyle().getBoolean( + com.android.internal.R.styleable. + Window_windowAllowEnterTransitionOverlap, true); + } + if (mAllowExitTransitionOverlap == null) { + mAllowExitTransitionOverlap = getWindowStyle().getBoolean( + com.android.internal.R.styleable. + Window_windowAllowExitTransitionOverlap, true); } } } } + private Transition getTransition(Transition currentValue, int id) { + if (currentValue != null) { + return currentValue; + } + int transitionId = getWindowStyle().getResourceId(id, -1); + Transition transition = null; + if (transitionId != -1 && transitionId != com.android.internal.R.transition.no_transition) { + TransitionInflater inflater = TransitionInflater.from(getContext()); + transition = inflater.inflateTransition(transitionId); + } + return transition; + } + private Drawable loadImageURI(Uri uri) { try { final Context context = getContext(); @@ -3649,6 +3673,66 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { com.android.internal.R.styleable.Window_windowIsTranslucent, 0), false); } + @Override + public void setEnterTransition(Transition enterTransition) { + mEnterTransition = enterTransition; + } + + @Override + public void setExitTransition(Transition exitTransition) { + mExitTransition = exitTransition; + } + + @Override + public void setSharedElementEnterTransition(Transition sharedElementEnterTransition) { + mSharedElementEnterTransition = sharedElementEnterTransition; + } + + @Override + public void setSharedElementExitTransition(Transition sharedElementExitTransition) { + mSharedElementExitTransition = sharedElementExitTransition; + } + + @Override + public Transition getEnterTransition() { + return mEnterTransition; + } + + @Override + public Transition getExitTransition() { + return mExitTransition; + } + + @Override + public Transition getSharedElementEnterTransition() { + return mSharedElementEnterTransition; + } + + @Override + public Transition getSharedElementExitTransition() { + return mSharedElementExitTransition; + } + + @Override + public void setAllowEnterTransitionOverlap(boolean allow) { + mAllowEnterTransitionOverlap = allow; + } + + @Override + public boolean getAllowEnterTransitionOverlap() { + return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap; + } + + @Override + public void setAllowExitTransitionOverlap(boolean allowExitTransitionOverlap) { + mAllowExitTransitionOverlap = allowExitTransitionOverlap; + } + + @Override + public boolean getAllowExitTransitionOverlap() { + return (mAllowExitTransitionOverlap == null) ? true : mAllowExitTransitionOverlap; + } + private static final class DrawableFeatureState { DrawableFeatureState(int _featureId) { featureId = _featureId; @@ -4082,670 +4166,4 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { void sendCloseSystemWindows(String reason) { PhoneWindowManager.sendCloseSystemWindows(getContext(), reason); } - - @Override - public void setTransitionOptions(Bundle options, SceneTransitionListener listener) { - mSceneTransitionListener = listener; - ActivityOptions activityOptions = null; - if (options != null) { - activityOptions = new ActivityOptions(options); - if (activityOptions.getAnimationType() != ActivityOptions.ANIM_SCENE_TRANSITION) { - activityOptions = null; - } - } - mActivityOptions = activityOptions; - } - - @Override - public void setAllowOverlappingEnterTransition(boolean allow) { - mAllowEnterOverlap = allow; - } - - @Override - public void setAllowOverlappingExitTransition(boolean allow) { - mAllowExitOverlap = allow; - } - - @Override - public void mapTransitionTargets(Map sharedElementNames) { - mSharedElementsMap = sharedElementNames; - } - - @Override - public void restoreViewVisibilityAfterTransitionToCallee() { - if (mTransitioningViews != null) { - setViewVisibility(mTransitioningViews, View.VISIBLE); - } - } - - @Override - public void startExitTransitionToCaller(final Runnable onTransitionEnd) { - Transition transition; - if (mContentScene == null || mTransitionManager == null || mActivityOptions == null - || (transition = mTransitionManager.getEnterTransition(mContentScene)) == null) { - onTransitionEnd.run(); - return; - } - if (mAllowExitOverlap) { - TransitionSet transitionSet = new TransitionSet(); - transitionSet.addTransition(transition); - Fade fade = new Fade(); - transitionSet.addTransition(fade); - transition = transitionSet; - } - - final ArrayMap sharedElements = new ArrayMap(); - mapSharedElements(sharedElements); - final Bundle sharedElementArgs = new Bundle(); - captureTerminalSharedElementState(sharedElements, sharedElementArgs); - - final ArrayList transitioningViews = new ArrayList(); - mDecor.captureTransitioningViews(transitioningViews); - transitioningViews.removeAll(sharedElements.values()); - - mSceneTransitionListener.convertToTranslucent(); - transition = transition.clone(); - Rect epicenter = calcEpicenter(sharedElements, mActivityOptions.getSharedElementNames()); - transition.setEpicenterCallback(new FixedEpicenterCallback(epicenter)); - ExitSceneBack exitScene = - new ExitSceneBack(onTransitionEnd, sharedElementArgs, sharedElements.values()); - exitScene.start(transition); - mTransitionManager.beginDelayedTransition(mDecor, transition); - setViewVisibility(transitioningViews, View.INVISIBLE); - } - - @Override - public Bundle startExitTransitionToCallee(Bundle options) { - if (mContentScene == null) { - return null; - } - Transition transition = mTransitionManager.getExitTransition(mContentScene); - if (transition == null) { - return null; - } - - ActivityOptions activityOptions = new ActivityOptions(options); - ArrayMap sharedElements = findSharedElements(activityOptions); - - // Find exiting Views and shared elements - ArrayList transitioningViews = captureTransitioningViews(sharedElements.values()); - - Transition exitTransition = addTransitionTargets(transition, - transitioningViews, true); - Transition sharedElementTransition = addTransitionTargets(transition, - transitioningViews, false); - - // transitionSet is the total exit transition, including hero animation. - TransitionSet transitionSet = new TransitionSet(); - transitionSet.addTransition(exitTransition); - transitionSet.addTransition(sharedElementTransition); - - Rect epicenter = calcEpicenter(sharedElements, activityOptions.getSharedElementNames()); - FixedEpicenterCallback epicenterCallback = new FixedEpicenterCallback(epicenter); - transitionSet.setEpicenterCallback(epicenterCallback); - - updateExitActivityOptions(activityOptions, sharedElements, - sharedElementTransition, transitioningViews, exitTransition, epicenterCallback); - - // Start exiting the Views that need to exit - TransitionManager.beginDelayedTransition(mDecor, transitionSet); - setViewVisibility(transitioningViews, View.INVISIBLE); - - return activityOptions.toBundle(); - } - - private ArrayList captureTransitioningViews(Collection sharedElements) { - mTransitioningViews = new ArrayList(); - mDecor.captureTransitioningViews(mTransitioningViews); - ArrayList transitioningViews = (ArrayList) mTransitioningViews.clone(); - transitioningViews.removeAll(sharedElements); - return transitioningViews; - } - - private ArrayMap findSharedElements(ActivityOptions activityOptions) { - ArrayMap sharedElements = new ArrayMap(); - mDecor.findSharedElements(sharedElements); - ArrayList localNames = activityOptions.getLocalElementNames(); - sharedElements.keySet().retainAll(localNames); - - ArrayList targetNames = activityOptions.getSharedElementNames(); - for (int i = 0; i < localNames.size(); i++) { - String localName = localNames.get(i); - View sharedElement = sharedElements.remove(localName); - String targetName = targetNames.get(i); - sharedElements.put(targetName, sharedElement); - } - return sharedElements; - } - - private static void runOnUiThread(Handler handler, Runnable runnable) { - if (handler.getLooper() != Looper.myLooper()) { - handler.post(runnable); - } else { - runnable.run(); - } - } - - private void updateExitActivityOptions(ActivityOptions activityOptions, - final Map sharedElements, Transition sharedElementTransition, - final ArrayList transitioningViews, Transition exitTransition, - final Transition.EpicenterCallback epicenterCallback) { - - // Schedule capturing of the shared element state - final Bundle sharedElementArgs = new Bundle(); - captureTerminalSharedElementState(sharedElements, sharedElementArgs); - - ActivityOptions.SharedElementSource sharedElementSource - = new ActivityOptions.SharedElementSource() { - private Handler mHandler = new Handler(); - - @Override - public Bundle getSharedElementExitState() { - return sharedElementArgs; - } - - @Override - public void acceptedSharedElements(final ArrayList sharedElementNames) { - if (sharedElementNames.size() == sharedElements.size()) { - return; // They were all accepted - } - runOnUiThread(mHandler, new Runnable() { - @Override - public void run() { - Transition transition = mTransitionManager.getExitTransition(mContentScene); - transition = transition.clone(); - transition.setEpicenterCallback(epicenterCallback); - TransitionManager.beginDelayedTransition(mDecor, transition); - for (String name : sharedElements.keySet()) { - if (!sharedElementNames.contains(name)) { - sharedElements.get(name).setVisibility(View.INVISIBLE); - } - } - sharedElements.keySet().retainAll(sharedElementNames); - } - }); - } - - @Override - public void hideSharedElements() { - if (sharedElements != null) { - runOnUiThread(mHandler, new Runnable() { - @Override - public void run() { - setViewVisibility(sharedElements.values(), View.INVISIBLE); - } - }); - } - } - - @Override - public void restore(final Bundle sharedElementState) { - runOnUiThread(mHandler, new Runnable() { - @Override - public void run() { - mTransitioningViews = null; - Transition transition = mTransitionManager.getExitTransition(mContentScene); - transition = transition.clone(); - transition.setEpicenterCallback(epicenterCallback); - setSharedElementState(sharedElements, sharedElementState); - setViewVisibility(sharedElements.values(), View.VISIBLE); - if (mSceneTransitionListener != null) { - mSceneTransitionListener.sharedElementStart(transition); - mDecor.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - mDecor.getViewTreeObserver().removeOnPreDrawListener(this); - mSceneTransitionListener.sharedElementEnd(); - return true; - } - }); - } - TransitionManager.beginDelayedTransition(mDecor, transition); - setViewVisibility(transitioningViews, View.VISIBLE); - for (View sharedElement: sharedElements.values()) { - sharedElement.requestLayout(); - } - } - }); - } - - @Override - public void prepareForRestore() { - if (mTransitioningViews != null) { - runOnUiThread(mHandler, new Runnable() { - @Override - public void run() { - setViewVisibility(mTransitioningViews, View.INVISIBLE); - } - }); - } - } - }; - - activityOptions.updateSceneTransitionAnimation( - exitTransition, sharedElementTransition, sharedElementSource); - } - - private void captureTerminalSharedElementState(final Map sharedElements, - final Bundle sharedElementArgs) { - mDecor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - mDecor.getViewTreeObserver().removeOnPreDrawListener(this); - int[] tempLoc = new int[2]; - for (String name : sharedElements.keySet()) { - View sharedElement = sharedElements.get(name); - captureSharedElementState(sharedElement, name, sharedElementArgs, tempLoc); - } - return true; - } - }); - } - - private static Transition addTransitionTargets(Transition transition, Collection views, - boolean add) { - TransitionSet set = new TransitionSet(); - set.addTransition(transition.clone()); - for (View view: views) { - if (add) { - set.addTarget(view); - } else { - set.excludeTarget(view, true); - } - } - return set; - } - - private static void setViewVisibility(Collection views, int visibility) { - for (View view : views) { - view.setVisibility(visibility); - } - } - - private static void setSharedElementState(Map sharedElements, - Bundle sharedElementState) { - int[] tempLoc = new int[2]; - for (Map.Entry entry: sharedElements.entrySet()) { - setSharedElementState(entry.getValue(), entry.getKey(), sharedElementState, tempLoc); - } - } - - /** - * Sets the captured values from a previous - * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])} - * @param view The View to apply placement changes to. - * @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. - */ - private static void setSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { - Bundle sharedElementBundle = transitionArgs.getBundle(name); - if (sharedElementBundle == null) { - return; - } - - float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); - view.setTranslationZ(z); - - int x = sharedElementBundle.getInt(KEY_SCREEN_X); - int y = sharedElementBundle.getInt(KEY_SCREEN_Y); - int width = sharedElementBundle.getInt(KEY_WIDTH); - int height = sharedElementBundle.getInt(KEY_HEIGHT); - - int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - 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 right = left + width; - int bottom = top + height; - view.layout(left, top, right, bottom); - - view.requestLayout(); - } - - /** - * Captures placement information for Views with a shared element name for - * Activity Transitions. - * @param view The View to capture the placement information for. - * @param name The shared element name in the target Activity to apply the placement - * information for. - * @param transitionArgs Bundle to store shared element placement information. - * @param tempLoc A temporary int[2] for capturing the current location of views. - * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[]) - */ - private static void captureSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { - Bundle sharedElementBundle = new Bundle(); - view.getLocationOnScreen(tempLoc); - float scaleX = view.getScaleX(); - sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); - int width = Math.round(view.getWidth() * scaleX); - sharedElementBundle.putInt(KEY_WIDTH, width); - - float scaleY = view.getScaleY(); - sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); - int height= Math.round(view.getHeight() * scaleY); - sharedElementBundle.putInt(KEY_HEIGHT, height); - - sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); - - sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); - - transitionArgs.putBundle(name, sharedElementBundle); - } - - private void mapSharedElements(ArrayMap sharedElements) { - ArrayList sharedElementNames = mActivityOptions.getSharedElementNames(); - if (sharedElementNames != null) { - mDecor.findSharedElements(sharedElements); - if (mSharedElementsMap != null) { - for (Map.Entry entry : mSharedElementsMap.entrySet()) { - View sharedElement = sharedElements.remove(entry.getValue()); - if (sharedElement != null) { - sharedElements.put(entry.getKey(), sharedElement); - } - } - } - sharedElements.keySet().retainAll(sharedElementNames); - } - } - - private static Rect calcEpicenter(ArrayMap sharedElements, - ArrayList sharedElementNames) { - if (sharedElementNames != null) { - for (String name: sharedElementNames) { - if (name.startsWith("android:")) { - return null; - } - View view = sharedElements.get(name); - if (view != null) { - int[] loc = new int[2]; - view.getLocationOnScreen(loc); - int left = loc[0] + Math.round(view.getTranslationX()); - int top = loc[1] + Math.round(view.getTranslationY()); - int right = left + view.getWidth(); - int bottom = top + view.getHeight(); - return new Rect(left, top, right, bottom); - } - } - } - return null; - } - - private class ExitSceneBack extends Transition.TransitionListenerAdapter implements - Animator.AnimatorListener { - private boolean mExitTransitionComplete; - private boolean mBackgroundFadeComplete; - private boolean mOnCompleteExecuted; - private boolean mSharedElementTransitioned; - private Runnable mOnComplete; - private Bundle mSharedElementArgs; - private Collection mSharedElements; - - public ExitSceneBack(Runnable onComplete, Bundle sharedElementArgs, - Collection sharedElements) { - mOnComplete = onComplete; - mSharedElementArgs = sharedElementArgs; - mSharedElements = sharedElements; - } - - public void start(Transition exitTransition) { - if (mActivityOptions != null) { - mActivityOptions.dispatchPrepareRestore(); - } - exitTransition.addListener(this); - Drawable background = mDecor.getBackground(); - if (background != null) { - ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 0); - animator.addListener(this); - animator.start(); - } else { - mBackgroundFadeComplete = true; - startCalledActivityEnter(); - } - } - - @Override - public void onTransitionEnd(Transition transition) { - transition.removeListener(this); - mExitTransitionComplete = true; - notifyComplete(); - if (!mAllowExitOverlap) { - startCalledActivityEnter(); - } - } - - private void notifyComplete() { - if (mExitTransitionComplete && mBackgroundFadeComplete - && mSharedElementTransitioned && !mOnCompleteExecuted) { - mOnComplete.run(); - mSceneTransitionListener.nullPendingTransition(); - mOnCompleteExecuted = true; - } - } - - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mBackgroundFadeComplete = true; - if (mAllowExitOverlap) { - startCalledActivityEnter(); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - private void startCalledActivityEnter() { - mActivityOptions.dispatchRestore(mSharedElementArgs); - setViewVisibility(mSharedElements, View.INVISIBLE); - mSharedElementTransitioned = true; - notifyComplete(); - } - } - - /** - * Provides code for handling the Activity transition entering scene. - * When the first scene is laid out (onPreDraw), it makes views invisible. - * It then starts the entering transition by making non-shared elements visible. When - * the entering transition is started, the calling Activity is notified that - * this Activity is ready to receive the shared element. When the calling Activity notifies - * that the shared element is ready, this Activity is notified through the - * SceneTransitionListener. - * - * This class also takes into account fading the background -- either waiting until the - * shared element is ready or the calling Activity's exit transition is complete. - */ - private class EnterScene implements ViewTreeObserver.OnPreDrawListener, Runnable, - ActivityOptions.ActivityTransitionTarget, Animator.AnimatorListener { - private boolean mSharedElementReadyReceived; - private boolean mAllDone; - private Handler mHandler = new Handler(); - private boolean mEnterTransitionStarted; - private ArrayMap mSharedElementTargets = new ArrayMap(); - private ArrayList mEnteringViews = new ArrayList(); - private Transition.EpicenterCallback mEpicenterCallback; - - public EnterScene() { - mSceneTransitionListener.nullPendingTransition(); - Drawable background = getDecorView().getBackground(); - if (background != null) { - background.setAlpha(0); - mDecor.drawableChanged(); - } - mSceneTransitionListener.convertToTranslucent(); - } - - @Override - public boolean onPreDraw() { - ViewTreeObserver observer = mDecor.getViewTreeObserver(); - observer.removeOnPreDrawListener(this); - if (!mEnterTransitionStarted && mSceneTransitionListener != null) { - mEnterTransitionStarted = true; - mDecor.captureTransitioningViews(mEnteringViews); - mapSharedElements(mSharedElementTargets); - mEnteringViews.removeAll(mSharedElementTargets.values()); - Rect epicenter = calcEpicenter(mSharedElementTargets, - mActivityOptions.getSharedElementNames()); - mEpicenterCallback = new FixedEpicenterCallback(epicenter); - - setViewVisibility(mEnteringViews, View.INVISIBLE); - setViewVisibility(mSharedElementTargets.values(), View.INVISIBLE); - if (mAllowEnterOverlap) { - beginEnterScene(); - } - observer.addOnPreDrawListener(this); - return false; - } else { - mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT); - mActivityOptions.dispatchSceneTransitionStarted(this, - new ArrayList(mSharedElementTargets.keySet())); - return !mSharedElementReadyReceived; - } - } - - public void start() { - ViewTreeObserver observer = mDecor.getViewTreeObserver(); - observer.addOnPreDrawListener(this); - } - - @Override - public void run() { - exitTransitionComplete(); - } - - @Override - public void sharedElementTransitionComplete(final Bundle transitionArgs) { - if (!mSharedElementReadyReceived) { - mSharedElementReadyReceived = true; - mHandler.removeCallbacks(this); - mHandler.postDelayed(this, MAX_TRANSITION_FINISH_WAIT); - if (!mSharedElementTargets.isEmpty()) { - runOnUiThread(mHandler, new Runnable() { - @Override - public void run() { - Transition transition = getTransitionManager().getEnterTransition( - mContentScene); - if (transition == null) { - transition = TransitionManager.getDefaultTransition(); - } - transition = addTransitionTargets(transition, - mSharedElementTargets.values(), - true); - transition.setEpicenterCallback(mEpicenterCallback); - if (transitionArgs == null) { - TransitionManager.beginDelayedTransition(mDecor, transition); - setViewVisibility(mSharedElementTargets.values(), View.VISIBLE); - } else { - mSceneTransitionListener.sharedElementStart(transition); - setSharedElementState(mSharedElementTargets, transitionArgs); - setViewVisibility(mSharedElementTargets.values(), View.VISIBLE); - mDecor.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - mDecor.getViewTreeObserver() - .removeOnPreDrawListener(this); - mSceneTransitionListener.sharedElementEnd(); - mActivityOptions.dispatchSharedElementsReady(); - return true; - } - }); - TransitionManager.beginDelayedTransition(mDecor, transition); - } - } - }); - } - if (mAllowEnterOverlap) { - fadeInBackground(); - } - } - } - - private void fadeInBackground() { - Drawable background = getDecorView().getBackground(); - if (background == null) { - mSceneTransitionListener.convertFromTranslucent(); - } else { - ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255); - animator.addListener(this); - animator.start(); - } - } - - @Override - public void exitTransitionComplete() { - if (mAllDone) { - return; - } - mAllDone = true; - sharedElementTransitionComplete(null); - mHandler.removeCallbacks(this); - if (!mAllowEnterOverlap) { - runOnUiThread(mHandler, new Runnable() { - @Override - public void run() { - beginEnterScene(); - fadeInBackground(); - } - }); - } - } - - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mSceneTransitionListener.convertFromTranslucent(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - private void beginEnterScene() { - Transition transition = getTransitionManager().getEnterTransition(mContentScene); - if (transition == null) { - transition = TransitionManager.getDefaultTransition(); - } - transition = addTransitionTargets(transition, mEnteringViews, true); - transition.setEpicenterCallback(mEpicenterCallback); - TransitionManager.beginDelayedTransition(mDecor, transition); - setViewVisibility(mEnteringViews, View.VISIBLE); - } - } - - private static class FixedEpicenterCallback extends Transition.EpicenterCallback { - private Rect mEpicenter; - - public FixedEpicenterCallback(Rect epicenter) { - mEpicenter = epicenter; - } - - @Override - public Rect getEpicenter(Transition transition) { - return mEpicenter; - } - }; }