diff options
-rw-r--r-- | api/current.txt | 26 | ||||
-rw-r--r-- | core/java/android/app/Activity.java | 93 | ||||
-rw-r--r-- | core/java/android/app/ActivityOptions.java | 230 | ||||
-rw-r--r-- | core/java/android/transition/Transition.java | 45 | ||||
-rw-r--r-- | core/java/android/transition/TransitionInflater.java | 43 | ||||
-rw-r--r-- | core/java/android/transition/TransitionManager.java | 150 | ||||
-rw-r--r-- | core/java/android/view/View.java | 29 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 40 | ||||
-rw-r--r-- | core/java/android/view/Window.java | 38 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ActionBarImpl.java | 5 | ||||
-rw-r--r-- | core/res/res/layout-xlarge/screen_action_bar.xml | 1 | ||||
-rw-r--r-- | core/res/res/layout/screen_action_bar.xml | 1 | ||||
-rw-r--r-- | core/res/res/layout/screen_custom_title.xml | 1 | ||||
-rw-r--r-- | core/res/res/values/attrs.xml | 8 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 2 | ||||
-rw-r--r-- | policy/src/com/android/internal/policy/impl/PhoneWindow.java | 363 |
16 files changed, 621 insertions, 454 deletions
diff --git a/api/current.txt b/api/current.txt index 8340265..5cf7e48 100644 --- a/api/current.txt +++ b/api/current.txt @@ -345,7 +345,7 @@ package android { field public static final int canRetrieveWindowContent = 16843653; // 0x1010385 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final deprecated int capitalize = 16843113; // 0x1010169 - field public static final int castsShadow = 16843777; // 0x1010401 + field public static final int castsShadow = 16843775; // 0x10103ff field public static final int category = 16843752; // 0x10103e8 field public static final int centerBright = 16842956; // 0x10100cc field public static final int centerColor = 16843275; // 0x101020b @@ -542,7 +542,6 @@ 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 = 16843773; // 0x10103fd field public static final int fromXDelta = 16843206; // 0x10101c6 field public static final int fromXScale = 16843202; // 0x10101c2 field public static final int fromYDelta = 16843208; // 0x10101c8 @@ -890,7 +889,7 @@ package android { field public static final int required = 16843406; // 0x101028e field public static final int requiredAccountType = 16843734; // 0x10103d6 field public static final int requiredForAllUsers = 16843728; // 0x10103d0 - field public static final int requiredForProfile = 16843778; // 0x1010402 + field public static final int requiredForProfile = 16843776; // 0x1010400 field public static final int requiresFadingEdge = 16843685; // 0x10103a5 field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364 field public static final int resizeMode = 16843619; // 0x1010363 @@ -961,7 +960,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 = 16843775; // 0x10103ff + field public static final int sharedElementName = 16843773; // 0x10103fd field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserLabel = 16843361; // 0x1010261 field public static final int shouldDisableView = 16843246; // 0x10101ee @@ -1145,7 +1144,6 @@ 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 = 16843774; // 0x10103fe field public static final int toXDelta = 16843207; // 0x10101c7 field public static final int toXScale = 16843203; // 0x10101c3 field public static final int toYDelta = 16843209; // 0x10101c9 @@ -1161,7 +1159,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 = 16843776; // 0x1010400 + field public static final int transitionGroup = 16843774; // 0x10103fe field public static final int transitionOrdering = 16843744; // 0x10103e0 field public static final int translationX = 16843554; // 0x1010322 field public static final int translationY = 16843555; // 0x1010323 @@ -3041,7 +3039,6 @@ 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(); @@ -3063,6 +3060,8 @@ package android.app { method public void onAttachFragment(android.app.Fragment); method public void onAttachedToWindow(); method public void onBackPressed(); + method public void onCaptureSharedElementEnd(); + method public void onCaptureSharedElementStart(android.transition.Transition); method protected void onChildTitleChanged(android.app.Activity, java.lang.CharSequence); method public void onConfigurationChanged(android.content.res.Configuration); method public void onContentChanged(); @@ -3137,7 +3136,6 @@ 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); @@ -3178,7 +3176,6 @@ 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); @@ -3349,7 +3346,8 @@ 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 makeSceneTransitionAnimation(android.view.View, java.lang.String); + method public static android.app.ActivityOptions makeSceneTransitionAnimation(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); @@ -26829,15 +26827,11 @@ package android.transition { ctor public TransitionManager(); method public static void beginDelayedTransition(android.view.ViewGroup); method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition); - method public android.transition.Transition getNamedTransition(java.lang.String, android.transition.Scene); - method public android.transition.Transition getNamedTransition(android.transition.Scene, java.lang.String); - method public java.lang.String[] getTargetSceneNames(android.transition.Scene); method public static void go(android.transition.Scene); method public static void go(android.transition.Scene, android.transition.Transition); + method public void setExitTransition(android.transition.Scene, android.transition.Transition); method public void setTransition(android.transition.Scene, android.transition.Transition); method public void setTransition(android.transition.Scene, android.transition.Scene, android.transition.Transition); - method public void setTransition(android.transition.Scene, java.lang.String, android.transition.Transition); - method public void setTransition(java.lang.String, android.transition.Scene, android.transition.Transition); method public void transitionTo(android.transition.Scene); } @@ -29808,6 +29802,7 @@ package android.view { method public abstract boolean isFloating(); method public abstract boolean isShortcutKey(int, android.view.KeyEvent); method public final void makeActive(); + method public void mapTransitionTargets(java.util.Map<java.lang.String, java.lang.String>); method protected abstract void onActive(); method public abstract void onConfigurationChanged(android.content.res.Configuration); method public abstract void openPanel(int, android.view.KeyEvent); @@ -29846,6 +29841,7 @@ 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 setTriggerEarlyEnterTransition(boolean); 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 9a3c290..3297fe0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -18,6 +18,7 @@ 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; @@ -3316,7 +3317,7 @@ public class Activity extends ContextThemeWrapper @Nullable Bundle appSearchData, boolean globalSearch) { ensureSearchManager(); mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), - appSearchData, globalSearch); + appSearchData, globalSearch); } /** @@ -3446,7 +3447,11 @@ public class Activity extends ContextThemeWrapper * @see #startActivity */ public void startActivityForResult(Intent intent, int requestCode) { - startActivityForResult(intent, requestCode, null); + Bundle options = null; + if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + options = ActivityOptions.makeSceneTransitionAnimation(null).toBundle(); + } + startActivityForResult(intent, requestCode, options); } /** @@ -3484,12 +3489,15 @@ public class Activity extends ContextThemeWrapper * @see #startActivity */ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { - TransitionManager tm = getContentTransitionManager(); - if (tm != null && options != null) { + if (options != null) { ActivityOptions activityOptions = new ActivityOptions(options); if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { - getWindow().startExitTransition(activityOptions); - options = activityOptions.toBundle(); + if (mActionBar != null) { + ArrayMap<String, View> sharedElementMap = new ArrayMap<String, View>(); + mActionBar.captureSharedElements(sharedElementMap); + activityOptions.addSharedElements(sharedElementMap); + } + options = mWindow.startExitTransition(activityOptions); } } if (mParent == null) { @@ -3664,7 +3672,7 @@ public class Activity extends ContextThemeWrapper */ @Override public void startActivity(Intent intent) { - startActivity(intent, null); + this.startActivity(intent, null); } /** @@ -4720,7 +4728,8 @@ public class Activity extends ContextThemeWrapper */ public final void setProgressBarIndeterminate(boolean indeterminate) { getWindow().setFeatureInt(Window.FEATURE_PROGRESS, - indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF); + indeterminate ? Window.PROGRESS_INDETERMINATE_ON + : Window.PROGRESS_INDETERMINATE_OFF); } /** @@ -5330,12 +5339,6 @@ public class Activity extends ContextThemeWrapper mTransitionActivityOptions = activityOptions; sceneTransitionListener = new Window.SceneTransitionListener() { @Override - public void enterSharedElement(Bundle transitionArgs) { - startSharedElementTransition(transitionArgs); - mTransitionActivityOptions = null; - } - - @Override public void nullPendingTransition() { overridePendingTransition(0, 0); } @@ -5349,6 +5352,16 @@ public class Activity extends ContextThemeWrapper public void convertToTranslucent() { Activity.this.convertToTranslucent(null); } + + @Override + public void sharedElementStart(Transition transition) { + Activity.this.onCaptureSharedElementStart(transition); + } + + @Override + public void sharedElementEnd() { + Activity.this.onCaptureSharedElementEnd(); + } }; } @@ -5542,53 +5555,23 @@ public class Activity extends ContextThemeWrapper } /** - * 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> + * Called when setting up Activity Scene transitions when the start state for shared + * elements has been captured. Override this method to modify the start position of shared + * elements for the entry Transition. * - * @return The Bundle passed into Bundle parameter of - * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle)} - * in the calling Activity. + * @param transition The <code>Transition</code> being used to change + * bounds of shared elements in the source Activity to + * the bounds defined by the entering Scene. */ - public Bundle getTransitionArgs() { - if (mTransitionActivityOptions == null) { - return null; - } - return mTransitionActivityOptions.getSceneTransitionArgs(); + public void onCaptureSharedElementStart(Transition transition) { } /** - * 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. + * Called when setting up Activity Scene transitions when the final state for + * shared elements state has been captured. Override this method to modify the destination + * position of shared elements for the entry Transition. */ - 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); + public void onCaptureSharedElementEnd() { } /** diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 3f97c40..07247ff 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -16,7 +16,6 @@ package android.app; -import android.animation.Animator; import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; @@ -24,8 +23,8 @@ import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; import android.transition.Transition; -import android.util.ArrayMap; import android.util.Log; +import android.util.Pair; import android.view.View; import java.util.ArrayList; @@ -100,12 +99,6 @@ public class ActivityOptions { public static final String KEY_ANIM_START_LISTENER = "android:animStartListener"; /** - * Arguments for the scene transition about to begin. - * @hide - */ - public static final String KEY_SCENE_TRANSITION_ARGS = "android:sceneTransitionArgs"; - - /** * For Activity transitions, the calling Activity's TransitionListener used to * notify the called Activity when the shared element and the exit transitions * complete. @@ -120,9 +113,15 @@ public class ActivityOptions { private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener"; /** - * The shared element's texture ID (TODO: not used yet). + * The names of shared elements that are transitioned to the started Activity. + * This is also the name of shared elements that the started Activity accepted. */ - private static final String KEY_SHARED_ELEMENT_TEXTURE_ID = "android:sharedElementTextureId"; + private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names"; + + /** + * The shared elements names of the views in the calling Activity. + */ + private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names"; /** @hide */ public static final int ANIM_NONE = 0; @@ -146,9 +145,10 @@ public class ActivityOptions { private int mStartY; private int mStartWidth; private int mStartHeight; - private Bundle mTransitionArgs; private IRemoteCallback mAnimationStartedListener; private IRemoteCallback mTransitionCompleteListener; + private ArrayList<String> mSharedElementNames; + private ArrayList<String> mLocalElementNames; /** * Create an ActivityOptions specifying a custom animation to run when @@ -226,7 +226,7 @@ public class ActivityOptions { /** @hide */ public interface ActivityTransitionTarget { - void sharedElementTransitionComplete(); + void sharedElementTransitionComplete(Bundle transitionArgs); void exitTransitionComplete(); } @@ -348,35 +348,51 @@ public class ActivityOptions { } /** - * 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. - * - * 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> + * Create an ActivityOptions to transition between Activities using cross-Activity scene + * animations. This method carries the position of one shared element to the started Activity. * * <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 sharedElement The View to transition to the started Activity. sharedElement must + * have a non-null sharedElementName. + * @param sharedElementName The shared element name as used in the target Activity. This may + * be null if it has the same name as sharedElement. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + */ + public static ActivityOptions makeSceneTransitionAnimation(View sharedElement, + String sharedElementName) { + return makeSceneTransitionAnimation( + new Pair<View, String>(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. * - * @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) + * <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 sharedElements The View to transition to the started Activity along with the + * shared element name as used in the started Activity. The view + * must have a non-null sharedElementName. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. */ - public static ActivityOptions makeSceneTransitionAnimation(Bundle args) { + public static ActivityOptions makeSceneTransitionAnimation( + Pair<View, String>... sharedElements) { ActivityOptions opts = new ActivityOptions(); opts.mAnimationType = ANIM_SCENE_TRANSITION; - opts.mTransitionArgs = args; + opts.mSharedElementNames = new ArrayList<String>(); + opts.mLocalElementNames = new ArrayList<String>(); + + if (sharedElements != null) { + for (Pair<View, String> sharedElement : sharedElements) { + opts.addSharedElement(sharedElement.first, sharedElement.second); + } + } return opts; } @@ -412,9 +428,10 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: - mTransitionArgs = opts.getBundle(KEY_SCENE_TRANSITION_ARGS); mTransitionCompleteListener = IRemoteCallback.Stub.asInterface( opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER)); + mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); + mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES); break; } } @@ -465,17 +482,19 @@ public class ActivityOptions { } /** @hide */ - public Bundle getSceneTransitionArgs() { - return mTransitionArgs; - } - - /** @hide */ public IRemoteCallback getOnAnimationStartListener() { return mAnimationStartedListener; } /** @hide */ - public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target) { + public ArrayList<String> getSharedElementNames() { return mSharedElementNames; } + + /** @hide */ + public ArrayList<String> getLocalElementNames() { return mLocalElementNames; } + + /** @hide */ + public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target, + ArrayList<String> sharedElementNames) { boolean listenerSent = false; if (mTransitionCompleteListener != null) { IRemoteCallback callback = new IRemoteCallback.Stub() { @@ -484,13 +503,13 @@ public class ActivityOptions { if (data == null) { target.exitTransitionComplete(); } else { - // TODO: Use texture id - target.sharedElementTransitionComplete(); + target.sharedElementTransitionComplete(data); } } }; Bundle bundle = new Bundle(); bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder()); + bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames); try { mTransitionCompleteListener.sendResult(bundle); listenerSent = true; @@ -499,12 +518,23 @@ public class ActivityOptions { } } if (!listenerSent) { - target.sharedElementTransitionComplete(); + target.sharedElementTransitionComplete(null); target.exitTransitionComplete(); } } /** @hide */ + public void dispatchSharedElementsReady() { + if (mTransitionCompleteListener != null) { + try { + mTransitionCompleteListener.sendResult(null); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't synchronize shared elements", e); + } + } + } + + /** @hide */ public void abort() { if (mAnimationStartedListener != null) { try { @@ -530,6 +560,8 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } + mSharedElementNames = null; + mLocalElementNames = null; switch (otherOptions.mAnimationType) { case ANIM_CUSTOM: mAnimationType = otherOptions.mAnimationType; @@ -544,7 +576,6 @@ public class ActivityOptions { } mAnimationStartedListener = otherOptions.mAnimationStartedListener; mTransitionCompleteListener = null; - mTransitionArgs = null; break; case ANIM_SCALE_UP: mAnimationType = otherOptions.mAnimationType; @@ -560,7 +591,6 @@ public class ActivityOptions { } mAnimationStartedListener = null; mTransitionCompleteListener = null; - mTransitionArgs = null; break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: @@ -576,14 +606,14 @@ public class ActivityOptions { } mAnimationStartedListener = otherOptions.mAnimationStartedListener; mTransitionCompleteListener = null; - mTransitionArgs = null; break; case ANIM_SCENE_TRANSITION: mAnimationType = otherOptions.mAnimationType; mTransitionCompleteListener = otherOptions.mTransitionCompleteListener; - mTransitionArgs = otherOptions.mTransitionArgs; mThumbnail = null; mAnimationStartedListener = null; + mSharedElementNames = otherOptions.mSharedElementNames; + mLocalElementNames = otherOptions.mLocalElementNames; break; } } @@ -627,11 +657,12 @@ public class ActivityOptions { break; case ANIM_SCENE_TRANSITION: b.putInt(KEY_ANIM_TYPE, mAnimationType); - b.putBundle(KEY_SCENE_TRANSITION_ARGS, mTransitionArgs); if (mTransitionCompleteListener != null) { b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener.asBinder()); } + b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames); + b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames); break; } return b; @@ -652,32 +683,52 @@ public class ActivityOptions { } /** @hide */ - public interface SharedElementSource { - int getTextureId(); + public void addSharedElements(Map<String, View> sharedElements) { + for (Map.Entry<String, View> entry : sharedElements.entrySet()) { + addSharedElement(entry.getValue(), entry.getKey()); + } } - /** - * 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); + /** @hide */ + public void updateSceneTransitionAnimation(Transition exitTransition, + Transition sharedElementTransition, SharedElementSource sharedElementSource) { + mTransitionCompleteListener = new ExitTransitionListener(exitTransition, + sharedElementTransition, sharedElementSource); + } + + private void addSharedElement(View view, String name) { + String sharedElementName = view.getSharedElementName(); + if (name == null) { + name = sharedElementName; + } + mSharedElementNames.add(name); + mLocalElementNames.add(sharedElementName); + } + + /** @hide */ + public interface SharedElementSource { + Bundle getSharedElementExitState(); + void acceptedSharedElements(ArrayList<String> sharedElementNames); + void hideSharedElements(); } private static class ExitTransitionListener extends IRemoteCallback.Stub - implements Transition.TransitionListener, Animator.AnimatorListener { - private ArrayList<Animator> mSharedElementAnimators = new ArrayList<Animator>(); + implements Transition.TransitionListener { private boolean mSharedElementNotified; private Transition mExitTransition; + private Transition mSharedElementTransition; private IRemoteCallback mTransitionCompleteCallback; private boolean mExitComplete; + private boolean mSharedElementComplete; private SharedElementSource mSharedElementSource; - public ExitTransitionListener(Transition transition, SharedElementSource sharedElementSource) { + public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition, + SharedElementSource sharedElementSource) { mSharedElementSource = sharedElementSource; - mExitTransition = transition; + mExitTransition = exitTransition; mExitTransition.addListener(this); + mSharedElementTransition = sharedElementTransition; + mSharedElementTransition.addListener(this); } @Override @@ -685,36 +736,36 @@ public class ActivityOptions { if (data != null) { mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface( data.getBinder(KEY_TRANSITION_TARGET_LISTENER)); + ArrayList<String> sharedElementNames + = data.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); + mSharedElementSource.acceptedSharedElements(sharedElementNames); notifySharedElement(); notifyExit(); + } else { + mSharedElementSource.hideSharedElements(); } } @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); + if (transition == mExitTransition) { + mExitComplete = true; + notifyExit(); + mExitTransition.removeListener(this); + } else { + mSharedElementComplete = true; + notifySharedElement(); + mSharedElementTransition.removeListener(this); + } } @Override public void onTransitionCancel(Transition transition) { - mExitComplete = true; - notifyExit(); - mExitTransition.removeListener(this); + onTransitionEnd(transition); } @Override @@ -725,34 +776,13 @@ public class ActivityOptions { 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() + if (!mSharedElementNotified && mSharedElementComplete && mTransitionCompleteCallback != null) { mSharedElementNotified = true; try { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_SHARED_ELEMENT_TEXTURE_ID, mSharedElementSource.getTextureId()); - mTransitionCompleteCallback.sendResult(bundle); + Bundle sharedElementState = mSharedElementSource.getSharedElementExitState(); + mTransitionCompleteCallback.sendResult(sharedElementState); } 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 fd3f9b3..9f1e72d 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -552,8 +552,7 @@ public abstract class Transition implements Cloneable { return false; } - /** @hide */ - public static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { + private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get(); if (runningAnimators == null) { runningAnimators = new ArrayMap<Animator, AnimationInfo>(); @@ -1113,30 +1112,32 @@ public abstract class Transition implements Cloneable { } } } - TransitionValues values = new TransitionValues(); - values.view = view; - if (start) { - captureStartValues(values); - } else { - captureEndValues(values); - } - if (start) { - if (!isListViewItem) { - mStartValues.viewValues.put(view, values); - if (id >= 0) { - mStartValues.idValues.put((int) id, values); - } + if (view.getParent() instanceof ViewGroup) { + TransitionValues values = new TransitionValues(); + values.view = view; + if (start) { + captureStartValues(values); } else { - mStartValues.itemIdValues.put(itemId, values); + captureEndValues(values); } - } else { - if (!isListViewItem) { - mEndValues.viewValues.put(view, values); - if (id >= 0) { - mEndValues.idValues.put((int) id, values); + if (start) { + if (!isListViewItem) { + mStartValues.viewValues.put(view, values); + if (id >= 0) { + mStartValues.idValues.put((int) id, values); + } + } else { + mStartValues.itemIdValues.put(itemId, values); } } else { - mEndValues.itemIdValues.put(itemId, values); + if (!isListViewItem) { + mEndValues.viewValues.put(view, values); + if (id >= 0) { + mEndValues.idValues.put((int) id, values); + } + } else { + mEndValues.itemIdValues.put(itemId, values); + } } } if (view instanceof ViewGroup) { diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 9fa554c..912f2ed 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -285,46 +285,27 @@ public class TransitionInflater { com.android.internal.R.styleable.TransitionManager); int transitionId = a.getResourceId( com.android.internal.R.styleable.TransitionManager_transition, -1); - Scene fromScene = null, toScene = null; int fromId = a.getResourceId( com.android.internal.R.styleable.TransitionManager_fromScene, -1); - if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext); + Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext); int toId = a.getResourceId( com.android.internal.R.styleable.TransitionManager_toScene, -1); - if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext); - String fromName = a.getString( - com.android.internal.R.styleable.TransitionManager_fromSceneName); - String toName = a.getString( - com.android.internal.R.styleable.TransitionManager_toSceneName); + Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); + if (transitionId >= 0) { Transition transition = inflateTransition(transitionId); if (transition != null) { - if (fromScene != null) { - boolean hasDest = false; - if (toScene != null) { - transitionManager.setTransition(fromScene, toScene, transition); - hasDest = true; - } - - if (!TextUtils.isEmpty(toName)) { - transitionManager.setTransition(fromScene, toName, transition); - hasDest = true; - } - - if (!hasDest) { - throw new RuntimeException("No matching toScene or toSceneName for given " + - "fromScene for transition ID " + transitionId); - } - } else if (toId >= 0) { - transitionManager.setTransition(toScene, transition); - } - if (fromName != null) { - if (toScene != null) { - transitionManager.setTransition(fromName, toScene, transition); - } else { - throw new RuntimeException("No matching toScene for given fromSceneName " + + if (fromScene == null) { + if (toScene == null) { + throw new RuntimeException("No matching fromScene or toScene " + "for transition ID " + transitionId); + } else { + transitionManager.setTransition(toScene, transition); } + } else if (toScene == null) { + transitionManager.setExitTransition(fromScene, transition); + } else { + transitionManager.setTransition(fromScene, toScene, transition); } } } diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 0106f7f..f3abfb0 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -70,12 +70,9 @@ public class TransitionManager { private static final String[] EMPTY_STRINGS = new String[0]; ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); + ArrayMap<Scene, Transition> mExitSceneTransitions = new ArrayMap<Scene, Transition>(); ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); - ArrayMap<Scene, ArrayMap<String, Transition>> mSceneNameTransitions = - new ArrayMap<Scene, ArrayMap<String, Transition>>(); - ArrayMap<String, ArrayMap<Scene, Transition>> mNameSceneTransitions = - new ArrayMap<String, ArrayMap<Scene, Transition>>(); private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>> sRunningTransitions = new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>(); @@ -122,6 +119,21 @@ public class TransitionManager { } /** + * Sets a specific transition to occur when the given scene is exited. This + * has the lowest priority -- if a Scene-to-Scene transition or + * Scene enter transition can be applied, it will. + * + * @param scene The scene which, when exited, will cause the given + * transition to run. + * @param transition The transition that will play when the given scene is + * exited. A value of null will result in the default behavior of + * using the default transition instead. + */ + public void setExitTransition(Scene scene, Transition transition) { + mExitSceneTransitions.put(scene, transition); + } + + /** * Sets a specific transition to occur when the given pair of scenes is * exited/entered. * @@ -169,6 +181,9 @@ public class TransitionManager { } } transition = mSceneTransitions.get(scene); + if (transition == null && sceneRoot != null) { + transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot)); + } return (transition != null) ? transition : sDefaultTransition; } @@ -224,138 +239,31 @@ public class TransitionManager { } /** - * Retrieve the transition from a named scene to a target defined scene if one has been + * Retrieve the transition to a target defined scene if one has been * associated with this TransitionManager. * - * <p>A named scene is an indirect link for a transition. Fundamentally a named - * scene represents a potentially arbitrary intersection point of two otherwise independent - * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" - * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. - * In this way applications may define an API for more sophisticated transitions between - * caller and called activities very similar to the way that <code>Intent</code> extras - * define APIs for arguments and data propagation between activities.</p> - * - * @param fromName Named scene that this transition corresponds to * @param toScene Target scene that this transition will move to - * @return Transition corresponding to the given fromName and toScene or null + * @return Transition corresponding to the given toScene or null * if no association exists in this TransitionManager * - * @see #setTransition(String, Scene, Transition) + * @see #setTransition(Scene, Transition) + * @hide */ - public Transition getNamedTransition(String fromName, Scene toScene) { - ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName); - if (m != null) { - return m.get(toScene); - } - return null; + public Transition getEnterTransition(Scene toScene) { + return mSceneTransitions.get(toScene); } /** * Retrieve the transition from a defined scene to a target named scene if one has been * associated with this TransitionManager. * - * <p>A named scene is an indirect link for a transition. Fundamentally a named - * scene represents a potentially arbitrary intersection point of two otherwise independent - * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" - * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. - * In this way applications may define an API for more sophisticated transitions between - * caller and called activities very similar to the way that <code>Intent</code> extras - * define APIs for arguments and data propagation between activities.</p> - * * @param fromScene Scene that this transition starts from - * @param toName Name of the target scene - * @return Transition corresponding to the given fromScene and toName or null + * @return Transition corresponding to the given fromScene or null * if no association exists in this TransitionManager + * @hide */ - public Transition getNamedTransition(Scene fromScene, String toName) { - ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene); - if (m != null) { - return m.get(toName); - } - return null; - } - - /** - * Retrieve the supported target named scenes when transitioning away from the given scene. - * - * <p>A named scene is an indirect link for a transition. Fundamentally a named - * scene represents a potentially arbitrary intersection point of two otherwise independent - * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" - * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. - * In this way applications may define an API for more sophisticated transitions between - * caller and called activities very similar to the way that <code>Intent</code> extras - * define APIs for arguments and data propagation between activities.</p> - * - * @param fromScene Scene to transition from - * @return An array of Strings naming each supported transition starting from - * <code>fromScene</code>. If no transitions to a named scene from the given - * scene are supported this function will return a String[] of length 0. - * - * @see #setTransition(Scene, String, Transition) - */ - public String[] getTargetSceneNames(Scene fromScene) { - final ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene); - if (m == null) { - return EMPTY_STRINGS; - } - final int count = m.size(); - final String[] result = new String[count]; - for (int i = 0; i < count; i++) { - result[i] = m.keyAt(i); - } - return result; - } - - /** - * Set a transition from a specific scene to a named scene. - * - * <p>A named scene is an indirect link for a transition. Fundamentally a named - * scene represents a potentially arbitrary intersection point of two otherwise independent - * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" - * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. - * In this way applications may define an API for more sophisticated transitions between - * caller and called activities very similar to the way that <code>Intent</code> extras - * define APIs for arguments and data propagation between activities.</p> - * - * @param fromScene Scene to transition from - * @param toName Named scene to transition to - * @param transition Transition to use - * - * @see #getTargetSceneNames(Scene) - */ - public void setTransition(Scene fromScene, String toName, Transition transition) { - ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene); - if (m == null) { - m = new ArrayMap<String, Transition>(); - mSceneNameTransitions.put(fromScene, m); - } - m.put(toName, transition); - } - - /** - * Set a transition from a named scene to a concrete scene. - * - * <p>A named scene is an indirect link for a transition. Fundamentally a named - * scene represents a potentially arbitrary intersection point of two otherwise independent - * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" - * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. - * In this way applications may define an API for more sophisticated transitions between - * caller and called activities very similar to the way that <code>Intent</code> extras - * define APIs for arguments and data propagation between activities.</p> - * - * @param fromName Named scene to transition from - * @param toScene Scene to transition to - * @param transition Transition to use - * - * @see #getNamedTransition(String, Scene) - */ - public void setTransition(String fromName, Scene toScene, Transition transition) { - ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName); - if (m == null) { - m = new ArrayMap<Scene, Transition>(); - mNameSceneTransitions.put(fromName, m); - } - m.put(toScene, transition); + public Transition getExitTransition(Scene fromScene) { + return mExitSceneTransitions.get(fromScene); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e9082c3..99aee29 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -101,7 +101,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -18842,6 +18844,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions. + * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and + * a normal View or a ViewGroup with + * {@link android.view.ViewGroup#isTransitionGroup()} true. + * @hide + */ + public void captureTransitioningViews(List<View> transitioningViews) { + if (getVisibility() == View.VISIBLE) { + transitioningViews.add(this); + } + } + + /** + * Adds all Views that have {@link #getSharedElementName()} non-null to sharedElements. + * @param sharedElements Will contain all Views in the hierarchy having a shared element name. + * @hide + */ + public void findSharedElements(Map<String, View> sharedElements) { + if (getVisibility() == VISIBLE) { + String sharedElementName = getSharedElementName(); + if (sharedElementName != null) { + sharedElements.put(sharedElementName, this); + } + } + } + // // Properties // diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 9cd3c9d..cf5e8cf 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -31,6 +31,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; +import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; @@ -50,6 +51,8 @@ import com.android.internal.util.Predicate; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Map; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; @@ -2300,14 +2303,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * 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) + * is not null or if {@link #getSharedElementName()} is not null. */ public boolean isTransitionGroup() { if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) { return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0); } else { - return getBackground() != null; + return getBackground() != null || getSharedElementName() != null; } } @@ -2318,7 +2320,6 @@ 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.os.Bundle) */ public void setTransitionGroup(boolean isTransitionGroup) { mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET; @@ -5880,6 +5881,37 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager protected void onSetLayoutParams(View child, LayoutParams layoutParams) { } + /** @hide */ + @Override + public void captureTransitioningViews(List<View> transitioningViews) { + if (getVisibility() != View.VISIBLE) { + return; + } + if (isTransitionGroup()) { + transitioningViews.add(this); + } else { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.captureTransitioningViews(transitioningViews); + } + } + } + + /** @hide */ + @Override + public void findSharedElements(Map<String, View> sharedElements) { + if (getVisibility() != VISIBLE) { + return; + } + super.findSharedElements(sharedElements); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.findSharedElements(sharedElements); + } + } + /** * LayoutParams are used by views to tell their parents how they want to be * laid out. See diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 11740ab..24b8248 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -29,9 +29,12 @@ import android.os.Bundle; import android.os.IBinder; import android.os.SystemProperties; import android.transition.Scene; +import android.transition.Transition; import android.transition.TransitionManager; import android.view.accessibility.AccessibilityEvent; +import java.util.Map; + /** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the @@ -1386,30 +1389,43 @@ public abstract class Window { * @hide */ public interface SceneTransitionListener { - void enterSharedElement(Bundle transitionArgs); void nullPendingTransition(); void convertFromTranslucent(); void convertToTranslucent(); + void sharedElementStart(Transition transition); + void sharedElementEnd(); } /** - * 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. + * Controls when the Activity enter scene is triggered and the background is faded in. If + * triggerEarly is true, the enter scene will begin as soon as possible and the background + * will fade in when all shared elements are ready to begin transitioning. If triggerEarly is + * false, the Activity enter scene and background fade will be triggered 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 + * @param triggerEarly Set to true to have the Activity enter scene transition in as early as + * possible or set to false to wait for the calling Activity to exit first. */ - public void setEarlyBackgroundTransition(boolean fadeEarly) { + public void setTriggerEarlyEnterTransition(boolean triggerEarly) { } /** * Start the exit transition. * @hide */ - public void startExitTransition(ActivityOptions activityOptions) { + public Bundle startExitTransition(ActivityOptions options) { + return null; + } + + /** + * On entering Activity Scene transitions, shared element names may be mapped from a + * source Activity's specified name to a unique shared element name in the View hierarchy. + * Under most circumstances, mapping is not necessary - a single View will have the + * shared element name given by the calling Activity. However, if there are several similar + * Views (e.g. in a ListView), the correct shared element must be mapped. + * @param sharedElementNames A mapping from the calling Activity's assigned shared element + * name to a unique shared element name in the View hierarchy. + */ + public void mapTransitionTargets(Map<String, String> sharedElementNames) { } } diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 0a80495..cc51a8b 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -57,6 +57,7 @@ import android.widget.SpinnerAdapter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Map; /** * ActionBarImpl is the ActionBar implementation used @@ -355,6 +356,10 @@ public class ActionBarImpl extends ActionBar { setSubtitle(mContext.getString(resId)); } + public void captureSharedElements(Map<String, View> sharedElements) { + mContainerView.findSharedElements(sharedElements); + } + public void setSelectedNavigationItem(int position) { switch (mActionView.getNavigationMode()) { case NAVIGATION_MODE_TABS: diff --git a/core/res/res/layout-xlarge/screen_action_bar.xml b/core/res/res/layout-xlarge/screen_action_bar.xml index e495e53..d2fe9fa 100644 --- a/core/res/res/layout-xlarge/screen_action_bar.xml +++ b/core/res/res/layout-xlarge/screen_action_bar.xml @@ -34,6 +34,7 @@ the Action Bar enabled overlaying application content. android:layout_height="wrap_content" android:layout_alignParentTop="true" style="?android:attr/actionBarStyle" + android:sharedElementName="android:action_bar" android:gravity="top"> <com.android.internal.widget.ActionBarView android:id="@+id/action_bar" diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml index b1889a2..7b9a20b 100644 --- a/core/res/res/layout/screen_action_bar.xml +++ b/core/res/res/layout/screen_action_bar.xml @@ -33,6 +33,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_height="wrap_content" android:layout_alignParentTop="true" style="?android:attr/actionBarStyle" + android:sharedElementName="android:action_bar" android:gravity="top"> <com.android.internal.widget.ActionBarView android:id="@+id/action_bar" diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml index e3364d1..d02cc8b 100644 --- a/core/res/res/layout/screen_custom_title.xml +++ b/core/res/res/layout/screen_custom_title.xml @@ -31,6 +31,7 @@ This is a custom layout for a screen. <FrameLayout android:id="@android:id/title_container" android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" + android:sharedElementName="android:title" style="?android:attr/windowTitleBackgroundStyle"> </FrameLayout> <FrameLayout android:id="@android:id/content" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index bfd7565..1c5be42 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4760,14 +4760,6 @@ <attr name="fromScene" format="reference" /> <!-- The destination scene in this scene change. --> <attr name="toScene" format="reference" /> - <!-- The name of the originating scene in this scene change. - Apps should treat this name as an API in the same sense - that an Intent action or extra key is. --> - <attr name="fromSceneName" format="string" /> - <!-- The name of the destination scene in this scene change. - Apps should treat this name as an API in the same sense - that an Intent action or extra key is. --> - <attr name="toSceneName" format="string" /> </declare-styleable> <!-- ========================== --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index c814d25..3106daa 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2103,8 +2103,6 @@ <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="attr" name="castsShadow" /> diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index c73d90a..02a2680 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -29,6 +29,8 @@ import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionInflater; import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.ArrayMap; import android.view.ViewConfiguration; import com.android.internal.R; @@ -105,6 +107,9 @@ import android.widget.TextView; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; /** * Android-specific Window. @@ -120,6 +125,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private static final long MAX_TRANSITION_START_WAIT = 500; private static final long MAX_TRANSITION_FINISH_WAIT = 1000; + private static final String KEY_SCREEN_X = "shared_element:screenX"; + private static final String KEY_SCREEN_Y = "shared_element:screenY"; + private static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; + private static final String KEY_WIDTH = "shared_element:width"; + private static final String KEY_HEIGHT = "shared_element:height"; + private static final String KEY_NAME = "shared_element:name"; + /** * Simple callback used by the context menu and its submenus. The options * menu submenus do not use this (their behavior is more complex). @@ -239,7 +251,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ActivityOptions mActivityOptions; private SceneTransitionListener mSceneTransitionListener; - private boolean mFadeEarly = true; + private boolean mTriggerEarly = true; + private Map<String, String> mSharedElementsMap; static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( @@ -2562,6 +2575,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return super.fitSystemWindows(insets); } + @Override + public boolean isTransitionGroup() { + return false; + } + private void updateStatusGuard(Rect insets) { boolean showStatusGuard = false; // Show the status guard when the non-overlay contextual action bar is showing @@ -3988,81 +4006,217 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void setEarlyBackgroundTransition(boolean fadeEarly) { - mFadeEarly = fadeEarly; + public void setTriggerEarlyEnterTransition(boolean triggerEarly) { + mTriggerEarly = triggerEarly; } @Override - public void startExitTransition(ActivityOptions activityOptions) { - Transition transition = mTransitionManager.getNamedTransition(getContentScene(), "null"); + public void mapTransitionTargets(Map<String, String> sharedElementNames) { + mSharedElementsMap = sharedElementNames; + } + + @Override + public Bundle startExitTransition(ActivityOptions activityOptions) { + if (mContentScene == null) { + return null; + } + Transition transition = mTransitionManager.getExitTransition(mContentScene); if (transition == null) { - transition = TransitionManager.getDefaultTransition().clone(); + return null; + } + + ArrayMap<String, View> sharedElements = findSharedElements(activityOptions); + + // Find exiting Views and shared elements + final ArrayList<View> transitioningViews = new ArrayList<View>(); + mDecor.captureTransitioningViews(transitioningViews); + transitioningViews.removeAll(sharedElements.values()); + + Transition exitTransition = cloneAndSetTransitionTargets(transition, + transitioningViews, true); + Transition sharedElementTransition = cloneAndSetTransitionTargets(transition, + transitioningViews, false); + + // transitionSet is the total exit transition, including hero animation. + TransitionSet transitionSet = new TransitionSet(); + transitionSet.addTransition(exitTransition); + transitionSet.addTransition(sharedElementTransition); + + updateExitActivityOptions(activityOptions, sharedElements, + sharedElementTransition, exitTransition); + + // Start exiting the Views that need to exit + TransitionManager.beginDelayedTransition(mDecor, transitionSet); + setViewVisibility(transitioningViews, View.INVISIBLE); + + return activityOptions.toBundle(); + } + + private ArrayMap<String, View> findSharedElements(ActivityOptions activityOptions) { + ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); + mDecor.findSharedElements(sharedElements); + ArrayList<String> localNames = activityOptions.getLocalElementNames(); + sharedElements.keySet().retainAll(localNames); + + ArrayList<String> targetNames = activityOptions.getSharedElementNames(); + for (int i = 0; i < localNames.size(); i++) { + String localName = localNames.get(i); + View sharedElement = sharedElements.remove(localName); + String targetName = targetNames.get(i); + sharedElements.put(targetName, sharedElement); } - activityOptions.setExitTransition(transition, new ActivityOptions.SharedElementSource() { + return sharedElements; + } + + private void updateExitActivityOptions(ActivityOptions activityOptions, + final Map<String, View> sharedElements, Transition sharedElementTransition, + Transition exitTransition) { + + // Schedule capturing of the shared element state + final Bundle sharedElementArgs = new Bundle(); + captureTerminalSharedElementState(sharedElements, sharedElementArgs); + + ActivityOptions.SharedElementSource sharedElementSource + = new ActivityOptions.SharedElementSource() { @Override - public int getTextureId() { - // TODO: move shared elements to a layer and return the texture id - recurseHideExitingSharedElements(mContentParent); - return 0; + public Bundle getSharedElementExitState() { + return sharedElementArgs; } - }); - 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; + @Override + public void acceptedSharedElements(ArrayList<String> sharedElementNames) { + if (sharedElementNames.size() == sharedElements.size()) { + return; // They were all accepted + } + Transition transition = mTransitionManager.getExitTransition(mContentScene).clone(); + TransitionManager.beginDelayedTransition(mDecor, transition); + for (String name: sharedElements.keySet()) { + if (!sharedElementNames.contains(name)) { + sharedElements.get(name).setVisibility(View.INVISIBLE); + } + } + sharedElements.keySet().retainAll(sharedElementNames); } - if (child instanceof ViewGroup && !((ViewGroup)child).isTransitionGroup()) { - recurseExitNonSharedElements((ViewGroup) child); - } else { - child.setVisibility(View.INVISIBLE); + + @Override + public void hideSharedElements() { + if (sharedElements != null) { + setViewVisibility(sharedElements.values(), View.INVISIBLE); + } } - } + }; + + activityOptions.updateSceneTransitionAnimation( + exitTransition, sharedElementTransition, sharedElementSource); } - 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; + private void captureTerminalSharedElementState(final Map<String, View> sharedElements, + final Bundle sharedElementArgs) { + mDecor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mDecor.getViewTreeObserver().removeOnPreDrawListener(this); + int[] tempLoc = new int[2]; + for (String name : sharedElements.keySet()) { + View sharedElement = sharedElements.get(name); + captureSharedElementState(sharedElement, name, sharedElementArgs, tempLoc); + } + return true; } - if (child.getSharedElementName() != null) { - sharedElements.add(child); - child.setVisibility(View.INVISIBLE); - } else if (child instanceof ViewGroup && !((ViewGroup)child).isTransitionGroup()) { - recurseHideViews((ViewGroup) child, nonSharedElements, sharedElements); + }); + } + + private static Transition cloneAndSetTransitionTargets(Transition transition, + List<View> views, boolean add) { + transition = transition.clone(); + if (!transition.getTargetIds().isEmpty() || !transition.getTargets().isEmpty()) { + TransitionSet set = new TransitionSet(); + set.addTransition(transition); + transition = set; + } + for (View view: views) { + if (add) { + transition.addTarget(view); } else { - nonSharedElements.add(child); - child.setVisibility(View.INVISIBLE); + transition.excludeTarget(view, true); } } + return transition; } - 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); - } + private static void setViewVisibility(Collection<View> views, int visibility) { + for (View view : views) { + view.setVisibility(visibility); } } /** + * 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 tempLoc A temporary int[2] for capturing the current location of views. + */ + private static void setSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = transitionArgs.getBundle(name); + if (sharedElementBundle == null) { + return; + } + + int x = sharedElementBundle.getInt(KEY_SCREEN_X); + view.getLocationOnScreen(tempLoc); + int offsetX = x - tempLoc[0]; + view.offsetLeftAndRight(offsetX); + + int width = sharedElementBundle.getInt(KEY_WIDTH); + view.setRight(view.getLeft() + width); + + int y = sharedElementBundle.getInt(KEY_SCREEN_Y); + int offsetY = y - tempLoc[1]; + view.offsetTopAndBottom(offsetY); + + int height = sharedElementBundle.getInt(KEY_HEIGHT); + view.setBottom(view.getTop() + height); + + float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); + view.setTranslationZ(z); + } + + /** + * Captures placement information for Views with a shared element name for + * Activity Transitions. + * @param view The View to capture the placement information for. + * @param name The shared element name in the target Activity to apply the placement + * information for. + * @param transitionArgs Bundle to store shared element placement information. + * @param tempLoc A temporary int[2] for capturing the current location of views. + * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[]) + */ + private static void captureSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = new Bundle(); + view.getLocationOnScreen(tempLoc); + float scaleX = view.getScaleX(); + sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); + int width = Math.round(view.getWidth() * scaleX); + sharedElementBundle.putInt(KEY_WIDTH, width); + + float scaleY = view.getScaleY(); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); + int height= Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_HEIGHT, height); + + sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); + + sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); + + transitionArgs.putBundle(name, sharedElementBundle); + } + + /** * 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 @@ -4080,46 +4234,57 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private boolean mAllDone; private Handler mHandler = new Handler(); private boolean mEnterTransitionStarted; - private ArrayList<View> mSharedElements = new ArrayList<View>(); + private ArrayMap<String, View> mSharedElementTargets = new ArrayMap<String, View>(); + private ArrayList<View> mEnteringViews = new ArrayList<View>(); public EnterScene() { mSceneTransitionListener.nullPendingTransition(); Drawable background = getDecorView().getBackground(); if (background != null) { - setBackgroundDrawable(null); background.setAlpha(0); - setBackgroundDrawable(background); + mDecor.drawableChanged(); } mSceneTransitionListener.convertToTranslucent(); } @Override public boolean onPreDraw() { - ViewTreeObserver observer = mContentParent.getViewTreeObserver(); + ViewTreeObserver observer = mDecor.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(); + mDecor.captureTransitioningViews(mEnteringViews); + ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames(); + if (sharedElementNames != null) { + mDecor.findSharedElements(mSharedElementTargets); + if (mSharedElementsMap != null) { + for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) { + View sharedElement = mSharedElementTargets.remove(entry.getValue()); + if (sharedElement != null) { + mSharedElementTargets.put(entry.getKey(), sharedElement); + } + } + } + mSharedElementTargets.keySet().retainAll(sharedElementNames); + mEnteringViews.removeAll(mSharedElementTargets.values()); } - TransitionManager.beginDelayedTransition(mContentParent, transition); - for (View hidden : enteringViews) { - hidden.setVisibility(View.VISIBLE); + + setViewVisibility(mEnteringViews, View.INVISIBLE); + setViewVisibility(mSharedElementTargets.values(), View.INVISIBLE); + if (mTriggerEarly) { + beginEnterScene(); } observer.addOnPreDrawListener(this); } else { mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT); - mActivityOptions.dispatchSceneTransitionStarted(this); + mActivityOptions.dispatchSceneTransitionStarted(this, + new ArrayList<String>(mSharedElementTargets.keySet())); } return true; } public void start() { - ViewTreeObserver observer = mContentParent.getViewTreeObserver(); + ViewTreeObserver observer = mDecor.getViewTreeObserver(); observer.addOnPreDrawListener(this); } @@ -4129,25 +4294,43 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void sharedElementTransitionComplete() { + public void sharedElementTransitionComplete(Bundle transitionArgs) { 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 (!mSharedElementTargets.isEmpty()) { + Transition transition = getTransitionManager().getEnterTransition( + mContentScene); + if (transition == null) { + transition = TransitionManager.getDefaultTransition(); + } + transition = transition.clone(); + if (transitionArgs == null) { + TransitionManager.beginDelayedTransition(mDecor, transition); + setViewVisibility(mSharedElementTargets.values(), View.VISIBLE); + } else { + int[] tempLoc = new int[2]; + for (Map.Entry<String, View> entry: mSharedElementTargets.entrySet()) { + setSharedElementState(entry.getValue(), entry.getKey(), transitionArgs, + tempLoc); + } + setViewVisibility(mSharedElementTargets.values(), View.VISIBLE); + mSceneTransitionListener.sharedElementStart(transition); + mDecor.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mDecor.getViewTreeObserver().removeOnPreDrawListener(this); + mSceneTransitionListener.sharedElementEnd(); + mActivityOptions.dispatchSharedElementsReady(); + return true; + } + }); + TransitionManager.beginDelayedTransition(mDecor, transition); } - }); - if (mFadeEarly) { + } + if (mTriggerEarly) { fadeInBackground(); } } @@ -4170,9 +4353,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return; } mAllDone = true; - sharedElementTransitionComplete(); + sharedElementTransitionComplete(null); mHandler.removeCallbacks(this); - if (!mFadeEarly) { + if (!mTriggerEarly) { + beginEnterScene(); fadeInBackground(); } } @@ -4193,5 +4377,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void onAnimationRepeat(Animator animation) { } + + private void beginEnterScene() { + Transition transition = getTransitionManager().getEnterTransition(mContentScene); + if (transition == null) { + transition = TransitionManager.getDefaultTransition().clone(); + } + TransitionManager.beginDelayedTransition(mDecor, transition); + setViewVisibility(mEnteringViews, View.VISIBLE); + } } } |