diff options
| author | 2014-02-05 00:23:03 +0000 | |
|---|---|---|
| committer | 2014-02-05 00:23:03 +0000 | |
| commit | 8adb491e08b79657deedc13c427f785c889ee448 (patch) | |
| tree | f4dfc21266e24f6842ea5af9247cf0839caa09a7 | |
| parent | 9e4adfb358ca3680288c07201efc8811472a579d (diff) | |
| parent | 0a778eda690a66173733a63622886e888d405c45 (diff) | |
Merge "Cross-Activity Scene transition API."
| -rw-r--r-- | api/current.txt | 17 | ||||
| -rw-r--r-- | core/java/android/app/Activity.java | 140 | ||||
| -rw-r--r-- | core/java/android/app/ActivityOptions.java | 290 | ||||
| -rw-r--r-- | core/java/android/transition/Transition.java | 15 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 33 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 42 | ||||
| -rw-r--r-- | core/java/android/view/Window.java | 38 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 13 | ||||
| -rw-r--r-- | core/res/res/values/ids.xml | 1 | ||||
| -rw-r--r-- | core/res/res/values/public.xml | 6 | ||||
| -rw-r--r-- | policy/src/com/android/internal/policy/impl/PhoneWindow.java | 268 |
11 files changed, 699 insertions, 164 deletions
diff --git a/api/current.txt b/api/current.txt index ac04dd178e84..c0ed81af31e0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -543,6 +543,7 @@ package android { field public static final int fromAlpha = 16843210; // 0x10101ca field public static final int fromDegrees = 16843187; // 0x10101b3 field public static final int fromScene = 16843741; // 0x10103dd + field public static final int fromSceneName = 16843774; // 0x10103fe field public static final int fromXDelta = 16843206; // 0x10101c6 field public static final int fromXScale = 16843202; // 0x10101c2 field public static final int fromYDelta = 16843208; // 0x10101c8 @@ -960,6 +961,7 @@ package android { field public static final int shadowRadius = 16843108; // 0x1010164 field public static final int shape = 16843162; // 0x101019a field public static final int shareInterpolator = 16843195; // 0x10101bb + field public static final int sharedElementName = 16843776; // 0x1010400 field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserLabel = 16843361; // 0x1010261 field public static final int shouldDisableView = 16843246; // 0x10101ee @@ -1142,6 +1144,7 @@ package android { field public static final int toAlpha = 16843211; // 0x10101cb field public static final int toDegrees = 16843188; // 0x10101b4 field public static final int toScene = 16843742; // 0x10103de + field public static final int toSceneName = 16843775; // 0x10103ff field public static final int toXDelta = 16843207; // 0x10101c7 field public static final int toXScale = 16843203; // 0x10101c3 field public static final int toYDelta = 16843209; // 0x10101c9 @@ -1157,6 +1160,7 @@ package android { field public static final int transformPivotX = 16843552; // 0x1010320 field public static final int transformPivotY = 16843553; // 0x1010321 field public static final int transition = 16843743; // 0x10103df + field public static final int transitionGroup = 16843777; // 0x1010401 field public static final int transitionOrdering = 16843744; // 0x10103e0 field public static final int translationX = 16843554; // 0x1010322 field public static final int translationY = 16843555; // 0x1010323 @@ -1514,6 +1518,7 @@ package android { field public static final int selectAll = 16908319; // 0x102001f field public static final int selectTextMode = 16908333; // 0x102002d field public static final int selectedIcon = 16908302; // 0x102000e + field public static final int shared_element_name = 16908334; // 0x102002e field public static final int startSelectingText = 16908328; // 0x1020028 field public static final int stopSelectingText = 16908329; // 0x1020029 field public static final int summary = 16908304; // 0x1020010 @@ -3035,6 +3040,7 @@ package android.app { method public int getTaskId(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); + method public android.os.Bundle getTransitionArgs(); method public final int getVolumeControlStream(); method public android.view.Window getWindow(); method public android.view.WindowManager getWindowManager(); @@ -3130,6 +3136,7 @@ package android.app { method public void setContentView(android.view.View); method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); method public final void setDefaultKeyMode(int); + method public void setEarlyBackgroundTransition(boolean); method public final void setFeatureDrawable(int, android.graphics.drawable.Drawable); method public final void setFeatureDrawableAlpha(int, int); method public final void setFeatureDrawableResource(int, int); @@ -3170,6 +3177,7 @@ package android.app { method public boolean startNextMatchingActivity(android.content.Intent); method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); + method protected void startSharedElementTransition(android.os.Bundle); method public deprecated void stopManagingCursor(android.database.Cursor); method public void takeKeyEvents(boolean); method public void triggerSearch(java.lang.String, android.os.Bundle); @@ -3340,6 +3348,7 @@ 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.os.Bundle); 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); @@ -18378,7 +18387,7 @@ package android.os { public abstract class CountDownTimer { ctor public CountDownTimer(long, long); - method public final void cancel(); + method public final synchronized void cancel(); method public abstract void onFinish(); method public abstract void onTick(long); method public final synchronized android.os.CountDownTimer start(); @@ -26682,6 +26691,7 @@ package android.transition { method public android.transition.Transition addListener(android.transition.Transition.TransitionListener); method public android.transition.Transition addTarget(int); method public android.transition.Transition addTarget(android.view.View); + method public boolean canRemoveViews(); method public abstract void captureEndValues(android.transition.TransitionValues); method public abstract void captureStartValues(android.transition.TransitionValues); method public android.transition.Transition clone(); @@ -28746,6 +28756,7 @@ package android.view { method public int getScrollBarStyle(); method public final int getScrollX(); method public final int getScrollY(); + method public java.lang.String getSharedElementName(); method public int getSolidColor(); method protected int getSuggestedMinimumHeight(); method protected int getSuggestedMinimumWidth(); @@ -28999,6 +29010,7 @@ package android.view { method public void setScrollY(int); method public void setScrollbarFadingEnabled(boolean); method public void setSelected(boolean); + method public void setSharedElementName(java.lang.String); method public void setSoundEffectsEnabled(boolean); method public void setSystemUiVisibility(int); method public void setTag(java.lang.Object); @@ -29385,6 +29397,7 @@ package android.view { method protected boolean isChildrenDrawingOrderEnabled(); method protected boolean isChildrenDrawnWithCacheEnabled(); method public boolean isMotionEventSplittingEnabled(); + method public boolean isTransitionGroup(); method public final void layout(int, int, int, int); method protected void measureChild(android.view.View, int, int); method protected void measureChildWithMargins(android.view.View, int, int, int, int); @@ -29430,6 +29443,7 @@ package android.view { method public void setOnHierarchyChangeListener(android.view.ViewGroup.OnHierarchyChangeListener); method public void setPersistentDrawingCache(int); method protected void setStaticTransformationsEnabled(boolean); + method public void setTransitionGroup(boolean); method public boolean shouldDelayChildPressedState(); method public boolean showContextMenuForChild(android.view.View); method public android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback); @@ -29724,7 +29738,6 @@ package android.view { method public abstract void setTitle(java.lang.CharSequence); method public abstract deprecated void setTitleColor(int); method public void setTransitionManager(android.transition.TransitionManager); - method public void setTransitionOptions(android.os.Bundle); method public void setType(int); method public void setUiOptions(int); method public void setUiOptions(int, int); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 698f06a16f89..a8716bf96d1c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -18,7 +18,6 @@ package android.app; import android.annotation.NonNull; import android.transition.Scene; -import android.transition.Transition; import android.transition.TransitionManager; import android.util.ArrayMap; import android.util.SuperNotCalledException; @@ -771,6 +770,7 @@ public class Activity extends ContextThemeWrapper private Thread mUiThread; final Handler mHandler = new Handler(); + private ActivityOptions mTransitionActivityOptions; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -3443,38 +3443,16 @@ public class Activity extends ContextThemeWrapper * * @throws android.content.ActivityNotFoundException * - * @see #startActivity + * @see #startActivity */ public void startActivityForResult(Intent intent, int requestCode) { - final TransitionManager tm = getWindow().getTransitionManager(); - final Scene currScene = getWindow().getContentScene(); - final String[] targetSceneNames = currScene != null && tm != null ? - tm.getTargetSceneNames(currScene) : null; - - if (targetSceneNames == null || targetSceneNames.length == 0) { - startActivityForResult(intent, requestCode, null); - } else { - // TODO Capture the scene transition args and send along - final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation( - targetSceneNames, null, - new ActivityOptions.OnSceneTransitionStartedListener() { - @Override public void onSceneTransitionStarted(String destSceneName) { - final Transition t = tm.getNamedTransition(currScene, destSceneName); - // TODO Fill this in to notify the outgoing activity that it should - // treat this as a sync point for the transition - the target - // transition has started. - Log.d(TAG, "Scene transition to scene " + destSceneName + - " transition " + t); - } - }, mHandler); - startActivityForResult(intent, requestCode, opts.toBundle()); - } + startActivityForResult(intent, requestCode, null); } /** * Launch an activity for which you would like a result when it finished. * When this activity exits, your - * onActivityResult() method will be called with the given requestCode. + * onActivityResult() method will be called with the given requestCode. * Using a negative requestCode is the same as calling * {@link #startActivity} (the activity is not launched as a sub-activity). * @@ -3487,9 +3465,9 @@ public class Activity extends ContextThemeWrapper * * <p>As a special case, if you call startActivityForResult() with a requestCode * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your - * activity, then your window will not be displayed until a result is - * returned back from the started activity. This is to avoid visible - * flickering when redirecting to another activity. + * activity, then your window will not be displayed until a result is + * returned back from the started activity. This is to avoid visible + * flickering when redirecting to another activity. * * <p>This method throws {@link android.content.ActivityNotFoundException} * if there was no Activity found to run the given Intent. @@ -3503,9 +3481,17 @@ public class Activity extends ContextThemeWrapper * * @throws android.content.ActivityNotFoundException * - * @see #startActivity + * @see #startActivity */ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { + TransitionManager tm = getContentTransitionManager(); + if (tm != null && options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + getWindow().startExitTransition(activityOptions); + options = activityOptions.toBundle(); + } + } if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( @@ -5313,7 +5299,7 @@ public class Activity extends ContextThemeWrapper mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); - + mMainThread = aThread; mInstrumentation = instr; mToken = token; @@ -5335,8 +5321,40 @@ public class Activity extends ContextThemeWrapper mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); - mWindow.setTransitionOptions(options); mCurrentConfig = config; + mTransitionActivityOptions = null; + Window.SceneTransitionListener sceneTransitionListener = null; + if (options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mTransitionActivityOptions = activityOptions; + sceneTransitionListener = new Window.SceneTransitionListener() { + @Override + public void enterSharedElement(Bundle transitionArgs) { + startSharedElementTransition(transitionArgs); + mTransitionActivityOptions = null; + } + + @Override + public void nullPendingTransition() { + overridePendingTransition(0, 0); + } + + @Override + public void convertFromTranslucent() { + Activity.this.convertFromTranslucent(); + } + + @Override + public void convertToTranslucent() { + Activity.this.convertToTranslucent(null); + } + }; + + } + } + + mWindow.setTransitionOptions(mTransitionActivityOptions, sceneTransitionListener); } /** @hide */ @@ -5350,7 +5368,7 @@ public class Activity extends ContextThemeWrapper com.android.internal.R.styleable.Window_windowNoDisplay, false); mFragments.dispatchActivityCreated(); } - + final void performStart() { mFragments.noteStateNotSaved(); mCalled = false; @@ -5507,7 +5525,7 @@ public class Activity extends ContextThemeWrapper } } } - + mStopped = true; } mResumed = false; @@ -5522,7 +5540,57 @@ public class Activity extends ContextThemeWrapper mLoaderManager.doDestroy(); } } - + + /** + * Gets the entering Activity transition args. Will be null if + * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle)} was + * not used to pass a Bundle to startActivity. The Bundle passed to that method in the + * calling Activity is returned here. + * <p>After startSharedElementTransition is called, this method will return null.</p> + * + * @return The Bundle passed into Bundle parameter of + * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle)} + * in the calling Activity. + */ + public Bundle getTransitionArgs() { + if (mTransitionActivityOptions == null) { + return null; + } + return mTransitionActivityOptions.getSceneTransitionArgs(); + } + + /** + * Override to transfer a shared element from a calling Activity to this Activity. + * Shared elements will be made VISIBLE before this call. The Activity is responsible + * for transitioning the shared elements from their location to the eventual destination. + * The shared element will be laid out a the destination when this method is called. + * + * @param transitionArgs The same as returned from {@link #getTransitionArgs()}, this should + * contain information from the calling Activity to tell where the + * shared element should be placed. + */ + protected void startSharedElementTransition(Bundle transitionArgs) { + } + + /** + * Controls how the background fade is triggered when there is an entering Activity transition. + * If fadeEarly is true, the Window background will fade in as soon as the shared elements are + * ready to switch. If fadeEarly is false, the background will fade only after the calling + * Activity's exit transition completes. By default, the Window will fade in when the calling + * Activity's exit transition completes. + * + * @param fadeEarly Set to true to fade out the exiting Activity as soon as the shared elements + * are transferred. Set to false to fade out the exiting Activity as soon as + * the shared element is transferred. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle) + */ + public void setEarlyBackgroundTransition(boolean fadeEarly) { + if (mTransitionActivityOptions == null) { + return; + } + mWindow.setEarlyBackgroundTransition(fadeEarly); + } + /** * @hide */ @@ -5530,7 +5598,7 @@ public class Activity extends ContextThemeWrapper return mResumed; } - void dispatchActivityResult(String who, int requestCode, + void dispatchActivityResult(String who, int requestCode, int resultCode, Intent data) { if (false) Log.v( TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 582ce3c1e186..3f97c40149fe 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -16,16 +16,21 @@ package android.app; +import android.animation.Animator; import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; -import android.text.TextUtils; +import android.transition.Transition; +import android.util.ArrayMap; import android.util.Log; import android.view.View; +import java.util.ArrayList; +import java.util.Map; + /** * Helper class for building an options Bundle that can be used with * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) @@ -95,33 +100,29 @@ public class ActivityOptions { public static final String KEY_ANIM_START_LISTENER = "android:animStartListener"; /** - * A string array of names for the destination scene. This defines an API in the same - * way that intent action or extra names do and should follow a similar convention: - * "com.example.scene.FOO" - * + * Arguments for the scene transition about to begin. * @hide */ - public static final String KEY_DEST_SCENE_NAMES = "android:destSceneNames"; + public static final String KEY_SCENE_TRANSITION_ARGS = "android:sceneTransitionArgs"; /** - * A string indicating the destination scene name that was chosen by the target. - * Used by {@link OnSceneTransitionStartedListener}. - * @hide + * For Activity transitions, the calling Activity's TransitionListener used to + * notify the called Activity when the shared element and the exit transitions + * complete. */ - public static final String KEY_DEST_SCENE_NAME_CHOSEN = "android:destSceneNameChosen"; + private static final String KEY_TRANSITION_COMPLETE_LISTENER + = "android:transitionCompleteListener"; /** - * Callback for when scene transition is started. - * @hide + * For Activity transitions, the called Activity's listener to receive calls + * when transitions complete. */ - public static final String KEY_SCENE_TRANSITION_START_LISTENER = - "android:sceneTransitionStartListener"; + private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener"; /** - * Arguments for the scene transition about to begin. - * @hide + * The shared element's texture ID (TODO: not used yet). */ - public static final String KEY_SCENE_TRANSITION_ARGS = "android:sceneTransitionArgs"; + private static final String KEY_SHARED_ELEMENT_TEXTURE_ID = "android:sharedElementTextureId"; /** @hide */ public static final int ANIM_NONE = 0; @@ -145,10 +146,9 @@ public class ActivityOptions { private int mStartY; private int mStartWidth; private int mStartHeight; - private String[] mDestSceneNames; private Bundle mTransitionArgs; private IRemoteCallback mAnimationStartedListener; - private IRemoteCallback mSceneTransitionStartedListener; + private IRemoteCallback mTransitionCompleteListener; /** * Create an ActivityOptions specifying a custom animation to run when @@ -215,24 +215,6 @@ public class ActivityOptions { } } - private void setOnSceneTransitionStartedListener(Handler handler, - OnSceneTransitionStartedListener listener) { - if (listener != null) { - final Handler h = handler; - final OnSceneTransitionStartedListener l = listener; - mSceneTransitionStartedListener = new IRemoteCallback.Stub() { - @Override public void sendResult(final Bundle data) throws RemoteException { - h.post(new Runnable() { - public void run() { - l.onSceneTransitionStarted(data != null ? - data.getString(KEY_DEST_SCENE_NAME_CHOSEN) : null); - } - }); - } - }; - } - } - /** * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation} * to find out when the given animation has started running. @@ -242,13 +224,10 @@ public class ActivityOptions { void onAnimationStarted(); } - /** - * Callback for use with {@link ActivityOptions#makeSceneTransitionAnimation} - * to find out when a transition is about to begin. - * @hide - */ - public interface OnSceneTransitionStartedListener { - void onSceneTransitionStarted(String destSceneName); + /** @hide */ + public interface ActivityTransitionTarget { + void sharedElementTransitionComplete(); + void exitTransitionComplete(); } /** @@ -369,18 +348,35 @@ public class ActivityOptions { } /** - * Create an ActivityOptions specifying an animation where an activity window is asked - * to perform animations within the window content. + * Create an ActivityOptions to transition between Activities using cross-Activity animation. + * When visual elements are to carry between Activities, args should be used to tell the called + * Activity about the location and size. * - * @hide + * TODO: Provide facility to capture layout and bitmap of shared elements. + * + * <p>When + * {@link android.app.Activity#startActivities(android.content.Intent[], android.os.Bundle)} + * is used with the {@link #toBundle()} result, the Activity's content scene will automatically + * transition out by setting their visibility to {@link View#INVISIBLE}. Shared elements + * ({@link android.view.View#setSharedElementName(String)}) are unmodified during the + * transition to allow the started Activity to seamlessly take it over. ViewGroups typically + * don't transition out, and instead transition out their children unless they have a + * background. To modify this behavior, use + * {@link android.view.ViewGroup#setTransitionGroup(boolean)}.</p> + * + * <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 args Contains information for transferring a view between this Activity and the + * target Activity. Will be used by the called Activity to transition the + * view to its eventual destination + * @see android.app.Activity#startSharedElementTransition(android.os.Bundle) */ - public static ActivityOptions makeSceneTransitionAnimation(String[] destSceneNames, - Bundle args, OnSceneTransitionStartedListener listener, Handler handler) { + public static ActivityOptions makeSceneTransitionAnimation(Bundle args) { ActivityOptions opts = new ActivityOptions(); opts.mAnimationType = ANIM_SCENE_TRANSITION; - opts.mDestSceneNames = destSceneNames; opts.mTransitionArgs = args; - opts.setOnSceneTransitionStartedListener(handler, listener); return opts; } @@ -416,10 +412,9 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: - mDestSceneNames = opts.getStringArray(KEY_DEST_SCENE_NAMES); mTransitionArgs = opts.getBundle(KEY_SCENE_TRANSITION_ARGS); - mSceneTransitionStartedListener = IRemoteCallback.Stub.asInterface( - opts.getBinder(KEY_SCENE_TRANSITION_START_LISTENER)); + mTransitionCompleteListener = IRemoteCallback.Stub.asInterface( + opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER)); break; } } @@ -470,11 +465,6 @@ public class ActivityOptions { } /** @hide */ - public String[] getDestSceneNames() { - return mDestSceneNames; - } - - /** @hide */ public Bundle getSceneTransitionArgs() { return mTransitionArgs; } @@ -485,19 +475,33 @@ public class ActivityOptions { } /** @hide */ - public void dispatchSceneTransitionStarted(String destScene) { - if (mSceneTransitionStartedListener != null) { - Bundle data = null; - if (!TextUtils.isEmpty(destScene)) { - data = new Bundle(); - data.putString(KEY_DEST_SCENE_NAME_CHOSEN, destScene); - } + public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target) { + boolean listenerSent = false; + if (mTransitionCompleteListener != null) { + IRemoteCallback callback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + if (data == null) { + target.exitTransitionComplete(); + } else { + // TODO: Use texture id + target.sharedElementTransitionComplete(); + } + } + }; + Bundle bundle = new Bundle(); + bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder()); try { - mSceneTransitionStartedListener.sendResult(data); + mTransitionCompleteListener.sendResult(bundle); + listenerSent = true; } catch (RemoteException e) { - Log.e(TAG, "Caught exception dispatching scene transition start", e); + Log.w(TAG, "Couldn't retrieve transition notifications", e); } } + if (!listenerSent) { + target.sharedElementTransitionComplete(); + target.exitTransitionComplete(); + } } /** @hide */ @@ -508,12 +512,6 @@ public class ActivityOptions { } catch (RemoteException e) { } } - if (mSceneTransitionStartedListener != null) { - try { - mSceneTransitionStartedListener.sendResult(null); - } catch (RemoteException e) { - } - } } /** @hide */ @@ -545,9 +543,8 @@ public class ActivityOptions { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; - mSceneTransitionStartedListener = null; + mTransitionCompleteListener = null; mTransitionArgs = null; - mDestSceneNames = null; break; case ANIM_SCALE_UP: mAnimationType = otherOptions.mAnimationType; @@ -562,9 +559,8 @@ public class ActivityOptions { } } mAnimationStartedListener = null; - mSceneTransitionStartedListener = null; + mTransitionCompleteListener = null; mTransitionArgs = null; - mDestSceneNames = null; break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: @@ -579,20 +575,12 @@ public class ActivityOptions { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; - mSceneTransitionStartedListener = null; + mTransitionCompleteListener = null; mTransitionArgs = null; - mDestSceneNames = null; break; case ANIM_SCENE_TRANSITION: mAnimationType = otherOptions.mAnimationType; - if (mSceneTransitionStartedListener != null) { - try { - mSceneTransitionStartedListener.sendResult(null); - } catch (RemoteException e) { - } - } - mSceneTransitionStartedListener = otherOptions.mSceneTransitionStartedListener; - mDestSceneNames = otherOptions.mDestSceneNames; + mTransitionCompleteListener = otherOptions.mTransitionCompleteListener; mTransitionArgs = otherOptions.mTransitionArgs; mThumbnail = null; mAnimationStartedListener = null; @@ -639,10 +627,11 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: b.putInt(KEY_ANIM_TYPE, mAnimationType); - b.putStringArray(KEY_DEST_SCENE_NAMES, mDestSceneNames); b.putBundle(KEY_SCENE_TRANSITION_ARGS, mTransitionArgs); - b.putBinder(KEY_SCENE_TRANSITION_START_LISTENER, mSceneTransitionStartedListener - != null ? mSceneTransitionStartedListener.asBinder() : null); + if (mTransitionCompleteListener != null) { + b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER, + mTransitionCompleteListener.asBinder()); + } break; } return b; @@ -661,4 +650,123 @@ public class ActivityOptions { return null; } + + /** @hide */ + public interface SharedElementSource { + int getTextureId(); + } + + /** + * In the calling Activity when transitioning out, sets the Transition to listen for + * changes. + * @hide + */ + public void setExitTransition(Transition transition, SharedElementSource sharedElementSource) { + mTransitionCompleteListener = new ExitTransitionListener(transition, sharedElementSource); + } + + private static class ExitTransitionListener extends IRemoteCallback.Stub + implements Transition.TransitionListener, Animator.AnimatorListener { + private ArrayList<Animator> mSharedElementAnimators = new ArrayList<Animator>(); + private boolean mSharedElementNotified; + private Transition mExitTransition; + private IRemoteCallback mTransitionCompleteCallback; + private boolean mExitComplete; + private SharedElementSource mSharedElementSource; + + public ExitTransitionListener(Transition transition, SharedElementSource sharedElementSource) { + mSharedElementSource = sharedElementSource; + mExitTransition = transition; + mExitTransition.addListener(this); + } + + @Override + public void sendResult(Bundle data) throws RemoteException { + if (data != null) { + mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface( + data.getBinder(KEY_TRANSITION_TARGET_LISTENER)); + notifySharedElement(); + notifyExit(); + } + } + + @Override + public void onTransitionStart(Transition transition) { + ArrayMap<Animator, Transition.AnimationInfo> runningAnimators + = Transition.getRunningAnimators(); + for (Map.Entry<Animator, Transition.AnimationInfo> entry : runningAnimators.entrySet()) { + if (entry.getValue().view.getSharedElementName() != null) { + mSharedElementAnimators.add(entry.getKey()); + entry.getKey().addListener(this); + } + } + notifySharedElement(); + } + + @Override + public void onTransitionEnd(Transition transition) { + mExitComplete = true; + notifyExit(); + mExitTransition.removeListener(this); + } + + @Override + public void onTransitionCancel(Transition transition) { + mExitComplete = true; + notifyExit(); + mExitTransition.removeListener(this); + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } + + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mSharedElementAnimators.remove(animation); + notifySharedElement(); + } + + @Override + public void onAnimationCancel(Animator animation) { + mSharedElementAnimators.remove(animation); + notifySharedElement(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + private void notifySharedElement() { + if (!mSharedElementNotified && mSharedElementAnimators.isEmpty() + && mTransitionCompleteCallback != null) { + mSharedElementNotified = true; + try { + Bundle bundle = new Bundle(); + bundle.putInt(KEY_SHARED_ELEMENT_TEXTURE_ID, mSharedElementSource.getTextureId()); + mTransitionCompleteCallback.sendResult(bundle); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't notify that the transition ended", e); + } + } + } + + private void notifyExit() { + if (mExitComplete && mTransitionCompleteCallback != null) { + try { + mTransitionCompleteCallback.sendResult(null); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't notify that the transition ended", e); + } + } + } + } } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index da9ba5a7fd75..fd3f9b3c86f7 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -552,7 +552,8 @@ public abstract class Transition implements Cloneable { return false; } - private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { + /** @hide */ + public static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get(); if (runningAnimators == null) { runningAnimators = new ArrayMap<Animator, AnimationInfo>(); @@ -1077,6 +1078,9 @@ public abstract class Transition implements Cloneable { if (view == null) { return; } + if (!isValidTarget(view, view.getId())) { + return; + } boolean isListViewItem = false; if (view.getParent() instanceof ListView) { isListViewItem = true; @@ -1467,6 +1471,10 @@ public abstract class Transition implements Cloneable { mCanRemoveViews = canRemoveViews; } + public boolean canRemoveViews() { + return mCanRemoveViews; + } + @Override public String toString() { return toString(""); @@ -1629,9 +1637,10 @@ public abstract class Transition implements Cloneable { * animation should be canceled or a new animation noop'd. The structure holds * information about the state that an animation is going to, to be compared to * end state of a new animation. + * @hide */ - private static class AnimationInfo { - View view; + public static class AnimationInfo { + public View view; String name; TransitionValues values; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4b6f2b072f43..518908f8fcb6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -665,6 +665,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @attr ref android.R.styleable#View_scrollbarTrackVertical * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack + * @attr ref android.R.styleable#View_sharedElementName * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag * @attr ref android.R.styleable#View_textAlignment @@ -4038,6 +4039,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_accessibilityLiveRegion: setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT)); break; + case R.styleable.View_sharedElementName: + setSharedElementName(a.getString(attr)); + break; } } @@ -18872,6 +18876,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Specifies that the shared name of the View to be shared with another Activity. + * When transitioning between Activities, the name links a UI element in the starting + * Activity to UI element in the called Activity. Names should be unique in the + * View hierarchy. + * + * @param sharedElementName The cross-Activity View identifier. The called Activity will use + * the name to match the location with a View in its layout. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle) + */ + public void setSharedElementName(String sharedElementName) { + setTagInternal(com.android.internal.R.id.shared_element_name, sharedElementName); + } + + /** + * Returns the shared name of the View to be shared with another Activity. + * When transitioning between Activities, the name links a UI element in the starting + * Activity to UI element in the called Activity. Names should be unique in the + * View hierarchy. + * + * <p>This returns null if the View is not a shared element or the name if it is.</p> + * + * @return The name used for this View for cross-Activity transitions or null if + * this View has not been identified as shared. + */ + public String getSharedElementName() { + return (String) getTag(com.android.internal.R.id.shared_element_name); + } + + /** * Interface definition for a callback to be invoked when a hardware key event is * dispatched to this view. The callback will be invoked before the key event is * given to the view. This is only useful for hardware keyboards; a software input diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index b91091fbcca4..73b108f77f11 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -360,6 +360,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ static final int FLAG_ISOLATED_Z_VOLUME = 0x1000000; + static final int FLAG_IS_TRANSITION_GROUP = 0x2000000; + + static final int FLAG_IS_TRANSITION_GROUP_SET = 0x4000000; + /** * Indicates which types of drawing caches are to be kept in memory. * This field should be made private, so it is hidden from the SDK. @@ -556,6 +560,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case R.styleable.ViewGroup_layoutMode: setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED)); break; + case R.styleable.ViewGroup_transitionGroup: + setTransitionGroup(a.getBoolean(attr, false)); + break; } } @@ -2288,6 +2295,41 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Returns true if this ViewGroup should be considered as a single entity for removal + * when executing an Activity transition. If this is false, child elements will move + * individually during the transition. + * @return True if the ViewGroup should be acted on together during an Activity transition. + * The default value is false when the background is null and true when the background + * is not null. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle) + */ + public boolean isTransitionGroup() { + if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) { + return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0); + } else { + return getBackground() != null; + } + } + + /** + * Changes whether or not this ViewGroup should be treated as a single entity during + * ActivityTransitions. + * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit + * in Activity transitions. If false, the ViewGroup won't transition, + * only its children. If true, the entire ViewGroup will transition + * together. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle) + */ + public void setTransitionGroup(boolean isTransitionGroup) { + mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET; + if (isTransitionGroup) { + mGroupFlags |= FLAG_IS_TRANSITION_GROUP; + } else { + mGroupFlags &= ~FLAG_IS_TRANSITION_GROUP; + } + } + + /** * {@inheritDoc} */ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 1064a08a379d..11740abc3d3b 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; @@ -1374,8 +1375,41 @@ public abstract class Window { /** * Set options that can affect the transition behavior within this window. * @param options Options to set or null for none + * @hide */ - public void setTransitionOptions(Bundle options) { - throw new UnsupportedOperationException(); + public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) { + } + + /** + * A callback for Activity transitions to be told when the shared element is ready to be shown + * and start the transition to its target location. + * @hide + */ + public interface SceneTransitionListener { + void enterSharedElement(Bundle transitionArgs); + void nullPendingTransition(); + void convertFromTranslucent(); + void convertToTranslucent(); + } + + /** + * Controls how the background fade is triggered. If fadeEarly is true, the Window background + * will fade in as soon as the shared elements are ready to switch. If fadeEarly is false, + * the background will fade only after the calling Activity's exit transition completes. + * By default, the Window will fade in when the calling Activity's exit transition completes. + * + * @param fadeEarly Set to true to fade out the exiting Activity as soon as the shared elements + * are transferred. Set to false to fade out the exiting Activity as soon as + * the shared element is transferred. + * @hide + */ + public void setEarlyBackgroundTransition(boolean fadeEarly) { + } + + /** + * Start the exit transition. + * @hide + */ + public void startExitTransition(ActivityOptions activityOptions) { } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index f77d66b19c22..9526e13d5c54 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2250,6 +2250,11 @@ view with a theme override will inherit the themed context. --> <attr name="theme" /> + <!-- Specifies that the shared name of the View to be shared with another Activity. + When transitioning between Activities, the name links a UI element in the starting + Activity to UI element in the called Activity. Names should be unique in the + View hierarchy. --> + <attr name="sharedElementName" format="string" /> </declare-styleable> <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any @@ -2336,6 +2341,14 @@ <!-- Use the children's optical bounds when laying out this container. --> <enum name="opticalBounds" value="1" /> </attr> + + <!-- Sets whether or not this ViewGroup should be treated as a single entity + 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.os.Bundle)} + for more information. --> + <attr name="transitionGroup" format="boolean" /> </declare-styleable> <!-- A {@link android.view.ViewStub} lets you lazily include other XML layouts diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 5c0baaa4998f..56bb15f7275f 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -82,4 +82,5 @@ <item type="id" name="action_bar_spinner" /> <item type="id" name="current_scene" /> <item type="id" name="scene_layoutid_cache" /> + <item type="id" name="shared_element_name" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e99b5bad4332..722f965eae91 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2104,6 +2104,12 @@ <public type="attr" name="controlY1" /> <public type="attr" name="controlX2" /> <public type="attr" name="controlY2" /> + <public type="attr" name="fromSceneName" /> + <public type="attr" name="toSceneName" /> + <public type="attr" name="sharedElementName" /> + <public type="attr" name="transitionGroup" /> + + <public type="id" name="shared_element_name" /> <public type="style" name="Widget.Holo.FragmentBreadCrumbs" /> <public type="style" name="Widget.Holo.Light.FragmentBreadCrumbs" /> diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index a0ea53ccc8d1..c73d90a9044a 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -22,6 +22,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.*; +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.app.ActivityOptions; import android.transition.Scene; import android.transition.Transition; @@ -88,6 +90,7 @@ import android.view.ViewManager; import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.ViewStub; +import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; @@ -114,6 +117,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private final static String TAG = "PhoneWindow"; private final static boolean SWEEP_OPEN_MENU = false; + private static final long MAX_TRANSITION_START_WAIT = 500; + private static final long MAX_TRANSITION_FINISH_WAIT = 1000; /** * Simple callback used by the context menu and its submenus. The options @@ -136,22 +141,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ViewGroup mContentParent; SurfaceHolder.Callback2 mTakeSurfaceCallback; - + InputQueue.Callback mTakeInputQueueCallback; - + private boolean mIsFloating; private LayoutInflater mLayoutInflater; private TextView mTitleView; - + private ActionBarView mActionBar; private ActionMenuPresenterCallback mActionMenuPresenterCallback; private PanelMenuPresenterCallback mPanelMenuPresenterCallback; private TransitionManager mTransitionManager; private Scene mContentScene; - private Bundle mTransitionOptions; // The icon resource has been explicitly set elsewhere // and should not be overwritten with a default. @@ -233,6 +237,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } }; + private ActivityOptions mActivityOptions; + private SceneTransitionListener mSceneTransitionListener; + private boolean mFadeEarly = true; + static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); @@ -303,11 +311,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void setTransitionOptions(Bundle options) { - mTransitionOptions = options; - } - - @Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature @@ -378,26 +381,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } private void transitionTo(Scene scene) { - Transition selected = null; - if (mTransitionOptions != null) { - final ActivityOptions opts = new ActivityOptions(mTransitionOptions); - mTransitionOptions = null; - - String selectedName = null; - for (String sceneName : opts.getDestSceneNames()) { - final Transition t = mTransitionManager.getNamedTransition(sceneName, scene); - if (t != null) { - // TODO handle args/state; inject into t/clone with params - selected = t; - selectedName = sceneName; - break; - } + if (mContentScene == null) { + scene.enter(); + if (mActivityOptions != null) { + new EnterScene().start(); } - opts.dispatchSceneTransitionStarted(selectedName); - } - - if (selected != null) { - TransitionManager.go(scene, selected); } else { mTransitionManager.transitionTo(scene); } @@ -413,11 +401,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { public void takeSurface(SurfaceHolder.Callback2 callback) { mTakeSurfaceCallback = callback; } - + public void takeInputQueue(InputQueue.Callback callback) { mTakeInputQueueCallback = callback; } - + @Override public boolean isFloating() { return mIsFloating; @@ -3553,6 +3541,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return mVolumeControlStreamType; } + private boolean isTranslucent() { + TypedArray a = getWindowStyle(); + return a.getBoolean(a.getResourceId( + com.android.internal.R.styleable.Window_windowIsTranslucent, 0), false); + } + private static final class DrawableFeatureState { DrawableFeatureState(int _featureId) { featureId = _featureId; @@ -3986,4 +3980,218 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { void sendCloseSystemWindows(String reason) { PhoneWindowManager.sendCloseSystemWindows(getContext(), reason); } + + @Override + public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) { + mSceneTransitionListener = listener; + mActivityOptions = options; + } + + @Override + public void setEarlyBackgroundTransition(boolean fadeEarly) { + mFadeEarly = fadeEarly; + } + + @Override + public void startExitTransition(ActivityOptions activityOptions) { + Transition transition = mTransitionManager.getNamedTransition(getContentScene(), "null"); + if (transition == null) { + transition = TransitionManager.getDefaultTransition().clone(); + } + activityOptions.setExitTransition(transition, new ActivityOptions.SharedElementSource() { + @Override + public int getTextureId() { + // TODO: move shared elements to a layer and return the texture id + recurseHideExitingSharedElements(mContentParent); + return 0; + } + }); + ViewGroup sceneRoot = getContentScene().getSceneRoot(); + TransitionManager.beginDelayedTransition(sceneRoot, transition); + recurseExitNonSharedElements(mContentParent); + } + + private static void recurseExitNonSharedElements(ViewGroup viewGroup) { + int numChildren = viewGroup.getChildCount(); + for (int i = 0; i < numChildren; i++) { + View child = viewGroup.getChildAt(i); + if (child.getSharedElementName() != null || (child.getVisibility() != View.VISIBLE)) { + continue; + } + if (child instanceof ViewGroup && !((ViewGroup)child).isTransitionGroup()) { + recurseExitNonSharedElements((ViewGroup) child); + } else { + child.setVisibility(View.INVISIBLE); + } + } + } + + private static void recurseHideViews(ViewGroup viewGroup, ArrayList<View> nonSharedElements, + ArrayList<View> sharedElements) { + int numChildren = viewGroup.getChildCount(); + for (int i = 0; i < numChildren; i++) { + View child = viewGroup.getChildAt(i); + if (child.getVisibility() != View.VISIBLE) { + continue; + } + if (child.getSharedElementName() != null) { + sharedElements.add(child); + child.setVisibility(View.INVISIBLE); + } else if (child instanceof ViewGroup && !((ViewGroup)child).isTransitionGroup()) { + recurseHideViews((ViewGroup) child, nonSharedElements, sharedElements); + } else { + nonSharedElements.add(child); + child.setVisibility(View.INVISIBLE); + } + } + } + + private static void recurseHideExitingSharedElements(ViewGroup viewGroup) { + int numChildren = viewGroup.getChildCount(); + for (int i = 0; i < numChildren; i++) { + View child = viewGroup.getChildAt(i); + if (child.getVisibility() != View.VISIBLE) { + continue; + } + if (child.getSharedElementName() != null) { + child.setVisibility(View.INVISIBLE); + } else if (child instanceof ViewGroup) { + ViewGroup childViewGroup = (ViewGroup) child; + recurseHideExitingSharedElements(childViewGroup); + } + } + } + + /** + * Provides code for handling the Activity transition entering scene. + * When the first scene is laid out (onPreDraw), it makes views invisible. + * It then starts the entering transition by making non-shared elements visible. When + * the entering transition is started, the calling Activity is notified that + * this Activity is ready to receive the shared element. When the calling Activity notifies + * that the shared element is ready, this Activity is notified through the + * SceneTransitionListener. + * + * This class also takes into account fading the background -- either waiting until the + * shared element is ready or the calling Activity's exit transition is complete. + */ + private class EnterScene implements ViewTreeObserver.OnPreDrawListener, Runnable, + ActivityOptions.ActivityTransitionTarget, Animator.AnimatorListener { + private boolean mSharedElementReadyReceived; + private boolean mAllDone; + private Handler mHandler = new Handler(); + private boolean mEnterTransitionStarted; + private ArrayList<View> mSharedElements = new ArrayList<View>(); + + public EnterScene() { + mSceneTransitionListener.nullPendingTransition(); + Drawable background = getDecorView().getBackground(); + if (background != null) { + setBackgroundDrawable(null); + background.setAlpha(0); + setBackgroundDrawable(background); + } + mSceneTransitionListener.convertToTranslucent(); + } + + @Override + public boolean onPreDraw() { + ViewTreeObserver observer = mContentParent.getViewTreeObserver(); + observer.removeOnPreDrawListener(this); + if (!mEnterTransitionStarted && mSceneTransitionListener != null) { + mEnterTransitionStarted = true; + ArrayList<View> enteringViews = new ArrayList<View>(); + recurseHideViews(mContentParent, enteringViews, mSharedElements); + Transition transition = getTransitionManager().getNamedTransition("null", + mContentScene); + if (transition == null) { + transition = TransitionManager.getDefaultTransition().clone(); + } + TransitionManager.beginDelayedTransition(mContentParent, transition); + for (View hidden : enteringViews) { + hidden.setVisibility(View.VISIBLE); + } + observer.addOnPreDrawListener(this); + } else { + mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT); + mActivityOptions.dispatchSceneTransitionStarted(this); + } + return true; + } + + public void start() { + ViewTreeObserver observer = mContentParent.getViewTreeObserver(); + observer.addOnPreDrawListener(this); + } + + @Override + public void run() { + exitTransitionComplete(); + } + + @Override + public void sharedElementTransitionComplete() { + if (!mSharedElementReadyReceived) { + mSharedElementReadyReceived = true; + mHandler.removeCallbacks(this); + mHandler.postDelayed(this, MAX_TRANSITION_FINISH_WAIT); + for (View sharedElement: mSharedElements) { + sharedElement.setVisibility(View.VISIBLE); + } + mSharedElements.clear(); + mContentParent.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mContentParent.getViewTreeObserver().removeOnPreDrawListener(this); + mSceneTransitionListener.enterSharedElement( + mActivityOptions.getSceneTransitionArgs()); + return false; + } + }); + if (mFadeEarly) { + fadeInBackground(); + } + } + } + + private void fadeInBackground() { + Drawable background = getDecorView().getBackground(); + if (background == null) { + mSceneTransitionListener.convertFromTranslucent(); + } else { + ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255); + animator.addListener(this); + animator.start(); + } + } + + @Override + public void exitTransitionComplete() { + if (mAllDone) { + return; + } + mAllDone = true; + sharedElementTransitionComplete(); + mHandler.removeCallbacks(this); + if (!mFadeEarly) { + fadeInBackground(); + } + } + + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mSceneTransitionListener.convertFromTranslucent(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + } } |