From 28a8d108050b59629e8be9aa4a02739cd08ea941 Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 10 Jun 2014 11:12:01 -0700 Subject: [PATCH] DO NOT MERGE Allow enter activity transitions to be delayed until data is ready. Bug 15539194 Change-Id: I8de9806b0bcdf8129eb3b5a555a2433f0c7be634 --- api/current.txt | 2 + core/java/android/app/Activity.java | 28 ++++++++ .../app/ActivityTransitionCoordinator.java | 12 ++++ .../android/app/ActivityTransitionState.java | 38 ++++++++-- .../app/EnterTransitionCoordinator.java | 71 ++++++++++++++----- 5 files changed, 130 insertions(+), 21 deletions(-) diff --git a/api/current.txt b/api/current.txt index 53006ae932a45..e2393854cbc8c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3333,6 +3333,7 @@ package android.app { method public void openContextMenu(android.view.View); method public void openOptionsMenu(); method public void overridePendingTransition(int, int); + method public void postponeEnterTransition(); method public void recreate(); method public void registerForContextMenu(android.view.View); method public final deprecated void removeDialog(int); @@ -3388,6 +3389,7 @@ package android.app { method public deprecated void startManagingCursor(android.database.Cursor); method public boolean startNextMatchingActivity(android.content.Intent); method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); + method public void startPostponedEnterTransition(); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); method public void stopLockTask(); method public deprecated void stopManagingCursor(android.database.Cursor); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 23b5f29035195..dd7b274885f52 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5615,6 +5615,34 @@ public class Activity extends ContextThemeWrapper mExitTransitionListener = listener; } + /** + * Postpone the entering activity transition when Activity was started with + * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, + * android.util.Pair[])}. + *

This method gives the Activity the ability to delay starting the entering and + * shared element transitions until all data is loaded. Until then, the Activity won't + * draw into its window, leaving the window transparent. This may also cause the + * returning animation to be delayed until data is ready. This method should be + * called in {@link #onCreate(android.os.Bundle)} or in + * {@link #onActivityReenter(int, android.content.Intent)}. + * {@link #startPostponedEnterTransition()} must be called to allow the Activity to + * start the transitions. If the Activity did not use + * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, + * android.util.Pair[])}, then this method does nothing.

+ */ + public void postponeEnterTransition() { + mActivityTransitionState.postponeEnterTransition(); + } + + /** + * Begin postponed transitions after {@link #postponeEnterTransition()} was called. + * If postponeEnterTransition() was called, you must call startPostponedEnterTransition() + * to have your Activity start drawing. + */ + public void startPostponedEnterTransition() { + mActivityTransitionState.startPostponedEnterTransition(); + } + // ------------------ Internal API ------------------ final void setParent(Activity parent) { diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index b658597b494ea..703df51a60183 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -214,11 +214,21 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { ArrayList allSharedElementNames, ArrayList accepted, ArrayList localNames, SharedElementListener listener, boolean isReturning) { + this(window, allSharedElementNames, listener, isReturning); + viewsReady(accepted, localNames); + } + + public ActivityTransitionCoordinator(Window window, + ArrayList allSharedElementNames, + SharedElementListener listener, boolean isReturning) { super(new Handler()); mWindow = window; mListener = listener; mAllSharedElementNames = allSharedElementNames; mIsReturning = isReturning; + } + + protected void viewsReady(ArrayList accepted, ArrayList localNames) { setSharedElements(accepted, localNames); if (getViewsTransition() != null) { getDecor().captureTransitioningViews(mTransitioningViews); @@ -274,6 +284,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return names; } + public ArrayList getAllSharedElementNames() { return mAllSharedElementNames; } + public static void setViewVisibility(Collection views, int visibility) { if (views != null) { for (View view : views) { diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index b32e9ad615db7..d94dadda055f0 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -87,6 +87,11 @@ class ActivityTransitionState { */ private boolean mHasExited; + /** + * Postpone painting and starting the enter transition until this is false. + */ + private boolean mIsEnterPostponed; + public ActivityTransitionState() { } @@ -140,15 +145,38 @@ class ActivityTransitionState { if (mEnterActivityOptions.isReturning()) { restoreExitedViews(); activity.getWindow().getDecorView().setVisibility(View.VISIBLE); - mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, - resultReceiver, sharedElementNames, mExitingFrom, mExitingTo); + } + mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, + resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning()); + + if (!mIsEnterPostponed) { + startEnter(); + } + } + + public void postponeEnterTransition() { + mIsEnterPostponed = true; + } + + public void startPostponedEnterTransition() { + if (mIsEnterPostponed) { + mIsEnterPostponed = false; + if (mEnterTransitionCoordinator != null) { + startEnter(); + } + } + } + + private void startEnter() { + if (mEnterActivityOptions.isReturning()) { + mEnterTransitionCoordinator.viewsReady(mExitingFrom, mExitingTo); } else { - mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, - resultReceiver, sharedElementNames, null, null); - mEnteringNames = sharedElementNames; + mEnterTransitionCoordinator.viewsReady(null, null); + mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames(); mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames(); mEnteringTo = mEnterTransitionCoordinator.getMappedNames(); } + mExitingFrom = null; mExitingTo = null; mEnterActivityOptions = null; diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 4b052e731e9a6..779e3de2b564d 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -53,18 +53,37 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsCanceled; private ObjectAnimator mBackgroundAnimator; private boolean mIsExitTransitionComplete; + private boolean mIsReadyForTransition; + private Bundle mSharedElementsBundle; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, - ArrayList sharedElementNames, - ArrayList acceptedNames, ArrayList mappedNames) { - super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames, - getListener(activity, acceptedNames), acceptedNames != null); + ArrayList sharedElementNames, boolean isReturning) { + super(activity.getWindow(), sharedElementNames, + getListener(activity, isReturning), isReturning); mActivity = activity; setResultReceiver(resultReceiver); prepareEnter(); Bundle resultReceiverBundle = new Bundle(); resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); + getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (mIsReadyForTransition) { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + } + return mIsReadyForTransition; + } + }); + } + + public void viewsReady(ArrayList accepted, ArrayList localNames) { + if (mIsReadyForTransition) { + return; + } + super.viewsReady(accepted, localNames); + + mIsReadyForTransition = true; if (mIsReturning) { mHandler = new Handler() { @Override @@ -75,6 +94,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null); } + setViewVisibility(mSharedElements, View.INVISIBLE); + if (getViewsTransition() != null) { + setViewVisibility(mTransitioningViews, View.INVISIBLE); + } + if (mSharedElementsBundle != null) { + onTakeSharedElements(); + } } private void sendSharedElementDestination() { @@ -94,9 +120,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } - private static SharedElementListener getListener(Activity activity, - ArrayList acceptedNames) { - boolean isReturning = acceptedNames != null; + private static SharedElementListener getListener(Activity activity, boolean isReturning) { return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; } @@ -108,7 +132,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (mHandler != null) { mHandler.removeMessages(MSG_CANCEL); } - onTakeSharedElements(resultData); + mSharedElementsBundle = resultData; + onTakeSharedElements(); } break; case MSG_EXIT_TRANSITION_COMPLETE: @@ -139,7 +164,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mSharedElementNames.clear(); mSharedElements.clear(); mAllSharedElementNames.clear(); - onTakeSharedElements(null); + startSharedElementTransition(null); onRemoteExitTransitionComplete(); } } @@ -149,10 +174,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } protected void prepareEnter() { - setViewVisibility(mSharedElements, View.INVISIBLE); - if (getViewsTransition() != null) { - setViewVisibility(mTransitioningViews, View.INVISIBLE); - } mActivity.overridePendingTransition(0, 0); if (!mIsReturning) { mActivity.convertToTranslucent(null, null); @@ -185,7 +206,25 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } - protected void onTakeSharedElements(Bundle sharedElementState) { + protected void onTakeSharedElements() { + if (!mIsReadyForTransition || mSharedElementsBundle == null) { + return; + } + final Bundle sharedElementState = mSharedElementsBundle; + mSharedElementsBundle = null; + getDecor().getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + startSharedElementTransition(sharedElementState); + return false; + } + }); + getDecor().invalidate(); + } + + private void startSharedElementTransition(Bundle sharedElementState) { setEpicenter(); // Remove rejected shared elements ArrayList rejectedNames = new ArrayList(mAllSharedElementNames); @@ -299,8 +338,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } public void stop() { + makeOpaque(); mHasStopped = true; - mActivity = null; mIsCanceled = true; mResultReceiver = null; if (mBackgroundAnimator != null) { @@ -310,7 +349,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } private void makeOpaque() { - if (!mHasStopped) { + if (!mHasStopped && mActivity != null) { mActivity.convertFromTranslucent(); mActivity = null; }