diff options
-rw-r--r-- | api/current.txt | 30 | ||||
-rw-r--r-- | core/java/android/app/Activity.java | 81 | ||||
-rw-r--r-- | core/java/android/app/ActivityOptions.java | 269 | ||||
-rw-r--r-- | core/java/android/app/ActivityTransitionCoordinator.java | 824 | ||||
-rw-r--r-- | core/java/android/app/ActivityTransitionState.java | 204 | ||||
-rw-r--r-- | core/java/android/app/EnterTransitionCoordinator.java | 542 | ||||
-rw-r--r-- | core/java/android/app/ExitTransitionCoordinator.java | 308 | ||||
-rw-r--r-- | core/java/android/app/SharedElementListener.java | 87 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 4 | ||||
-rw-r--r-- | core/res/res/values/attrs.xml | 2 |
10 files changed, 1140 insertions, 1211 deletions
diff --git a/api/current.txt b/api/current.txt index 5a25b12..799221a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3257,6 +3257,7 @@ package android.app { method public boolean navigateUpToFromChild(android.app.Activity, android.content.Intent); method public void onActionModeFinished(android.view.ActionMode); method public void onActionModeStarted(android.view.ActionMode); + method protected void onActivityReenter(int, android.content.Intent); method protected void onActivityResult(int, int, android.content.Intent); method public void onAttachFragment(android.app.Fragment); method public void onAttachedToWindow(); @@ -3335,7 +3336,6 @@ package android.app { method public final boolean requestWindowFeature(int); method public final void runOnUiThread(java.lang.Runnable); method public void setActionBar(android.widget.Toolbar); - 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); @@ -3357,6 +3357,7 @@ package android.app { method public final void setResult(int); method public final void setResult(int, android.content.Intent); method public final void setSecondaryProgress(int); + method public void setSharedElementListener(android.app.SharedElementListener); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); @@ -3576,28 +3577,13 @@ 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.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 makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String); + method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View, java.lang.String>...); 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<android.view.View, java.lang.String>[] getSharedElementsMapping(); - method public boolean handleRejectedSharedElements(java.util.List<android.view.View>); - method public void onCaptureSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>); - method public void onCaptureSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>); - method public void onEnterReady(); - method public void onExitTransitionComplete(); - method public void onRemoteExitComplete(); - method public void onSharedElementExitTransitionComplete(); - method public void onSharedElementTransferred(java.util.List<java.lang.String>, java.util.List<android.view.View>); - method public void onStartEnterTransition(java.util.List<java.lang.String>, java.util.List<android.view.View>); - method public void onStartExitTransition(java.util.List<java.lang.String>, java.util.List<android.view.View>); - } - public class AlarmManager { method public void cancel(android.app.PendingIntent); method public void set(int, long, android.app.PendingIntent); @@ -4821,6 +4807,14 @@ package android.app { field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0 } + public class SharedElementListener { + ctor public SharedElementListener(); + method public void handleRejectedSharedElements(java.util.List<android.view.View>); + method public void remapSharedElements(java.util.List<java.lang.String>, java.util.Map<java.lang.String, android.view.View>); + method public void setSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>); + method public void setSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>); + } + public deprecated class TabActivity extends android.app.ActivityGroup { ctor public TabActivity(); method public android.widget.TabHost getTabHost(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4a30b05..36c36a8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -777,8 +777,9 @@ public class Activity extends ContextThemeWrapper private Thread mUiThread; final Handler mHandler = new Handler(); - private ActivityOptions mCalledActivityOptions; - private EnterTransitionCoordinator mEnterTransitionCoordinator; + + private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState(); + SharedElementListener mTransitionListener = new SharedElementListener(); /** Return the intent that started this activity. */ public Intent getIntent() { @@ -1100,9 +1101,6 @@ public class Activity extends ContextThemeWrapper mTitleReady = true; onTitleChanged(getTitle(), getTitleColor()); } - if (mEnterTransitionCoordinator != null) { - mEnterTransitionCoordinator.readyToEnter(); - } mCalled = true; } @@ -1149,12 +1147,6 @@ public class Activity extends ContextThemeWrapper } getApplication().dispatchActivityStarted(this); - - final ActivityOptions activityOptions = getActivityOptions(); - if (activityOptions != null && - activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { - mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this); - } } /** @@ -1204,7 +1196,6 @@ public class Activity extends ContextThemeWrapper protected void onResume() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); getApplication().dispatchActivityResumed(this); - mCalledActivityOptions = null; mCalled = true; } @@ -1279,6 +1270,7 @@ public class Activity extends ContextThemeWrapper final void performSaveInstanceState(Bundle outState) { onSaveInstanceState(outState); saveManagedDialogs(outState); + mActivityTransitionState.saveState(outState); if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState); } @@ -1549,10 +1541,7 @@ public class Activity extends ContextThemeWrapper protected void onStop() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); - if (mCalledActivityOptions != null) { - mCalledActivityOptions.dispatchActivityStopped(); - mCalledActivityOptions = null; - } + mActivityTransitionState.onStop(); getApplication().dispatchActivityStopped(this); mTranslucentCallback = null; mCalled = true; @@ -3650,7 +3639,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(mWindow, null).toBundle(); + options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle(); } startActivityForResult(intent, requestCode, options); } @@ -3691,9 +3680,7 @@ public class Activity extends ContextThemeWrapper */ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (options != null) { - ActivityOptions activityOptions = new ActivityOptions(options); - activityOptions.dispatchStartExit(); - mCalledActivityOptions = activityOptions; + mActivityTransitionState.startExitOutTransition(this, options); } if (mParent == null) { Instrumentation.ActivityResult ar = @@ -4559,13 +4546,10 @@ 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.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window, - * android.app.ActivityOptions.ActivityTransitionListener) + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[]) */ public void finishWithTransition() { - if (mEnterTransitionCoordinator != null) { - mEnterTransitionCoordinator.startExit(); - } else { + if (!mActivityTransitionState.startExitBackTransition(this)) { finish(); } } @@ -4643,6 +4627,27 @@ public class Activity extends ContextThemeWrapper } /** + * Called when an activity you launched with an activity transition exposes this + * Activity through a returning activity transition, giving you the resultCode + * and any additional data from it. This method will only be called if the activity + * set a result code other than {@link #RESULT_CANCELED} and it supports activity + * transitions with {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * <p>The purpose of this function is to let the called Activity send a hint about + * its state so that this underlying Activity can prepare to be exposed. A call to + * this method does not guarantee that the called Activity has or will be exiting soon. + * It only indicates that it will expose this Activity's Window and it has + * some data to pass to prepare it.</p> + * + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + */ + protected void onActivityReenter(int resultCode, Intent data) { + } + + /** * Create a new PendingIntent object which you can hand to others * for them to use to send result data back to your * {@link #onActivityResult} callback. The created object will be either @@ -5246,7 +5251,8 @@ public class Activity extends ContextThemeWrapper * This call has no effect on non-translucent activities or on activities with the * {@link android.R.attr#windowIsFloating} attribute. * - * @see #convertToTranslucent(TranslucentConversionListener) + * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener, + * ActivityOptions) * @see TranslucentConversionListener * * @hide @@ -5544,18 +5550,18 @@ 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. <code>listener</code> 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}. + * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, + * android.view.View, String)} was used to start an Activity, <var>listener</var> + * will be called to handle shared elements. This requires + * {@link Window#FEATURE_CONTENT_TRANSITIONS}. * - * @param listener Used to listen to events in the entering transition. + * @param listener Used to manipulate how shared element transitions function. */ - public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) { - if (mEnterTransitionCoordinator != null) { - mEnterTransitionCoordinator.setActivityTransitionListener(listener); + public void setSharedElementListener(SharedElementListener listener) { + if (listener == null) { + listener = new SharedElementListener(); } + mTransitionListener = listener; } // ------------------ Internal API ------------------ @@ -5621,19 +5627,23 @@ public class Activity extends ContextThemeWrapper mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); mFragments.dispatchActivityCreated(); + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); } final void performCreate(Bundle icicle) { onCreate(icicle); + mActivityTransitionState.readState(icicle); performCreateCommon(); } final void performCreate(Bundle icicle, PersistableBundle persistentState) { onCreate(icicle, persistentState); + mActivityTransitionState.readState(icicle); performCreateCommon(); } final void performStart() { + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); @@ -5656,6 +5666,7 @@ public class Activity extends ContextThemeWrapper lm.doReportStart(); } } + mActivityTransitionState.enterReady(this); } final void performRestart() { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 692efd7..a057c3e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -17,18 +17,18 @@ package android.app; import android.content.Context; +import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; -import android.transition.Transition; -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,6 +108,12 @@ public class ActivityOptions { private static final String KEY_TRANSITION_COMPLETE_LISTENER = "android:transitionCompleteListener"; + private static final String KEY_TRANSITION_IS_RETURNING = "android:transitionIsReturning"; + private static final String KEY_TRANSITION_SHARED_ELEMENTS = "android:sharedElementNames"; + private static final String KEY_LOCAL_SHARED_ELEMENTS = "android:localSharedElementNames"; + private static final String KEY_RESULT_DATA = "android:resultData"; + private static final String KEY_RESULT_CODE = "android:resultCode"; + /** @hide */ public static final int ANIM_NONE = 0; /** @hide */ @@ -131,7 +137,12 @@ public class ActivityOptions { private int mStartWidth; private int mStartHeight; private IRemoteCallback mAnimationStartedListener; - private ResultReceiver mExitReceiver; + private ResultReceiver mTransitionReceiver; + private boolean mIsReturning; + private ArrayList<String> mSharedElementNames; + private ArrayList<String> mLocalSharedElementNames; + private Intent mResultData; + private int mResultCode; /** * Create an ActivityOptions specifying a custom animation to run when @@ -334,7 +345,7 @@ public class ActivityOptions { * <p>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.</p> - * @param window The window containing shared elements. + * @param activity The Activity whose window contains the 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 @@ -344,40 +355,70 @@ public class ActivityOptions { * @see android.transition.Transition#setEpicenterCallback( * android.transition.Transition.EpicenterCallback) */ - public static ActivityOptions makeSceneTransitionAnimation(Window window, + public static ActivityOptions makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName) { - return makeSceneTransitionAnimation(window, - new SharedElementMappingListener(sharedElement, sharedElementName)); + return makeSceneTransitionAnimation(activity, Pair.create(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. The position of the first element in the value returned from - * {@link android.app.ActivityOptions.ActivityTransitionListener#getSharedElementsMapping()} + * Activity. The position of the first element in sharedElements * 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. * * <p>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.</p> - * @param window The window containing shared elements. - * @param listener The listener to use to monitor activity transition events. + * @param activity The Activity whose window contains the shared elements. + * @param sharedElements The names of the shared elements to transfer to the called + * Activity and their associated Views. The Views must each have + * a unique shared element name. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. * Returns null if the Window does not have {@link Window#FEATURE_CONTENT_TRANSITIONS}. * @see android.transition.Transition#setEpicenterCallback( * android.transition.Transition.EpicenterCallback) */ - public static ActivityOptions makeSceneTransitionAnimation(Window window, - ActivityTransitionListener listener) { - if (!window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + public static ActivityOptions makeSceneTransitionAnimation(Activity activity, + Pair<View, String>... sharedElements) { + if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { return null; } ActivityOptions opts = new ActivityOptions(); opts.mAnimationType = ANIM_SCENE_TRANSITION; - ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener); - opts.mExitReceiver = exit; + + ArrayList<String> names = new ArrayList<String>(); + ArrayList<String> mappedNames = new ArrayList<String>(); + + if (sharedElements != null) { + for (int i = 0; i < sharedElements.length; i++) { + Pair<View, String> sharedElement = sharedElements[i]; + names.add(sharedElement.second); + mappedNames.add(sharedElement.first.getViewName()); + } + } + + ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names, + mappedNames, false); + opts.mTransitionReceiver = exit; + opts.mSharedElementNames = names; + opts.mLocalSharedElementNames = mappedNames; + opts.mIsReturning = false; + return opts; + } + + /** @hide */ + public static ActivityOptions makeSceneTransitionAnimation(Activity activity, + ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames, + int resultCode, Intent resultData) { + ActivityOptions opts = new ActivityOptions(); + opts.mAnimationType = ANIM_SCENE_TRANSITION; + opts.mSharedElementNames = sharedElementNames; + opts.mTransitionReceiver = exitCoordinator; + opts.mIsReturning = true; + opts.mResultCode = resultCode; + opts.mResultData = resultData; return opts; } @@ -413,7 +454,12 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: - mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER); + mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER); + mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false); + mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS); + mLocalSharedElementNames = opts.getStringArrayList(KEY_LOCAL_SHARED_ELEMENTS); + mResultData = opts.getParcelable(KEY_RESULT_DATA); + mResultCode = opts.getInt(KEY_RESULT_CODE); break; } } @@ -470,15 +516,15 @@ public class ActivityOptions { /** @hide */ public void dispatchActivityStopped() { - if (mExitReceiver != null) { - mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null); + if (mTransitionReceiver != null) { + mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null); } } /** @hide */ public void dispatchStartExit() { - if (mExitReceiver != null) { - mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null); + if (mTransitionReceiver != null) { + mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null); } } @@ -493,19 +539,37 @@ public class ActivityOptions { } /** @hide */ - public static void abort(Bundle options) { - if (options != null) { - (new ActivityOptions(options)).abort(); - } + public void setReturning() { + mIsReturning = true; } /** @hide */ - public EnterTransitionCoordinator createEnterActivityTransition(Activity activity) { - EnterTransitionCoordinator coordinator = null; - if (mAnimationType == ANIM_SCENE_TRANSITION) { - coordinator = new EnterTransitionCoordinator(activity, mExitReceiver); + public boolean isReturning() { + return mIsReturning; + } + + /** @hide */ + public ArrayList<String> getSharedElementNames() { + return mSharedElementNames; + } + + /** @hide */ + public ArrayList<String> getLocalSharedElementNames() { return mLocalSharedElementNames; } + + /** @hide */ + public ResultReceiver getResultReceiver() { return mTransitionReceiver; } + + /** @hide */ + public int getResultCode() { return mResultCode; } + + /** @hide */ + public Intent getResultData() { return mResultData; } + + /** @hide */ + public static void abort(Bundle options) { + if (options != null) { + (new ActivityOptions(options)).abort(); } - return coordinator; } /** @@ -517,7 +581,12 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } - mExitReceiver = null; + mTransitionReceiver = null; + mSharedElementNames = null; + mLocalSharedElementNames = null; + mIsReturning = false; + mResultData = null; + mResultCode = 0; switch (otherOptions.mAnimationType) { case ANIM_CUSTOM: mAnimationType = otherOptions.mAnimationType; @@ -562,9 +631,14 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: mAnimationType = otherOptions.mAnimationType; - mExitReceiver = otherOptions.mExitReceiver; + mTransitionReceiver = otherOptions.mTransitionReceiver; + mSharedElementNames = otherOptions.mSharedElementNames; + mLocalSharedElementNames = otherOptions.mLocalSharedElementNames; + mIsReturning = otherOptions.mIsReturning; mThumbnail = null; mAnimationStartedListener = null; + mResultData = otherOptions.mResultData; + mResultCode = otherOptions.mResultCode; break; } } @@ -608,9 +682,14 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: b.putInt(KEY_ANIM_TYPE, mAnimationType); - if (mExitReceiver != null) { - b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver); + if (mTransitionReceiver != null) { + b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver); } + b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning); + b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames); + b.putStringArrayList(KEY_LOCAL_SHARED_ELEMENTS, mLocalSharedElementNames); + b.putParcelable(KEY_RESULT_DATA, mResultData); + b.putInt(KEY_RESULT_CODE, mResultCode); break; } return b; @@ -630,126 +709,4 @@ public class ActivityOptions { return null; } - /** - * 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. - * - * @param sharedElementNames The names of the shared elements that were accepted into - * the View hierarchy. - * @param sharedElements The shared elements that are part of the View hierarchy. - * @param sharedElementSnapshots The Views containing snap shots of the shared element - * from the launching Window. These elements will not - * be part of the scene, but will be positioned relative - * to the Window decor View. - */ - public void onCaptureSharedElementStart(List<String> sharedElementNames, - List<View> sharedElements, List<View> sharedElementSnapshots) {} - - /** - * Called when the end state for shared elements is captured on enter. - * - * @param sharedElementNames The names of the shared elements that were accepted into - * the View hierarchy. - * @param sharedElements The shared elements that are part of the View hierarchy. - * @param sharedElementSnapshots The Views containing snap shots of the shared element - * from the launching Window. These elements will not - * be part of the scene, but will be positioned relative - * to the Window decor View. - */ - public void onCaptureSharedElementEnd(List<String> sharedElementNames, - List<View> sharedElements, List<View> sharedElementSnapshots) {} - - /** - * 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<String> sharedElementNames, - List<View> 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<String> sharedElementNames, - List<View> 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<String> sharedElementNames, - List<View> 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<View, String>[] getSharedElementsMapping() { return null; } - - /** - * Returns <code>true</code> if the ActivityTransitionListener will handle removing - * rejected shared elements from the scene. If <code>false</code> is returned, a default - * animation will be used to remove the rejected shared elements from the scene. - * - * @param rejectedSharedElements Views containing visual information of shared elements - * that are not part of the entering scene. These Views - * are positioned relative to the Window decor View. - * @return <code>false</code> if the default animation should be used to remove the - * rejected shared elements from the scene or <code>true</code> if the listener provides - * custom handling. - */ - public boolean handleRejectedSharedElements(List<View> rejectedSharedElements) { - return false; - } - } - - private static class SharedElementMappingListener extends ActivityTransitionListener { - Pair<View, String>[] mSharedElementsMapping = new Pair[1]; - - public SharedElementMappingListener(View view, String name) { - mSharedElementsMapping[0] = Pair.create(view, name); - } - - @Override - public Pair<View, String>[] getSharedElementsMapping() { - return mSharedElementsMapping; - } - } } diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index ca64788..6c6a52f 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -15,27 +15,14 @@ */ package android.app; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; 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.util.SparseArray; import android.view.View; import android.view.ViewGroup; -import android.view.ViewGroupOverlay; -import android.view.ViewTreeObserver; import android.view.Window; import android.widget.ImageView; @@ -83,14 +70,13 @@ import java.util.Collection; * - 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 + * 1) finishWithTransition() creates an ExitTransitionCoordinator and calls startExit() + * - The Window start transitioning to Translucent with a new ActivityOptions. * - 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 + * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created. * - 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 @@ -98,21 +84,21 @@ import java.util.Collection; * 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 + * EnterTransitionCoordinator + * 6) MSG_TAKE_SHARED_ELEMENTS is received by 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. - * - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator - * 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator + * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator + * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator * - The shared elements are made INVISIBLE * 8) The exit transition completes in the finishing Activity. - * - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator. + * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. * - finish() is called on the exiting Activity - * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator. + * 9) 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. */ @@ -120,30 +106,24 @@ 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"; + static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver"; + + protected static final String KEY_SCREEN_X = "shared_element:screenX"; + protected static final String KEY_SCREEN_Y = "shared_element:screenY"; + protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; + protected static final String KEY_WIDTH = "shared_element:width"; + protected static final String KEY_HEIGHT = "shared_element:height"; + protected static final String KEY_BITMAP = "shared_element:bitmap"; + protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; + protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; - 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"; - private static final String KEY_BITMAP = "shared_element:bitmap"; - private static final String KEY_SCALE_TYPE = "shared_element:scaleType"; - private static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; + // The background fade in/out duration. 150ms is pretty quick, but not abrupt. + public static final int FADE_BACKGROUND_DURATION_MS = 150; - private static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); + protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); /** * Sent by the exiting coordinator (either EnterTransitionCoordinator @@ -154,7 +134,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * 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; + public static final int MSG_SET_REMOTE_RECEIVER = 100; /** * Sent by the entering coordinator to tell the exiting coordinator @@ -165,17 +145,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { 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; + public static final int MSG_ACTIVITY_STOPPED = 102; /** * Sent by the exiting coordinator (either EnterTransitionCoordinator @@ -186,7 +159,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * 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; + public static final int MSG_TAKE_SHARED_ELEMENTS = 103; /** * Sent by the exiting coordinator (either @@ -196,309 +169,41 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * remote coordinator. If it is false, it will trigger the enter * transition to start. */ - public static final int MSG_EXIT_TRANSITION_COMPLETE = 105; + public static final int MSG_EXIT_TRANSITION_COMPLETE = 104; /** * Sent by Activity#startActivity to begin the exit transition. */ - public static final int MSG_START_EXIT_TRANSITION = 106; - - private Window mWindow; - private ArrayList<View> mSharedElements = new ArrayList<View>(); - private ArrayList<String> mTargetSharedNames = new ArrayList<String>(); - private ActivityOptions.ActivityTransitionListener mListener = - new ActivityOptions.ActivityTransitionListener(); - private ArrayList<View> mEnteringViews; - private ResultReceiver mRemoteResultReceiver; - private boolean mNotifiedSharedElementTransitionComplete; - private boolean mNotifiedExitTransitionComplete; - private boolean mSharedElementTransitionStarted; - - 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: - if (!mSharedElementTransitionStarted) { - send(resultCode, resultData); - } else { - onRemoteSceneExitComplete(); - } - break; - case MSG_TAKE_SHARED_ELEMENTS: - ArrayList<String> 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<String> sharedElementNames, Bundle state) { - setSharedElements(); - reconcileSharedElements(sharedElementNames); - mEnteringViews.removeAll(mSharedElements); - final ArrayList<View> accepted = new ArrayList<View>(); - final ArrayList<View> rejected = new ArrayList<View>(); - createSharedElementImages(accepted, rejected, sharedElementNames, state); - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = - setSharedElementState(state, accepted); - handleRejected(rejected); - - if (getViewsTransition() != null) { - setViewVisibility(mEnteringViews, View.INVISIBLE); - } - setViewVisibility(mSharedElements, View.VISIBLE); - Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(), - true); - setOriginalImageViewState(originalImageViewState); - - if (allowOverlappingTransitions()) { - onStartEnterTransition(transition, mEnteringViews); - } - - mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); - } + public static final int MSG_START_EXIT_TRANSITION = 105; /** - * Called when MSG_ACTIVITY_STOPPED is received. This is received when Activity.onStop is - * called after running startActivity* is called using an Activity Transition. + * It took too long for a message from the entering Activity, so we canceled the 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<View> enteringViews) { + public static final int MSG_CANCEL = 106; + + final private Window mWindow; + final protected ArrayList<String> mAllSharedElementNames; + final protected ArrayList<View> mSharedElements = new ArrayList<View>(); + final protected ArrayList<String> mSharedElementNames = new ArrayList<String>(); + final protected ArrayList<View> mTransitioningViews = new ArrayList<View>(); + final protected SharedElementListener mListener; + protected ResultReceiver mResultReceiver; + final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); + + public ActivityTransitionCoordinator(Window window, + ArrayList<String> allSharedElementNames, + ArrayList<String> accepted, ArrayList<String> localNames, + SharedElementListener listener) { + super(new Handler()); + mWindow = window; + mListener = listener; + mAllSharedElementNames = allSharedElementNames; + setSharedElements(accepted, localNames); if (getViewsTransition() != null) { - setViewVisibility(enteringViews, View.VISIBLE); + getDecor().captureTransitioningViews(mTransitioningViews); + mTransitioningViews.removeAll(mSharedElements); } - 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<View> 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<View> views) { - mEnteringViews = views; - } - - protected void setSharedElements() { - Pair<View, String>[] sharedElements = mListener.getSharedElementsMapping(); - mSharedElements.clear(); - mTargetSharedNames.clear(); - if (sharedElements == null) { - ArrayMap<String, View> map = new ArrayMap<String, View>(); - if (getViewsTransition() != null) { - setViewVisibility(mEnteringViews, View.VISIBLE); - } - getDecor().findNamedViews(map); - if (getViewsTransition() != null) { - 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<View, String> viewStringPair = sharedElements[i]; - View view = viewStringPair.first; - String name = viewStringPair.second; - mSharedElements.add(view); - mTargetSharedNames.add(name); - } - } - } - - protected ArrayList<View> getSharedElements() { - return mSharedElements; - } - - protected ArrayList<String> getSharedElementNames() { - return mTargetSharedNames; + setEpicenter(); } protected Window getWindow() { @@ -509,238 +214,43 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); } - protected void startExitTransition(ArrayList<String> sharedElements) { - setSharedElements(); - reconcileSharedElements(sharedElements); - ArrayList<View> transitioningViews = captureTransitioningViews(); - beginTransition(transitioningViews, true, true, false); - onStartExitTransition(transitioningViews); - if (getViewsTransition() != null) { - setViewVisibility(transitioningViews, View.INVISIBLE); + /** + * Sets the transition epicenter to the position of the first shared element. + */ + protected void setEpicenter() { + View epicenter = null; + if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty() && + mAllSharedElementNames.get(0).equals(mSharedElementNames.get(0))) { + epicenter = mSharedElements.get(0); } - mListener.onStartExitTransition(getSharedElementNames(), getSharedElements()); - } - - protected void clearConnections() { - mRemoteResultReceiver = null; + setEpicenter(epicenter); } - // public API - - public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) { - if (listener == null) { - mListener = new ActivityOptions.ActivityTransitionListener(); + private void setEpicenter(View view) { + if (view == null) { + mEpicenterCallback.setEpicenter(null); } else { - mListener = listener; + 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(); + Rect epicenter = new Rect(left, top, right, bottom); + mEpicenterCallback.setEpicenter(epicenter); } } - // private methods - - private Transition configureTransition(Transition transition) { - if (transition != null) { - transition = transition.clone(); - transition.setEpicenterCallback(mEpicenterCallback); - } - return transition; - } - - private void reconcileSharedElements(ArrayList<String> sharedElementNames) { - // keep only those that are in sharedElementNames. - int numSharedElements = sharedElementNames.size(); - int targetIndex = 0; - for (int i = 0; i < numSharedElements; i++) { - String name = sharedElementNames.get(i); - int index = mTargetSharedNames.indexOf(name); - if (index >= 0) { - // Swap the items at the indexes if necessary. - if (index != targetIndex) { - View temp = mSharedElements.get(index); - mSharedElements.set(index, mSharedElements.get(targetIndex)); - mSharedElements.set(targetIndex, temp); - mTargetSharedNames.set(index, mTargetSharedNames.get(targetIndex)); - mTargetSharedNames.set(targetIndex, name); - } - targetIndex++; - } - } - for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) { - mSharedElements.remove(i); - mTargetSharedNames.remove(i); - } - Rect epicenter = null; - if (!mTargetSharedNames.isEmpty() - && mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) { - epicenter = calcEpicenter(mSharedElements.get(0)); - } - mEpicenterCallback.setEpicenter(epicenter); - } - - private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( - Bundle sharedElementState, final ArrayList<View> acceptedOverlayViews) { - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = - new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); - final int[] tempLoc = new int[2]; - if (sharedElementState != null) { - for (int i = 0; i < mSharedElements.size(); i++) { - View sharedElement = mSharedElements.get(i); - String name = mTargetSharedNames.get(i); - Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, - name, sharedElementState); - if (originalState != null) { - originalImageState.put((ImageView) sharedElement, originalState); - } - View parent = (View) sharedElement.getParent(); - parent.getLocationOnScreen(tempLoc); - setSharedElementState(sharedElement, name, sharedElementState, tempLoc); - sharedElement.requestLayout(); - } - } - mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements, - acceptedOverlayViews); - - getDecor().getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements, - acceptedOverlayViews); - mSharedElementTransitionStarted = true; - return true; - } - } - ); - return originalImageState; - } - - private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, - Bundle transitionArgs) { - if (!(view instanceof ImageView)) { - return null; - } - Bundle bundle = transitionArgs.getBundle(name); - int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); - if (scaleTypeInt < 0) { - return null; - } - - ImageView imageView = (ImageView) view; - ImageView.ScaleType originalScaleType = imageView.getScaleType(); - - Matrix originalMatrix = null; - if (originalScaleType == ImageView.ScaleType.MATRIX) { - originalMatrix = new Matrix(imageView.getImageMatrix()); - } - - return Pair.create(originalScaleType, originalMatrix); - } - - /** - * 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 <code>Bundle</code> containing all placementinformation for named - * shared elements in the scene. - * @param parentLoc The x and y coordinates of the parent's screen position. - */ - private static void setSharedElementState(View view, String name, Bundle transitionArgs, - int[] parentLoc) { - Bundle sharedElementBundle = transitionArgs.getBundle(name); - if (sharedElementBundle == null) { - return; - } - - if (view instanceof ImageView) { - int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); - if (scaleTypeInt >= 0) { - ImageView imageView = (ImageView) view; - ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; - imageView.setScaleType(scaleType); - if (scaleType == ImageView.ScaleType.MATRIX) { - float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); - Matrix matrix = new Matrix(); - matrix.setValues(matrixValues); - imageView.setImageMatrix(matrix); - } - } - } - - 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); - - int left = x - parentLoc[0]; - int top = y - parentLoc[1]; - int right = left + width; - int bottom = top + height; - view.layout(left, top, right, bottom); + public ArrayList<String> getAcceptedNames() { + return mSharedElementNames; } - /** - * 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.getViewName()); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - view.draw(canvas); - sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); - - if (view instanceof ImageView) { - ImageView imageView = (ImageView) view; - int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); - sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); - if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { - float[] matrix = new float[9]; - imageView.getImageMatrix().getValues(matrix); - sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); - } + public ArrayList<String> getMappedNames() { + ArrayList<String> names = new ArrayList<String>(mSharedElements.size()); + for (int i = 0; i < mSharedElements.size(); i++) { + names.add(mSharedElements.get(i).getViewName()); } - - 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); + return names; } public static void setViewVisibility(Collection<View> views, int visibility) { @@ -751,12 +261,12 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { } } - private static Transition addTransitionTargets(Transition transition, Collection<View> views) { + protected static Transition addTargets(Transition transition, Collection<View> views) { if (transition == null || views == null || views.isEmpty()) { return null; } TransitionSet set = new TransitionSet(); - set.addTransition(transition.clone()); + set.addTransition(transition); if (views != null) { for (View view: views) { set.addTarget(view); @@ -765,152 +275,62 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return set; } - private ArrayList<View> captureTransitioningViews() { - if (getViewsTransition() == null) { - return null; - } - ArrayList<View> transitioningViews = new ArrayList<View>(); - 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<View> 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<View> 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; - } + protected Transition configureTransition(Transition transition) { if (transition != null) { - TransitionManager.beginDelayedTransition(getDecor(), transition); - if (transitionSharedElement && !mSharedElements.isEmpty()) { - mSharedElements.get(0).invalidate(); - } else if (transitionViews && !transitioningViews.isEmpty()) { - transitioningViews.get(0).invalidate(); - } + transition = transition.clone(); + transition.setEpicenterCallback(mEpicenterCallback); } return transition; } - private void handleRejected(final ArrayList<View> rejected) { - int numRejected = rejected.size(); - if (numRejected == 0) { - return; - } - boolean rejectionHandled = mListener.handleRejectedSharedElements(rejected); - if (rejectionHandled) { - return; - } - - ViewGroupOverlay overlay = getDecor().getOverlay(); - ObjectAnimator animator = null; - for (int i = 0; i < numRejected; i++) { - View view = rejected.get(i); - overlay.add(view); - animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0); - animator.start(); - } - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - ViewGroupOverlay overlay = getDecor().getOverlay(); - for (int i = rejected.size() - 1; i >= 0; i--) { - overlay.remove(rejected.get(i)); + protected static Transition mergeTransitions(Transition transition1, Transition transition2) { + if (transition1 == null) { + return transition2; + } else if (transition2 == null) { + return transition1; + } else { + TransitionSet transitionSet = new TransitionSet(); + transitionSet.addTransition(transition1); + transitionSet.addTransition(transition2); + return transitionSet; + } + } + + private void setSharedElements(ArrayList<String> accepted, ArrayList<String> localNames) { + if (!mAllSharedElementNames.isEmpty()) { + ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); + getDecor().findNamedViews(sharedElements); + if (accepted != null) { + for (int i = 0; i < localNames.size(); i++) { + String localName = localNames.get(i); + String acceptedName = accepted.get(i); + if (!localName.equals(acceptedName)) { + View view = sharedElements.remove(localName); + if (view != null) { + sharedElements.put(acceptedName, view); + } + } } } - }); - } - - private void createSharedElementImages(ArrayList<View> accepted, ArrayList<View> rejected, - ArrayList<String> sharedElementNames, Bundle state) { - int numSharedElements = sharedElementNames.size(); - Context context = getWindow().getContext(); - int[] parentLoc = new int[2]; - getDecor().getLocationOnScreen(parentLoc); - for (int i = 0; i < numSharedElements; i++) { - String name = sharedElementNames.get(i); - Bundle sharedElementBundle = state.getBundle(name); - if (sharedElementBundle != null) { - Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); - ImageView imageView = new ImageView(context); - imageView.setId(com.android.internal.R.id.shared_element); - imageView.setScaleType(ImageView.ScaleType.CENTER); - imageView.setImageBitmap(bitmap); - imageView.setViewName(name); - setSharedElementState(imageView, name, state, parentLoc); - if (mTargetSharedNames.contains(name)) { - accepted.add(imageView); - } else { - rejected.add(imageView); + sharedElements.retainAll(mAllSharedElementNames); + mListener.remapSharedElements(mAllSharedElementNames, sharedElements); + sharedElements.retainAll(mAllSharedElementNames); + for (int i = 0; i < mAllSharedElementNames.size(); i++) { + String name = mAllSharedElementNames.get(i); + View sharedElement = sharedElements.get(name); + if (sharedElement != null) { + mSharedElementNames.add(name); + mSharedElements.add(sharedElement); } } } } - private static void setOriginalImageViewState( - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { - for (int i = 0; i < originalState.size(); i++) { - ImageView imageView = originalState.keyAt(i); - Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); - imageView.setScaleType(state.first); - imageView.setImageMatrix(state.second); - } + protected void setResultReceiver(ResultReceiver resultReceiver) { + mResultReceiver = resultReceiver; } - private static int scaleTypeToInt(ImageView.ScaleType scaleType) { - for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { - if (scaleType == SCALE_TYPE_VALUES[i]) { - return i; - } - } - return -1; - } + protected abstract Transition getViewsTransition(); private static class FixedEpicenterCallback extends Transition.EpicenterCallback { private Rect mEpicenter; diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java new file mode 100644 index 0000000..63019b6 --- /dev/null +++ b/core/java/android/app/ActivityTransitionState.java @@ -0,0 +1,204 @@ +/* + * 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.os.ResultReceiver; +import android.util.ArrayMap; +import android.view.View; +import android.view.Window; + +import java.util.ArrayList; + +/** + * This class contains all persistence-related functionality for Activity Transitions. + * Activities start exit and enter Activity Transitions through this class. + */ +class ActivityTransitionState { + + private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements"; + + private static final String ENTERING_MAPPED_FROM = "android:enteringMappedFrom"; + + private static final String ENTERING_MAPPED_TO = "android:enteringMappedTo"; + + private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom"; + + private static final String EXITING_MAPPED_TO = "android:exitingMappedTo"; + + /** + * The shared elements that the calling Activity has said that they transferred to this + * Activity. + */ + private ArrayList<String> mEnteringNames; + + /** + * The shared elements that this Activity as accepted and mapped to local Views. + */ + private ArrayList<String> mEnteringFrom; + + /** + * The names of local Views that are mapped to those elements in mEnteringFrom. + */ + private ArrayList<String> mEnteringTo; + + /** + * The names of shared elements that were shared to the called Activity. + */ + private ArrayList<String> mExitingFrom; + + /** + * The names of local Views that were shared out, mapped to those elements in mExitingFrom. + */ + private ArrayList<String> mExitingTo; + + /** + * The ActivityOptions used to call an Activity. Used to make the elements restore + * Visibility of exited Views. + */ + private ActivityOptions mCalledActivityOptions; + + /** + * We must be able to cancel entering transitions to stop changing the Window to + * opaque when we exit before making the Window opaque. + */ + private EnterTransitionCoordinator mEnterTransitionCoordinator; + + /** + * ActivityOptions used on entering this Activity. + */ + private ActivityOptions mEnterActivityOptions; + + /** + * Has an exit transition been started? If so, we don't want to double-exit. + */ + private boolean mHasExited; + + public ActivityTransitionState() { + } + + public void readState(Bundle bundle) { + if (bundle != null) { + if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) { + mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS); + mEnteringFrom = bundle.getStringArrayList(ENTERING_MAPPED_FROM); + mEnteringTo = bundle.getStringArrayList(ENTERING_MAPPED_TO); + } + if (mEnterTransitionCoordinator == null) { + mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM); + mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO); + } + } + } + + public void saveState(Bundle bundle) { + if (mEnteringNames != null) { + bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames); + bundle.putStringArrayList(ENTERING_MAPPED_FROM, mEnteringFrom); + bundle.putStringArrayList(ENTERING_MAPPED_TO, mEnteringTo); + } + if (mExitingFrom != null) { + bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom); + bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo); + } + } + + public void setEnterActivityOptions(Activity activity, ActivityOptions options) { + if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) + && options != null && mEnterActivityOptions == null + && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mEnterActivityOptions = options; + if (mEnterActivityOptions.isReturning()) { + int result = mEnterActivityOptions.getResultCode(); + if (result != 0) { + activity.onActivityReenter(result, mEnterActivityOptions.getResultData()); + } + } + } + } + + public void enterReady(Activity activity) { + if (mEnterActivityOptions == null) { + return; + } + mHasExited = false; + ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames(); + ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver(); + if (mEnterActivityOptions.isReturning()) { + if (mCalledActivityOptions != null) { + mCalledActivityOptions.dispatchActivityStopped(); + mCalledActivityOptions = null; + } + activity.getWindow().getDecorView().setVisibility(View.VISIBLE); + mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, + resultReceiver, sharedElementNames, mExitingFrom, mExitingTo); + } else { + mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, + resultReceiver, sharedElementNames, null, null); + mEnteringNames = sharedElementNames; + mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames(); + mEnteringTo = mEnterTransitionCoordinator.getMappedNames(); + } + mExitingFrom = null; + mExitingTo = null; + mEnterActivityOptions = null; + } + + public void onStop() { + if (mCalledActivityOptions != null) { + mCalledActivityOptions.dispatchActivityStopped(); + mCalledActivityOptions = null; + } + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.stop(); + mEnterTransitionCoordinator = null; + } + } + + public boolean startExitBackTransition(Activity activity) { + if (mEnteringNames == null) { + return false; + } else { + if (!mHasExited) { + mHasExited = true; + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.stop(); + mEnterTransitionCoordinator = null; + } + ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); + activity.getWindow().getDecorView().findNamedViews(sharedElements); + + ExitTransitionCoordinator exitCoordinator = + new ExitTransitionCoordinator(activity, mEnteringNames, mEnteringFrom, + mEnteringTo, true); + exitCoordinator.startExit(activity.mResultCode, activity.mResultData); + } + return true; + } + } + + public void startExitOutTransition(Activity activity, Bundle options) { + if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + return; + } + mCalledActivityOptions = new ActivityOptions(options); + if (mCalledActivityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mExitingFrom = mCalledActivityOptions.getSharedElementNames(); + mExitingTo = mCalledActivityOptions.getLocalSharedElementNames(); + mCalledActivityOptions.dispatchStartExit(); + } + } +} diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index d2d8ed1..636205b 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -18,270 +18,414 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.graphics.drawable.ColorDrawable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.ResultReceiver; import android.transition.Transition; +import android.transition.TransitionManager; +import android.util.ArrayMap; +import android.util.Pair; import android.view.View; +import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; -import android.view.Window; +import android.widget.ImageView; import java.util.ArrayList; +import java.util.Collection; /** * 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. + * the enter scene and shared element transfer into the Scene, either during + * launch of an Activity or returning from a launched Activity. */ -class EnterTransitionCoordinator extends ActivityTransitionCoordinator - implements ViewTreeObserver.OnPreDrawListener { +class EnterTransitionCoordinator extends ActivityTransitionCoordinator { 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; + private static final long MAX_WAIT_MS = 1500; - /** - * The shared element names sent by the ExitTransitionCoordinator and may be - * shared when exiting back. - */ - private ArrayList<String> mEnteringSharedElementNames; - - /** - * The Activity that has created this coordinator. This is used solely to make the - * Window translucent/opaque. - */ + private boolean mSharedElementTransitionStarted; + private boolean mIsReturning; 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()); + private boolean mHasStopped; + private Handler mHandler; + private boolean mIsCanceled; + + public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, + ArrayList<String> sharedElementNames, + ArrayList<String> acceptedNames, ArrayList<String> mappedNames) { + super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames, + activity.mTransitionListener); mActivity = activity; - setRemoteResultReceiver(resultReceiver); + mIsReturning = acceptedNames != null; + setResultReceiver(resultReceiver); + prepareEnter(); + Bundle resultReceiverBundle = new Bundle(); + resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); + mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); + if (mIsReturning) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + cancel(); + } + }; + mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); + } } - 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(); - } + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case MSG_TAKE_SHARED_ELEMENTS: + if (!mIsCanceled) { + if (mHandler != null) { + mHandler.removeMessages(MSG_CANCEL); } - }, null); - Drawable background = getDecor().getBackground(); - if (background != null) { - window.setBackgroundDrawable(null); - background.setAlpha(0); - window.setBackgroundDrawable(background); + onTakeSharedElements(resultData); } + break; + case MSG_EXIT_TRANSITION_COMPLETE: + if (!mIsCanceled) { + if (!mSharedElementTransitionStarted) { + send(resultCode, resultData); + } else { + onRemoteExitTransitionComplete(); + } + } + break; + case MSG_CANCEL: + cancel(); + break; + } + } + + private void cancel() { + if (!mIsCanceled) { + mIsCanceled = true; + if (getViewsTransition() == null) { + setViewVisibility(mSharedElements, View.VISIBLE); + } else { + mTransitioningViews.addAll(mSharedElements); } + mSharedElementNames.clear(); + mSharedElements.clear(); + mAllSharedElementNames.clear(); + onTakeSharedElements(null); + onRemoteExitTransitionComplete(); } } - @Override - protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) { - mEnteringSharedElementNames = new ArrayList<String>(); - mEnteringSharedElementNames.addAll(sharedElementNames); - super.onTakeSharedElements(sharedElementNames, state); + public boolean isReturning() { + return mIsReturning; } - @Override - protected void sharedElementTransitionComplete(Bundle bundle) { - notifySharedElementTransitionComplete(bundle); - exitAfterSharedElementTransition(); + protected void prepareEnter() { + setViewVisibility(mSharedElements, View.INVISIBLE); + if (getViewsTransition() != null) { + setViewVisibility(mTransitioningViews, View.INVISIBLE); + } + mActivity.overridePendingTransition(0, 0); + if (!mIsReturning) { + mActivity.convertToTranslucent(null, null); + Drawable background = getDecor().getBackground(); + if (background != null) { + getWindow().setBackgroundDrawable(null); + background = background.mutate(); + background.setAlpha(0); + getWindow().setBackgroundDrawable(background); + } + } else { + mActivity = null; // all done with it now. + } } - @Override - public boolean onPreDraw() { - getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this); - setEnteringViews(readyEnteringViews()); - notifySetListener(); - onPrepareRestore(); - return false; + protected void onTakeSharedElements(Bundle sharedElementState) { + setEpicenter(); + // Remove rejected shared elements + ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); + rejectedNames.removeAll(mSharedElementNames); + ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); + mListener.handleRejectedSharedElements(rejectedSnapshots); + startRejectedAnimations(rejectedSnapshots); + + // Now start shared element transition + ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, + mSharedElementNames); + setViewVisibility(mSharedElements, View.VISIBLE); + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = + setSharedElementState(sharedElementState, sharedElementSnapshots); + + boolean startEnterTransition = allowOverlappingTransitions(); + boolean startSharedElementTransition = true; + Transition transition = beginTransition(startEnterTransition, startSharedElementTransition); + + if (startEnterTransition) { + startEnterTransition(transition); + } + + setOriginalImageViewState(originalImageViewState); + + if (mResultReceiver != null) { + mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); + } + mResultReceiver = null; // all done sending messages. } - @Override - public void startExit() { - if (!mExitTransitionStarted) { - mExitTransitionStarted = true; - startExitTransition(mEnteringSharedElementNames); + private Transition beginTransition(boolean startEnterTransition, + boolean startSharedElementTransition) { + Transition sharedElementTransition = null; + if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { + sharedElementTransition = configureTransition(getSharedElementTransition()); } + Transition viewsTransition = null; + if (startEnterTransition && !mTransitioningViews.isEmpty()) { + viewsTransition = configureTransition(getViewsTransition()); + viewsTransition = addTargets(viewsTransition, mTransitioningViews); + } + + Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); + if (transition != null) { + TransitionManager.beginDelayedTransition(getDecor(), transition); + if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { + mSharedElements.get(0).invalidate(); + } else if (startEnterTransition && !mTransitioningViews.isEmpty()) { + mTransitioningViews.get(0).invalidate(); + } + } + return transition; } - @Override - protected Transition getViewsTransition() { - if (!mSupportsTransition) { - return null; + private void startEnterTransition(Transition transition) { + setViewVisibility(mTransitioningViews, View.VISIBLE); + if (!mIsReturning) { + Drawable background = getDecor().getBackground(); + if (background != null) { + background = background.mutate(); + ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255); + animator.setDuration(FADE_BACKGROUND_DURATION_MS); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + makeOpaque(); + } + }); + animator.start(); + } else if (transition != null) { + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + makeOpaque(); + } + }); + } else { + makeOpaque(); + } } - return getWindow().getEnterTransition(); } - @Override - protected Transition getSharedElementTransition() { - if (!mSupportsTransition) { - return null; + public void stop() { + mHasStopped = true; + mActivity = null; + mIsCanceled = true; + mResultReceiver = null; + } + + private void makeOpaque() { + if (!mHasStopped) { + mActivity.convertFromTranslucent(); + mActivity = null; } - return getWindow().getSharedElementEnterTransition(); } - @Override - protected void onStartEnterTransition(Transition transition, ArrayList<View> 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(); - } - } - }); + private boolean allowOverlappingTransitions() { + return mIsReturning ? getWindow().getAllowExitTransitionOverlap() + : getWindow().getAllowEnterTransitionOverlap(); + } + + private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) { + if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) { + return; + } + ViewGroupOverlay overlay = getDecor().getOverlay(); + ObjectAnimator animator = null; + int numRejected = rejectedSnapshots.size(); + for (int i = 0; i < numRejected; i++) { + View snapshot = rejectedSnapshots.get(i); + overlay.add(snapshot); + animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0); animator.start(); - } else if (mWasOpaque) { - transition.addListener(new Transition.TransitionListenerAdapter() { - @Override - public void onTransitionEnd(Transition transition) { - mMakeOpaque = true; - mActivity.convertFromTranslucent(); - } - }); } - super.onStartEnterTransition(transition, enteringViews); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ViewGroupOverlay overlay = getDecor().getOverlay(); + int numRejected = rejectedSnapshots.size(); + for (int i = 0; i < numRejected; i++) { + overlay.remove(rejectedSnapshots.get(i)); + } + } + }); } - public ArrayList<View> readyEnteringViews() { - ArrayList<View> enteringViews = new ArrayList<View>(); - getDecor().captureTransitioningViews(enteringViews); - if (getViewsTransition() != null) { - setViewVisibility(enteringViews, View.INVISIBLE); + protected void onRemoteExitTransitionComplete() { + if (!allowOverlappingTransitions()) { + boolean startEnterTransition = true; + boolean startSharedElementTransition = false; + Transition transition = beginTransition(startEnterTransition, + startSharedElementTransition); + startEnterTransition(transition); } - return enteringViews; } - @Override - protected void startExitTransition(ArrayList<String> sharedElements) { - mMakeOpaque = false; - notifyPrepareRestore(); + private ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { + int numSharedElements = names.size(); + if (numSharedElements == 0) { + return null; + } + ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); + Context context = getWindow().getContext(); + int[] parentLoc = new int[2]; + getDecor().getLocationOnScreen(parentLoc); + for (String name: names) { + Bundle sharedElementBundle = state.getBundle(name); + if (sharedElementBundle != null) { + Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); + View snapshot = new View(context); + snapshot.setId(com.android.internal.R.id.shared_element); + Resources resources = getWindow().getContext().getResources(); + snapshot.setBackground(new BitmapDrawable(resources, bitmap)); + snapshot.setViewName(name); + setSharedElementState(snapshot, name, state, parentLoc); + snapshots.add(snapshot); + } + } + return snapshots; + } - if (getDecor().getBackground() == null) { - ColorDrawable black = new ColorDrawable(0xFF000000); - getWindow().setBackgroundDrawable(black); + private static void setSharedElementState(View view, String name, Bundle transitionArgs, + int[] parentLoc) { + Bundle sharedElementBundle = transitionArgs.getBundle(name); + if (sharedElementBundle == null) { + return; } - if (mWasOpaque) { - mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { - @Override - public void onTranslucentConversionComplete(boolean drawComplete) { - fadeOutBackground(); + + if (view instanceof ImageView) { + int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt >= 0) { + ImageView imageView = (ImageView) view; + ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; + imageView.setScaleType(scaleType); + if (scaleType == ImageView.ScaleType.MATRIX) { + float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); + Matrix matrix = new Matrix(); + matrix.setValues(matrixValues); + imageView.setImageMatrix(matrix); } - }, null); - } else { - fadeOutBackground(); + } } - super.startExitTransition(sharedElements); + 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); + + int left = x - parentLoc[0]; + int top = y - parentLoc[1]; + int right = left + width; + int bottom = top + height; + view.layout(left, top, right, bottom); } - 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(); + private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( + Bundle sharedElementState, final ArrayList<View> snapshots) { + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = + new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); + if (sharedElementState != null) { + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElementNames.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mSharedElementNames.get(i); + Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, + name, sharedElementState); + if (originalState != null) { + originalImageState.put((ImageView) sharedElement, originalState); } + View parent = (View) sharedElement.getParent(); + parent.getLocationOnScreen(tempLoc); + setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + sharedElement.requestLayout(); } - }); - animator.setDuration(FADE_BACKGROUND_DURATION_MS); - animator.start(); + } + mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots); + + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, + snapshots); + mSharedElementTransitionStarted = true; + return true; + } + } + ); + return originalImageState; } - @Override - protected void onExitTransitionEnd() { - mExitTransitionComplete = true; - exitAfterSharedElementTransition(); - super.onExitTransitionEnd(); + private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, + Bundle transitionArgs) { + if (!(view instanceof ImageView)) { + return null; + } + Bundle bundle = transitionArgs.getBundle(name); + int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt < 0) { + return null; + } + + ImageView imageView = (ImageView) view; + ImageView.ScaleType originalScaleType = imageView.getScaleType(); + + Matrix originalMatrix = null; + if (originalScaleType == ImageView.ScaleType.MATRIX) { + originalMatrix = new Matrix(imageView.getImageMatrix()); + } + + return Pair.create(originalScaleType, originalMatrix); } - @Override - protected void onSharedElementTransitionEnd() { - mSharedElementTransitionComplete = true; - if (mBackgroundFadedOut) { - super.onSharedElementTransitionEnd(); + private static void setOriginalImageViewState( + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { + for (int i = 0; i < originalState.size(); i++) { + ImageView imageView = originalState.keyAt(i); + Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); + imageView.setScaleType(state.first); + imageView.setImageMatrix(state.second); } } @Override - protected boolean allowOverlappingTransitions() { - return getWindow().getAllowEnterTransitionOverlap(); + protected Transition getViewsTransition() { + return getWindow().getEnterTransition(); } - private void exitAfterSharedElementTransition() { - if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) { - mActivity.finish(); - if (mSupportsTransition) { - mActivity.overridePendingTransition(0, 0); - } - notifyExitTransitionComplete(); - clearConnections(); - } + protected Transition getSharedElementTransition() { + return getWindow().getSharedElementEnterTransition(); } } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index d920787..43a60a3 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -15,11 +15,20 @@ */ package android.app; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.transition.Transition; -import android.util.Pair; +import android.transition.TransitionManager; import android.view.View; -import android.view.Window; +import android.widget.ImageView; import java.util.ArrayList; @@ -30,142 +39,245 @@ import java.util.ArrayList; */ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private static final String TAG = "ExitTransitionCoordinator"; + private static final long MAX_WAIT_MS = 1500; - /** - * The Views that have exited and need to be restored to VISIBLE when returning to the - * normal state. - */ - private ArrayList<View> mTransitioningViews; + private boolean mExitComplete; - /** - * Has the exit started? We don't want to accidentally exit multiple times. - */ - private boolean mExitStarted; + private Bundle mSharedElementBundle; - /** - * Has the called Activity's ResultReceiver been set? - */ - private boolean mIsResultReceiverSet; + private boolean mExitNotified; - /** - * Has the exit transition completed? If so, we can notify as soon as the ResultReceiver - * has been set. - */ - private boolean mExitComplete; + private boolean mSharedElementNotified; - /** - * Has the shared element transition completed? If so, we can notify as soon as the - * ResultReceiver has been set. - */ - private Bundle mSharedElements; + private Activity mActivity; - /** - * Has the shared element transition completed? - */ - private boolean mSharedElementsComplete; + private boolean mIsBackgroundReady; + + private boolean mIsReturning; - public ExitTransitionCoordinator(Window window, - ActivityOptions.ActivityTransitionListener listener) { - super(window); - setActivityTransitionListener(listener); + private boolean mIsCanceled; + + private Handler mHandler; + + public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, + ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { + super(activity.getWindow(), names, accepted, mapped, activity.mTransitionListener); + mIsReturning = isReturning; + mIsBackgroundReady = !mIsReturning; + mActivity = activity; } @Override - protected void onSetResultReceiver() { - mIsResultReceiverSet = true; - notifyCompletions(); + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case MSG_SET_REMOTE_RECEIVER: + mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); + if (mIsCanceled) { + mResultReceiver.send(MSG_CANCEL, null); + mResultReceiver = null; + } else { + if (mHandler != null) { + mHandler.removeMessages(MSG_CANCEL); + } + notifyComplete(); + } + break; + case MSG_HIDE_SHARED_ELEMENTS: + if (!mIsCanceled) { + hideSharedElements(); + } + break; + case MSG_START_EXIT_TRANSITION: + startExit(); + break; + case MSG_ACTIVITY_STOPPED: + setViewVisibility(mTransitioningViews, View.VISIBLE); + setViewVisibility(mSharedElements, View.VISIBLE); + break; + } } - @Override - protected void onPrepareRestore() { - makeTransitioningViewsInvisible(); - setEnteringViews(mTransitioningViews); - mTransitioningViews = null; - super.onPrepareRestore(); + private void hideSharedElements() { + setViewVisibility(mSharedElements, View.INVISIBLE); } - @Override - protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) { - super.onTakeSharedElements(sharedElementNames, state); - clearConnections(); + public void startExit() { + beginTransition(); + setViewVisibility(mTransitioningViews, View.INVISIBLE); } - @Override - protected void onActivityStopped() { - if (getViewsTransition() != null) { - setViewVisibility(mTransitioningViews, View.VISIBLE); + public void startExit(int resultCode, Intent data) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + mIsCanceled = true; + mActivity.finish(); + mActivity = null; + } + }; + mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); + if (getDecor().getBackground() == null) { + ColorDrawable black = new ColorDrawable(0xFF000000); + black.setAlpha(0); + getWindow().setBackgroundDrawable(black); + black.setAlpha(255); } - super.onActivityStopped(); + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, + mAllSharedElementNames, resultCode, data); + mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { + @Override + public void onTranslucentConversionComplete(boolean drawComplete) { + if (!mIsCanceled) { + fadeOutBackground(); + } + } + }, options); + startExit(); } - @Override - protected void sharedElementTransitionComplete(Bundle bundle) { - mSharedElements = bundle; - mSharedElementsComplete = true; - notifyCompletions(); + private void fadeOutBackground() { + ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(), + "alpha", 0); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIsBackgroundReady = true; + notifyComplete(); + } + }); + animator.setDuration(FADE_BACKGROUND_DURATION_MS); + animator.start(); } - @Override - protected void onExitTransitionEnd() { + private void beginTransition() { + Transition sharedElementTransition = configureTransition(getSharedElementTransition()); + Transition viewsTransition = configureTransition(getViewsTransition()); + viewsTransition = addTargets(viewsTransition, mTransitioningViews); + if (sharedElementTransition == null) { + sharedElementTransitionComplete(); + } else { + sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + sharedElementTransitionComplete(); + } + }); + } + if (viewsTransition == null) { + exitTransitionComplete(); + } else { + viewsTransition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + exitTransitionComplete(); + } + }); + } + + Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); + TransitionManager.beginDelayedTransition(getDecor(), transition); + } + + private void exitTransitionComplete() { mExitComplete = true; - notifyCompletions(); - super.onExitTransitionEnd(); + notifyComplete(); } - private void notifyCompletions() { - if (mIsResultReceiverSet && mSharedElementsComplete) { - if (mSharedElements != null) { - notifySharedElementTransitionComplete(mSharedElements); - mSharedElements = null; - } - if (mExitComplete) { - notifyExitTransitionComplete(); - } - } + protected boolean isReadyToNotify() { + return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; } - @Override - public void startExit() { - if (!mExitStarted) { - mExitStarted = true; - setSharedElements(); - startExitTransition(getSharedElementNames()); + private void sharedElementTransitionComplete() { + Bundle bundle = new Bundle(); + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElementNames.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mSharedElementNames.get(i); + captureSharedElementState(sharedElement, name, bundle, tempLoc); } + mSharedElementBundle = bundle; + notifyComplete(); } - @Override - protected Transition getViewsTransition() { - if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { - return null; + protected void notifyComplete() { + if (isReadyToNotify()) { + if (!mSharedElementNotified) { + mSharedElementNotified = true; + mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); + } + if (!mExitNotified && mExitComplete) { + mExitNotified = true; + mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); + mResultReceiver = null; // done talking + if (mIsReturning) { + mActivity.finish(); + mActivity.overridePendingTransition(0, 0); + } + mActivity = null; + } } - return getWindow().getExitTransition(); } - @Override - protected Transition getSharedElementTransition() { - if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { - return null; + /** + * 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. + */ + 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()); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); + + if (view instanceof ImageView) { + ImageView imageView = (ImageView) view; + int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); + sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); + if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { + float[] matrix = new float[9]; + imageView.getImageMatrix().getValues(matrix); + sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); + } } - return getWindow().getSharedElementExitTransition(); + + transitionArgs.putBundle(name, sharedElementBundle); } - private void makeTransitioningViewsInvisible() { - if (getViewsTransition() != null) { - setViewVisibility(mTransitioningViews, View.INVISIBLE); + private static int scaleTypeToInt(ImageView.ScaleType scaleType) { + for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { + if (scaleType == SCALE_TYPE_VALUES[i]) { + return i; + } } + return -1; } @Override - protected void onStartExitTransition(ArrayList<View> exitingViews) { - mTransitioningViews = new ArrayList<View>(); - if (exitingViews != null) { - mTransitioningViews.addAll(exitingViews); - } - mTransitioningViews.addAll(getSharedElements()); + protected Transition getViewsTransition() { + return getWindow().getExitTransition(); } - @Override - protected boolean allowOverlappingTransitions() { - return getWindow().getAllowExitTransitionOverlap(); + protected Transition getSharedElementTransition() { + return getWindow().getSharedElementExitTransition(); } } diff --git a/core/java/android/app/SharedElementListener.java b/core/java/android/app/SharedElementListener.java new file mode 100644 index 0000000..d4bc019 --- /dev/null +++ b/core/java/android/app/SharedElementListener.java @@ -0,0 +1,87 @@ +/* + * 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.view.View; + +import java.util.List; +import java.util.Map; + +/** + * Listener provided in + * {@link Activity#setSharedElementListener(SharedElementListener)} + * to monitor the Activity transitions. The events can be used to customize or override Activity + * Transition behavior. + */ +public class SharedElementListener { + /** + * Called to allow the listener to customize the start state of the shared element for + * the shared element entering transition. By default, the shared element is placed in + * the position and with the size of the shared element in the calling Activity or Fragment. + * + * @param sharedElementNames The names of the shared elements that were accepted into + * the View hierarchy. + * @param sharedElements The shared elements that are part of the View hierarchy. + * @param sharedElementSnapshots The Views containing snap shots of the shared element + * from the launching Window. These elements will not + * be part of the scene, but will be positioned relative + * to the Window decor View. + */ + public void setSharedElementStart(List<String> sharedElementNames, + List<View> sharedElements, List<View> sharedElementSnapshots) {} + + /** + * Called to allow the listener to customize the end state of the shared element for + * the shared element entering transition. + * + * @param sharedElementNames The names of the shared elements that were accepted into + * the View hierarchy. + * @param sharedElements The shared elements that are part of the View hierarchy. + * @param sharedElementSnapshots The Views containing snap shots of the shared element + * from the launching Window. These elements will not + * be part of the scene, but will be positioned relative + * to the Window decor View. + */ + public void setSharedElementEnd(List<String> sharedElementNames, + List<View> sharedElements, List<View> sharedElementSnapshots) {} + + /** + * If nothing is done, all shared elements that were not accepted by + * {@link #remapSharedElements(java.util.List, java.util.Map)} will be Transitioned + * out of the entering scene automatically. Any elements removed from + * rejectedSharedElements must be handled by the ActivityTransitionListener. + * <p>Views in rejectedSharedElements will have their position and size set to the + * position of the calling shared element, relative to the Window decor View. This + * view may be safely added to the decor View's overlay to remain in position.</p> + * + * @param rejectedSharedElements Views containing visual information of shared elements + * that are not part of the entering scene. These Views + * are positioned relative to the Window decor View. A + * View removed from this list will not be transitioned + * automatically. + */ + public void handleRejectedSharedElements(List<View> rejectedSharedElements) {} + + /** + * Lets the ActivityTransitionListener adjust the mapping of shared element names to + * Views. + * @param names The names of all shared elements transferred from the calling Activity + * to the started Activity. + * @param sharedElements The mapping of shared element names to Views. The best guess + * will be filled into sharedElements based on the View names. + */ + public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {} +} diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 92a42b7..b821a3e 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2318,8 +2318,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * 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) + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity, + * android.util.Pair[]) */ public void setTransitionGroup(boolean isTransitionGroup) { mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 9e684c7..a06a3fd 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2477,7 +2477,7 @@ 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.Window, + {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity, android.view.View, String)} for more information. Corresponds to {@link android.view.ViewGroup#setTransitionGroup(boolean)}.--> <attr name="transitionGroup" format="boolean" /> |