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