Split Activity Transitions out of PhoneWindow.
Bug 13622834
Made it possible to use shared elements without making
Views invisible.
Change-Id: I1e85c6bc19e634a9af225ad7f0309b4f003ea462
diff --git a/api/current.txt b/api/current.txt
index 7b09a85..894dabf8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1284,6 +1284,8 @@
field public static final int windowActionBar = 16843469; // 0x10102cd
field public static final int windowActionBarOverlay = 16843492; // 0x10102e4
field public static final int windowActionModeOverlay = 16843485; // 0x10102dd
+ field public static final int windowAllowEnterTransitionOverlap = 16843848; // 0x1010448
+ field public static final int windowAllowExitTransitionOverlap = 16843847; // 0x1010447
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
@@ -1293,7 +1295,9 @@
field public static final int windowDisablePreview = 16843298; // 0x1010222
field public static final int windowEnableSplitTouch = 16843543; // 0x1010317
field public static final int windowEnterAnimation = 16842932; // 0x10100b4
+ field public static final int windowEnterTransition = 16843843; // 0x1010443
field public static final int windowExitAnimation = 16842933; // 0x10100b5
+ field public static final int windowExitTransition = 16843844; // 0x1010444
field public static final int windowFrame = 16842837; // 0x1010055
field public static final int windowFullscreen = 16843277; // 0x101020d
field public static final int windowHideAnimation = 16842935; // 0x10100b7
@@ -1304,6 +1308,8 @@
field public static final int windowNoDisplay = 16843294; // 0x101021e
field public static final int windowNoTitle = 16842838; // 0x1010056
field public static final int windowOverscan = 16843727; // 0x10103cf
+ field public static final int windowSharedElementEnterTransition = 16843845; // 0x1010445
+ field public static final int windowSharedElementExitTransition = 16843846; // 0x1010446
field public static final int windowShowAnimation = 16842934; // 0x10100b6
field public static final int windowShowWallpaper = 16843410; // 0x1010292
field public static final int windowSoftInputMode = 16843307; // 0x101022b
@@ -2454,6 +2460,11 @@
field public static final int l_resource_pad9 = 16974328; // 0x10301f8
}
+ public static final class R.transition {
+ ctor public R.transition();
+ field public static final int no_transition = 17760256; // 0x10f0000
+ }
+
public static final class R.xml {
ctor public R.xml();
}
@@ -3209,8 +3220,6 @@
method public void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method public void onBackPressed();
- method public void onCaptureSharedElementEnd();
- method public void onCaptureSharedElementStart(android.transition.Transition);
method protected void onChildTitleChanged(android.app.Activity, java.lang.CharSequence);
method public void onConfigurationChanged(android.content.res.Configuration);
method public void onContentChanged();
@@ -3283,6 +3292,7 @@
method public final void runOnUiThread(java.lang.Runnable);
method public void setActionBar(android.widget.Toolbar);
method public void setActivityLabelAndIcon(java.lang.CharSequence, android.graphics.Bitmap);
+ 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);
@@ -3500,13 +3510,27 @@
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.View, java.lang.String);
- method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.util.Pair<android.view.View, java.lang.String>...);
+ method public static android.app.ActivityOptions 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 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 void onCaptureSharedElementEnd();
+ method public void onCaptureSharedElementStart();
+ 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);
@@ -27750,7 +27774,6 @@
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
- method public void setExitTransition(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Scene, android.transition.Transition);
method public void transitionTo(android.transition.Scene);
@@ -30787,6 +30810,8 @@
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
method public android.view.View findViewById(int);
+ method public boolean getAllowEnterTransitionOverlap();
+ method public boolean getAllowExitTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
method public final android.view.Window.Callback getCallback();
method public final android.view.Window getContainer();
@@ -30794,10 +30819,14 @@
method public final android.content.Context getContext();
method public abstract android.view.View getCurrentFocus();
method public abstract android.view.View getDecorView();
+ method public android.transition.Transition getEnterTransition();
+ method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
method protected final int getForcedWindowFlags();
method public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
+ method public android.transition.Transition getSharedElementEnterTransition();
+ method public android.transition.Transition getSharedElementExitTransition();
method public android.transition.TransitionManager getTransitionManager();
method public abstract int getVolumeControlStream();
method public android.view.WindowManager getWindowManager();
@@ -30811,7 +30840,6 @@
method public abstract boolean isFloating();
method public abstract boolean isShortcutKey(int, android.view.KeyEvent);
method public final void makeActive();
- method public void mapTransitionTargets(java.util.Map<java.lang.String, java.lang.String>);
method protected abstract void onActive();
method public abstract void onConfigurationChanged(android.content.res.Configuration);
method public abstract void openPanel(int, android.view.KeyEvent);
@@ -30822,8 +30850,8 @@
method public boolean requestFeature(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
- method public void setAllowOverlappingEnterTransition(boolean);
- method public void setAllowOverlappingExitTransition(boolean);
+ method public void setAllowEnterTransitionOverlap(boolean);
+ method public void setAllowExitTransitionOverlap(boolean);
method public void setAttributes(android.view.WindowManager.LayoutParams);
method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
method public void setBackgroundDrawableResource(int);
@@ -30836,6 +30864,8 @@
method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
method protected void setDefaultWindowFormat(int);
method public void setDimAmount(float);
+ method public void setEnterTransition(android.transition.Transition);
+ method public void setExitTransition(android.transition.Transition);
method public abstract void setFeatureDrawable(int, android.graphics.drawable.Drawable);
method public abstract void setFeatureDrawableAlpha(int, int);
method public abstract void setFeatureDrawableResource(int, int);
@@ -30848,6 +30878,8 @@
method public void setLayout(int, int);
method public void setLocalFocus(boolean, boolean);
method public void setLogo(int);
+ method public void setSharedElementEnterTransition(android.transition.Transition);
+ method public void setSharedElementExitTransition(android.transition.Transition);
method public void setSoftInputMode(int);
method public abstract void setTitle(java.lang.CharSequence);
method public abstract deprecated void setTitleColor(int);
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 9818c33..04f62e3 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -949,10 +949,6 @@
}
/** @hide */
- public void captureSharedElements(Map<String, View> sharedElements) {
- }
-
- /** @hide */
public ActionMode startActionMode(ActionMode.Callback callback) {
return null;
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b18eb98..a5a06e3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.transition.Scene;
-import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
@@ -773,6 +772,8 @@
private Thread mUiThread;
final Handler mHandler = new Handler();
+ private ActivityOptions mCalledActivityOptions;
+ private EnterTransitionCoordinator mEnterTransitionCoordinator;
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -1026,6 +1027,9 @@
mTitleReady = true;
onTitleChanged(getTitle(), getTitleColor());
}
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.readyToEnter();
+ }
mCalled = true;
}
@@ -1106,6 +1110,7 @@
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
+ mCalledActivityOptions = null;
mCalled = true;
}
@@ -1398,8 +1403,9 @@
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
- if (mWindow != null) {
- mWindow.restoreViewVisibilityAfterTransitionToCallee();
+ if (mCalledActivityOptions != null) {
+ mCalledActivityOptions.dispatchActivityStopped();
+ mCalledActivityOptions = null;
}
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
@@ -3484,7 +3490,7 @@
public void startActivityForResult(Intent intent, int requestCode) {
Bundle options = null;
if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- options = ActivityOptions.makeSceneTransitionAnimation().toBundle();
+ options = ActivityOptions.makeSceneTransitionAnimation(mWindow, null).toBundle();
}
startActivityForResult(intent, requestCode, options);
}
@@ -3526,14 +3532,8 @@
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (options != null) {
ActivityOptions activityOptions = new ActivityOptions(options);
- if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- if (mActionBar != null) {
- ArrayMap<String, View> sharedElementMap = new ArrayMap<String, View>();
- mActionBar.captureSharedElements(sharedElementMap);
- activityOptions.addSharedElements(sharedElementMap);
- }
- options = mWindow.startExitTransitionToCallee(options);
- }
+ activityOptions.dispatchStartExit();
+ mCalledActivityOptions = activityOptions;
}
if (mParent == null) {
Instrumentation.ActivityResult ar =
@@ -4391,16 +4391,15 @@
* 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.view.Window#setTriggerEarlySceneTransition(boolean, boolean)
- * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String)
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ * android.app.ActivityOptions.ActivityTransitionListener)
*/
public void finishWithTransition() {
- mWindow.startExitTransitionToCaller(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- });
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.startExit();
+ } else {
+ finish();
+ }
}
/**
@@ -5346,6 +5345,21 @@
}
}
+ /**
+ * 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}.
+ *
+ * @param listener Used to listen to events in the entering transition.
+ */
+ public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.setActivityTransitionListener(listener);
+ }
+ }
+
// ------------------ Internal API ------------------
final void setParent(Activity parent) {
@@ -5413,34 +5427,12 @@
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
- Window.SceneTransitionListener sceneTransitionListener
- = new Window.SceneTransitionListener() {
- @Override
- public void nullPendingTransition() {
- overridePendingTransition(0, 0);
+ if (options != null) {
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this);
}
-
- @Override
- public void convertFromTranslucent() {
- Activity.this.convertFromTranslucent();
- }
-
- @Override
- public void convertToTranslucent() {
- Activity.this.convertToTranslucent(null);
- }
-
- @Override
- public void sharedElementStart(Transition transition) {
- Activity.this.onCaptureSharedElementStart(transition);
- }
-
- @Override
- public void sharedElementEnd() {
- Activity.this.onCaptureSharedElementEnd();
- }
- };
- mWindow.setTransitionOptions(options, sceneTransitionListener);
+ }
}
/** @hide */
@@ -5628,26 +5620,6 @@
}
/**
- * Called when setting up Activity Scene transitions when the start state for shared
- * elements has been captured. Override this method to modify the start position of shared
- * elements for the entry Transition.
- *
- * @param transition The <code>Transition</code> being used to change
- * bounds of shared elements in the source Activity to
- * the bounds defined by the entering Scene.
- */
- public void onCaptureSharedElementStart(Transition transition) {
- }
-
- /**
- * Called when setting up Activity Scene transitions when the final state for
- * shared elements state has been captured. Override this method to modify the destination
- * position of shared elements for the entry Transition.
- */
- public void onCaptureSharedElementEnd() {
- }
-
- /**
* @hide
*/
public final boolean isResumed() {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4384580..85464c47 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -20,16 +20,16 @@
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.transition.Transition;
-import android.util.Log;
+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,23 +108,6 @@
private static final String KEY_TRANSITION_COMPLETE_LISTENER
= "android:transitionCompleteListener";
- /**
- * For Activity transitions, the called Activity's listener to receive calls
- * when transitions complete.
- */
- private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener";
-
- /**
- * The names of shared elements that are transitioned to the started Activity.
- * This is also the name of shared elements that the started Activity accepted.
- */
- private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names";
-
- /**
- * The shared elements names of the views in the calling Activity.
- */
- private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names";
-
/** @hide */
public static final int ANIM_NONE = 0;
/** @hide */
@@ -138,11 +121,6 @@
/** @hide */
public static final int ANIM_SCENE_TRANSITION = 5;
- private static final int MSG_SET_LISTENER = 100;
- private static final int MSG_HIDE_SHARED_ELEMENTS = 101;
- private static final int MSG_PREPARE_RESTORE = 102;
- private static final int MSG_RESTORE = 103;
-
private String mPackageName;
private int mAnimationType = ANIM_NONE;
private int mCustomEnterResId;
@@ -153,9 +131,7 @@
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
- private ResultReceiver mTransitionCompleteListener;
- private ArrayList<String> mSharedElementNames;
- private ArrayList<String> mLocalElementNames;
+ private ResultReceiver mExitReceiver;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -231,12 +207,6 @@
void onAnimationStarted();
}
- /** @hide */
- public interface ActivityTransitionTarget {
- void sharedElementTransitionComplete(Bundle transitionArgs);
- void exitTransitionComplete();
- }
-
/**
* Create an ActivityOptions specifying an animation where the new
* activity is scaled from a small originating area of the screen to
@@ -357,49 +327,53 @@
/**
* Create an ActivityOptions to transition between Activities using cross-Activity scene
* animations. This method carries the position of one shared element to the started Activity.
+ * The position of <code>sharedElement</code> will be used as the epicenter for the
+ * exit Transition. The position of the 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 sharedElement The View to transition to the started Activity. sharedElement must
* have a non-null sharedElementName.
* @param sharedElementName The shared element name as used in the target Activity. This may
* be null if it has the same name as sharedElement.
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
+ * @see android.transition.Transition#setEpicenterCallback(
+ * android.transition.Transition.EpicenterCallback)
*/
- public static ActivityOptions makeSceneTransitionAnimation(View sharedElement,
- String sharedElementName) {
- return makeSceneTransitionAnimation(
- new Pair<View, String>(sharedElement, sharedElementName));
+ public static ActivityOptions makeSceneTransitionAnimation(Window window,
+ View sharedElement, String sharedElementName) {
+ return makeSceneTransitionAnimation(window,
+ new SharedElementMappingListener(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.
+ * Activity. The position of the first element in the value returned from
+ * {@link android.app.ActivityOptions.ActivityTransitionListener#getSharedElementsMapping()}
+ * 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 sharedElements The View to transition to the started Activity along with the
- * shared element name as used in the started Activity. The view
- * must have a non-null sharedElementName.
+ * @param window The window containing shared elements.
+ * @param listener The listener to use to monitor activity transition events.
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
+ * @see android.transition.Transition#setEpicenterCallback(
+ * android.transition.Transition.EpicenterCallback)
*/
- public static ActivityOptions makeSceneTransitionAnimation(
- Pair<View, String>... sharedElements) {
+ public static ActivityOptions makeSceneTransitionAnimation(Window window,
+ ActivityTransitionListener listener) {
ActivityOptions opts = new ActivityOptions();
opts.mAnimationType = ANIM_SCENE_TRANSITION;
- opts.mSharedElementNames = new ArrayList<String>();
- opts.mLocalElementNames = new ArrayList<String>();
-
- if (sharedElements != null) {
- for (Pair<View, String> sharedElement : sharedElements) {
- opts.addSharedElement(sharedElement.first, sharedElement.second);
- }
- }
+ ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener);
+ opts.mExitReceiver = exit;
return opts;
}
@@ -435,9 +409,7 @@
break;
case ANIM_SCENE_TRANSITION:
- mTransitionCompleteListener = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
- mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
- mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES);
+ mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
break;
}
}
@@ -493,50 +465,16 @@
}
/** @hide */
- public ArrayList<String> getSharedElementNames() { return mSharedElementNames; }
-
- /** @hide */
- public ArrayList<String> getLocalElementNames() { return mLocalElementNames; }
-
- /** @hide */
- public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target,
- ArrayList<String> sharedElementNames) {
- if (mTransitionCompleteListener != null) {
- IRemoteCallback callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- if (data == null) {
- target.exitTransitionComplete();
- } else {
- target.sharedElementTransitionComplete(data);
- }
- }
- };
- Bundle bundle = new Bundle();
- bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder());
- bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames);
- mTransitionCompleteListener.send(MSG_SET_LISTENER, bundle);
+ public void dispatchActivityStopped() {
+ if (mExitReceiver != null) {
+ mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
}
}
/** @hide */
- public void dispatchSharedElementsReady() {
- if (mTransitionCompleteListener != null) {
- mTransitionCompleteListener.send(MSG_HIDE_SHARED_ELEMENTS, null);
- }
- }
-
- /** @hide */
- public void dispatchPrepareRestore() {
- if (mTransitionCompleteListener != null) {
- mTransitionCompleteListener.send(MSG_PREPARE_RESTORE, null);
- }
- }
-
- /** @hide */
- public void dispatchRestore(Bundle sharedElementsArgs) {
- if (mTransitionCompleteListener != null) {
- mTransitionCompleteListener.send(MSG_RESTORE, sharedElementsArgs);
+ public void dispatchStartExit() {
+ if (mExitReceiver != null) {
+ mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
}
}
@@ -557,6 +495,15 @@
}
}
+ /** @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
@@ -566,8 +513,7 @@
if (otherOptions.mPackageName != null) {
mPackageName = otherOptions.mPackageName;
}
- mSharedElementNames = null;
- mLocalElementNames = null;
+ mExitReceiver = null;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
mAnimationType = otherOptions.mAnimationType;
@@ -581,7 +527,6 @@
}
}
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
- mTransitionCompleteListener = null;
break;
case ANIM_SCALE_UP:
mAnimationType = otherOptions.mAnimationType;
@@ -596,7 +541,6 @@
}
}
mAnimationStartedListener = null;
- mTransitionCompleteListener = null;
break;
case ANIM_THUMBNAIL_SCALE_UP:
case ANIM_THUMBNAIL_SCALE_DOWN:
@@ -611,15 +555,12 @@
}
}
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
- mTransitionCompleteListener = null;
break;
case ANIM_SCENE_TRANSITION:
mAnimationType = otherOptions.mAnimationType;
- mTransitionCompleteListener = otherOptions.mTransitionCompleteListener;
+ mExitReceiver = otherOptions.mExitReceiver;
mThumbnail = null;
mAnimationStartedListener = null;
- mSharedElementNames = otherOptions.mSharedElementNames;
- mLocalElementNames = otherOptions.mLocalElementNames;
break;
}
}
@@ -663,11 +604,9 @@
break;
case ANIM_SCENE_TRANSITION:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
- if (mTransitionCompleteListener != null) {
- b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener);
+ if (mExitReceiver != null) {
+ b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver);
}
- b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames);
- b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames);
break;
}
return b;
@@ -687,130 +626,92 @@
return null;
}
- /** @hide */
- public void addSharedElements(Map<String, View> sharedElements) {
- for (Map.Entry<String, View> entry : sharedElements.entrySet()) {
- addSharedElement(entry.getValue(), entry.getKey());
- }
+ /**
+ * 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.
+ */
+ public void onCaptureSharedElementStart() {}
+
+ /**
+ * Called when the end state for shared elements is captured on enter.
+ */
+ public void onCaptureSharedElementEnd() {}
+
+ /**
+ * 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; }
}
- /** @hide */
- public void updateSceneTransitionAnimation(Transition exitTransition,
- Transition sharedElementTransition, SharedElementSource sharedElementSource) {
- mTransitionCompleteListener = new ExitTransitionListener(exitTransition,
- sharedElementTransition, sharedElementSource);
- }
+ private static class SharedElementMappingListener extends ActivityTransitionListener {
+ Pair<View, String>[] mSharedElementsMapping = new Pair[1];
- private void addSharedElement(View view, String name) {
- String sharedElementName = view.getSharedElementName();
- if (name == null) {
- name = sharedElementName;
- }
- mSharedElementNames.add(name);
- mLocalElementNames.add(sharedElementName);
- }
-
- /** @hide */
- public interface SharedElementSource {
- Bundle getSharedElementExitState();
- void acceptedSharedElements(ArrayList<String> sharedElementNames);
- void hideSharedElements();
- void restore(Bundle sharedElementState);
- void prepareForRestore();
- }
-
- private static class ExitTransitionListener extends ResultReceiver
- implements Transition.TransitionListener {
- private boolean mSharedElementNotified;
- private IRemoteCallback mTransitionCompleteCallback;
- private boolean mExitComplete;
- private boolean mSharedElementComplete;
- private SharedElementSource mSharedElementSource;
-
- public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition,
- SharedElementSource sharedElementSource) {
- super(null);
- mSharedElementSource = sharedElementSource;
- exitTransition.addListener(this);
- sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition transition) {
- mSharedElementComplete = true;
- notifySharedElement();
- transition.removeListener(this);
- }
- });
+ public SharedElementMappingListener(View view, String name) {
+ mSharedElementsMapping[0] = Pair.create(view, name);
}
@Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- switch (resultCode) {
- case MSG_SET_LISTENER:
- IBinder listener = resultData.getBinder(KEY_TRANSITION_TARGET_LISTENER);
- mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(listener);
- ArrayList<String> sharedElementNames
- = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
- mSharedElementSource.acceptedSharedElements(sharedElementNames);
- notifySharedElement();
- notifyExit();
- break;
- case MSG_HIDE_SHARED_ELEMENTS:
- mSharedElementSource.hideSharedElements();
- break;
- case MSG_PREPARE_RESTORE:
- mSharedElementSource.prepareForRestore();
- break;
- case MSG_RESTORE:
- mSharedElementSource.restore(resultData);
- break;
- }
- }
-
- @Override
- public void onTransitionStart(Transition transition) {
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mExitComplete = true;
- notifyExit();
- transition.removeListener(this);
- }
-
- @Override
- public void onTransitionCancel(Transition transition) {
- onTransitionEnd(transition);
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- }
-
- private void notifySharedElement() {
- if (!mSharedElementNotified && mSharedElementComplete
- && mTransitionCompleteCallback != null) {
- mSharedElementNotified = true;
- try {
- Bundle sharedElementState = mSharedElementSource.getSharedElementExitState();
- mTransitionCompleteCallback.sendResult(sharedElementState);
- } catch (RemoteException e) {
- Log.w(TAG, "Couldn't notify that the transition ended", e);
- }
- }
- }
-
- private void notifyExit() {
- if (mExitComplete && mTransitionCompleteCallback != null) {
- try {
- mTransitionCompleteCallback.sendResult(null);
- } catch (RemoteException e) {
- Log.w(TAG, "Couldn't notify that the transition ended", e);
- }
- }
+ public Pair<View, String>[] getSharedElementsMapping() {
+ return mSharedElementsMapping;
}
}
}
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
new file mode 100644
index 0000000..d8a356f
--- /dev/null
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -0,0 +1,736 @@
+/*
+ * 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.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.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
+ * that manage activity transitions and the communications coordinating them between
+ * Activities. The ExitTransitionCoordinator is created in the
+ * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
+ * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
+ * attached.
+ *
+ * Typical startActivity goes like this:
+ * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
+ * 2) Activity#startActivity called and that calls startExit() through
+ * ActivityOptions#dispatchStartExit
+ * - Exit transition starts by setting transitioning Views to INVISIBLE
+ * 3) Launched Activity starts, creating an EnterTransitionCoordinator.
+ * - The Window is made translucent
+ * - The Window background alpha is set to 0
+ * - The transitioning views are made INVISIBLE
+ * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
+ * 4) The shared element transition completes.
+ * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
+ * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the 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 and the background alpha is animated to opaque.
+ * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
+ * - The shared elements are made INVISIBLE
+ * 7) The exit transition completes in the calling Activity.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
+ * 8) 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 and the background is animated to opaque.
+ * 9) The background opacity animation completes.
+ * - The window is made opaque
+ * 10) The calling Activity gets an onStop() call
+ * - 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
+ * - 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
+ * - 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
+ * - The background alpha is animated to 0
+ * 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
+ * - 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
+ * - The shared elements are made INVISIBLE
+ * 8) The exit transition completes in the finishing Activity.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator.
+ * - finish() is called on the exiting Activity
+ * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator.
+ * - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ * by setting entering views to VISIBLE.
+ */
+abstract class ActivityTransitionCoordinator extends ResultReceiver {
+ private static final String TAG = "ActivityTransitionCoordinator";
+
+ /**
+ * The names of shared elements that are transitioned to the started Activity.
+ * This is also the name of shared elements that the started Activity accepted.
+ */
+ public static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names";
+
+ public static final String KEY_SHARED_ELEMENT_STATE = "android:shared_element_state";
+
+ /**
+ * For Activity transitions, the called Activity's listener to receive calls
+ * when transitions complete.
+ */
+ static final String KEY_TRANSITION_RESULTS_RECEIVER = "android:transitionTargetListener";
+
+ 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";
+
+ /**
+ * Sent by the exiting coordinator (either EnterTransitionCoordinator
+ * or ExitTransitionCoordinator) after the shared elements have
+ * become stationary (shared element transition completes). This tells
+ * the remote coordinator to take control of the shared elements and
+ * that animations may begin. The remote Activity won't start entering
+ * 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;
+
+ /**
+ * Sent by the entering coordinator to tell the exiting coordinator
+ * to hide its shared elements after it has started its shared
+ * element transition. This is temporary until the
+ * interlock of shared elements is figured out.
+ */
+ 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;
+
+ /**
+ * Sent by the exiting coordinator (either EnterTransitionCoordinator
+ * or ExitTransitionCoordinator) after the shared elements have
+ * become stationary (shared element transition completes). This tells
+ * the remote coordinator to take control of the shared elements and
+ * that animations may begin. The remote Activity won't start entering
+ * 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;
+
+ /**
+ * Sent by the exiting coordinator (either
+ * EnterTransitionCoordinator or ExitTransitionCoordinator) after
+ * the exiting Views have finished leaving the scene. This will
+ * be ignored if allowOverlappingTransitions() is true on the
+ * remote coordinator. If it is false, it will trigger the enter
+ * transition to start.
+ */
+ public static final int MSG_EXIT_TRANSITION_COMPLETE = 105;
+
+ /**
+ * Sent by Activity#startActivity to begin the exit transition.
+ */
+ public static final int MSG_START_EXIT_TRANSITION = 106;
+
+ private Window mWindow;
+ private ArrayList<View> mSharedElements = new ArrayList<View>();
+ private ArrayList<String> mTargetSharedNames = new ArrayList<String>();
+ private ActivityOptions.ActivityTransitionListener mListener =
+ new ActivityOptions.ActivityTransitionListener();
+ private ArrayList<View> mEnteringViews;
+ private ResultReceiver mRemoteResultReceiver;
+ private boolean mNotifiedSharedElementTransitionComplete;
+ private boolean mNotifiedExitTransitionComplete;
+
+ private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
+
+ private Transition.TransitionListener mSharedElementListener =
+ new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ onSharedElementTransitionEnd();
+ }
+ };
+
+ private Transition.TransitionListener mExitListener =
+ new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ onExitTransitionEnd();
+ }
+ };
+
+ public ActivityTransitionCoordinator(Window window)
+ {
+ super(new Handler());
+ mWindow = window;
+ }
+
+ // -------------------- ResultsReceiver Overrides ----------------------
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ switch (resultCode) {
+ case MSG_SET_LISTENER:
+ ResultReceiver resultReceiver
+ = resultData.getParcelable(KEY_TRANSITION_RESULTS_RECEIVER);
+ setRemoteResultReceiver(resultReceiver);
+ onSetResultReceiver();
+ break;
+ case MSG_HIDE_SHARED_ELEMENTS:
+ onHideSharedElements();
+ break;
+ case MSG_PREPARE_RESTORE:
+ onPrepareRestore();
+ break;
+ case MSG_EXIT_TRANSITION_COMPLETE:
+ 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);
+ setSharedElementState(state);
+ if (getViewsTransition() != null) {
+ setViewVisibility(mEnteringViews, View.INVISIBLE);
+ }
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(),
+ true);
+ 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>();
+ setViewVisibility(mEnteringViews, View.VISIBLE);
+ getDecor().findSharedElements(map);
+ 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;
+ }
+
+ protected Window getWindow() {
+ return mWindow;
+ }
+
+ protected ViewGroup getDecor() {
+ 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);
+ }
+ mListener.onStartExitTransition(getSharedElementNames(), getSharedElements());
+ }
+
+ protected void clearConnections() {
+ mRemoteResultReceiver = null;
+ }
+
+ // public API
+
+ public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
+ if (listener == null) {
+ mListener = new ActivityOptions.ActivityTransitionListener();
+ } else {
+ mListener = listener;
+ }
+ }
+
+ // private methods
+
+ private Transition configureTransition(Transition transition) {
+ if (transition != null) {
+ transition = transition.clone();
+ transition.setEpicenterCallback(mEpicenterCallback);
+ }
+ return transition;
+ }
+
+ private void reconcileSharedElements(ArrayList<String> sharedElementNames) {
+ Rect epicenter = null;
+ for (int i = mTargetSharedNames.size() - 1; i >= 0; i--) {
+ if (!sharedElementNames.contains(mTargetSharedNames.get(i))) {
+ mTargetSharedNames.remove(i);
+ mSharedElements.remove(i);
+ }
+ }
+ if (!mSharedElements.isEmpty()) {
+ epicenter = calcEpicenter(mSharedElements.get(0));
+ }
+ mEpicenterCallback.setEpicenter(epicenter);
+ }
+
+ private void setSharedElementState(Bundle sharedElementState) {
+ if (sharedElementState != null) {
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElements.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mTargetSharedNames.get(i);
+ setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
+ }
+ }
+ mListener.onCaptureSharedElementStart();
+ getDecor().getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mListener.onCaptureSharedElementEnd();
+ return true;
+ }
+ }
+ );
+ }
+
+ /**
+ * Sets the captured values from a previous
+ * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
+ * @param view The View to apply placement changes to.
+ * @param name The shared element name given from the source Activity.
+ * @param transitionArgs A <code>Bundle</code> containing all placementinformation for named
+ * shared elements in the scene.
+ * @param tempLoc A temporary int[2] for capturing the current location of views.
+ */
+ private static void setSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] tempLoc) {
+ Bundle sharedElementBundle = transitionArgs.getBundle(name);
+ if (sharedElementBundle == null) {
+ return;
+ }
+
+ 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);
+
+ ViewGroup parent = (ViewGroup) view.getParent();
+ parent.getLocationOnScreen(tempLoc);
+ int left = x - tempLoc[0];
+ int top = y - tempLoc[1];
+ int right = left + width;
+ int bottom = top + height;
+ view.layout(left, top, right, bottom);
+
+ view.requestLayout();
+ }
+
+ /**
+ * Captures placement information for Views with a shared element name for
+ * Activity Transitions.
+ * @param view The View to capture the placement information for.
+ * @param name The shared element name in the target Activity to apply the placement
+ * information for.
+ * @param transitionArgs Bundle to store shared element placement information.
+ * @param tempLoc A temporary int[2] for capturing the current location of views.
+ * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[])
+ */
+ private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] tempLoc) {
+ Bundle sharedElementBundle = new Bundle();
+ view.getLocationOnScreen(tempLoc);
+ float scaleX = view.getScaleX();
+ sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
+ int width = Math.round(view.getWidth() * scaleX);
+ sharedElementBundle.putInt(KEY_WIDTH, width);
+
+ float scaleY = view.getScaleY();
+ sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
+ int height= Math.round(view.getHeight() * scaleY);
+ sharedElementBundle.putInt(KEY_HEIGHT, height);
+
+ sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
+
+ sharedElementBundle.putString(KEY_NAME, view.getSharedElementName());
+
+ transitionArgs.putBundle(name, sharedElementBundle);
+ }
+
+ 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);
+ }
+
+ public static void setViewVisibility(Collection<View> views, int visibility) {
+ if (views != null) {
+ for (View view : views) {
+ view.setVisibility(visibility);
+ }
+ }
+ }
+
+ private static Transition addTransitionTargets(Transition transition, Collection<View> views) {
+ if (transition == null || views == null || views.isEmpty()) {
+ return null;
+ }
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition.clone());
+ if (views != null) {
+ for (View view: views) {
+ set.addTarget(view);
+ }
+ }
+ 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;
+ }
+ if (transition != null) {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ if (transitionSharedElement && !mSharedElements.isEmpty()) {
+ mSharedElements.get(0).invalidate();
+ } else if (transitionViews && !transitioningViews.isEmpty()) {
+ transitioningViews.get(0).invalidate();
+ }
+ }
+ return transition;
+ }
+
+ private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
+ private Rect mEpicenter;
+
+ public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
+
+ @Override
+ public Rect getEpicenter(Transition transition) {
+ return mEpicenter;
+ }
+ }
+}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
new file mode 100644
index 0000000..aa097e0
--- /dev/null
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -0,0 +1,292 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * 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.
+ */
+class EnterTransitionCoordinator extends ActivityTransitionCoordinator
+ implements ViewTreeObserver.OnPreDrawListener {
+ 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;
+
+ /**
+ * 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 Activity mActivity;
+
+ /**
+ * True if the Window was opaque at the start and we should make it opaque again after
+ * enter transitions have completed.
+ */
+ private boolean mWasOpaque;
+
+ /**
+ * During exit, is the background alpha == 0?
+ */
+ private boolean mBackgroundFadedOut;
+
+ /**
+ * During exit, has the shared element transition completed?
+ */
+ private boolean mSharedElementTransitionComplete;
+
+ /**
+ * Has the exit started? We don't want to accidentally exit multiple times. e.g. when
+ * back is hit twice during the exit animation.
+ */
+ private boolean mExitTransitionStarted;
+
+ /**
+ * Has the exit transition ended?
+ */
+ private boolean mExitTransitionComplete;
+
+ /**
+ * We only want to make the Window transparent and set the background alpha once. After that,
+ * the Activity won't want the same enter transition.
+ */
+ private boolean mMadeReady;
+
+ /**
+ * True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that
+ * enter and exit transitions should be active.
+ */
+ private boolean mSupportsTransition;
+
+ /**
+ * Background alpha animations may complete prior to receiving the callback for
+ * onTranslucentConversionComplete. If so, we need to immediately call to make the Window
+ * opaque.
+ */
+ private boolean mMakeOpaque;
+
+ public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) {
+ super(activity.getWindow());
+ 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();
+ }
+ }
+ });
+ Drawable background = getDecor().getBackground();
+ if (background != null) {
+ window.setBackgroundDrawable(null);
+ background.setAlpha(0);
+ window.setBackgroundDrawable(background);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onRemoteSceneExitComplete() {
+ super.onRemoteSceneExitComplete();
+ }
+
+ @Override
+ protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
+ mEnteringSharedElementNames = new ArrayList<String>();
+ mEnteringSharedElementNames.addAll(sharedElementNames);
+ super.onTakeSharedElements(sharedElementNames, state);
+ }
+
+ @Override
+ protected void sharedElementTransitionComplete(Bundle bundle) {
+ notifySharedElementTransitionComplete(bundle);
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
+ setEnteringViews(readyEnteringViews());
+ notifySetListener();
+ onPrepareRestore();
+ return false;
+ }
+
+ @Override
+ public void startExit() {
+ if (!mExitTransitionStarted) {
+ mExitTransitionStarted = true;
+ startExitTransition(mEnteringSharedElementNames);
+ }
+ }
+
+ @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) {
+ 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();
+ }
+ });
+ } 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();
+ clearConnections();
+ }
+
+ @Override
+ protected void onSharedElementTransitionEnd() {
+ mSharedElementTransitionComplete = true;
+ if (mBackgroundFadedOut) {
+ super.onSharedElementTransitionEnd();
+ }
+ }
+
+ @Override
+ protected boolean allowOverlappingTransitions() {
+ return getWindow().getAllowEnterTransitionOverlap();
+ }
+
+ private void exitAfterSharedElementTransition() {
+ if (mSharedElementTransitionComplete && mExitTransitionComplete) {
+ mActivity.finish();
+ if (mSupportsTransition) {
+ mActivity.overridePendingTransition(0, 0);
+ }
+ notifyExitTransitionComplete();
+ }
+ }
+}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
new file mode 100644
index 0000000..d920787
--- /dev/null
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -0,0 +1,171 @@
+/*
+ * 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.transition.Transition;
+import android.util.Pair;
+import android.view.View;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
+ * to govern the exit of the Scene and the shared elements when calling an Activity as well as
+ * the reentry of the Scene when coming back from the called Activity.
+ */
+class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
+ private static final String TAG = "ExitTransitionCoordinator";
+
+ /**
+ * 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;
+
+ /**
+ * Has the shared element transition completed?
+ */
+ private boolean mSharedElementsComplete;
+
+ public ExitTransitionCoordinator(Window window,
+ ActivityOptions.ActivityTransitionListener listener) {
+ super(window);
+ setActivityTransitionListener(listener);
+ }
+
+ @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();
+ }
+ }
+ }
+
+ @Override
+ public void startExit() {
+ if (!mExitStarted) {
+ mExitStarted = true;
+ setSharedElements();
+ startExitTransition(getSharedElementNames());
+ }
+ }
+
+ @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/transition/Transition.java b/core/java/android/transition/Transition.java
index b7ae31e..c67d6fa 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -1585,7 +1585,7 @@
* hierarchy underneath it.
*/
void capturePropagationValues(TransitionValues transitionValues) {
- if (mPropagation != null) {
+ if (mPropagation != null && !transitionValues.values.isEmpty()) {
String[] propertyNames = mPropagation.getPropagationProperties();
if (propertyNames == null) {
return;
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index f675c6a..14ecc15 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -309,15 +309,11 @@
if (transitionId >= 0) {
Transition transition = inflateTransition(transitionId);
if (transition != null) {
+ if (toScene == null) {
+ throw new RuntimeException("No toScene for transition ID " + transitionId);
+ }
if (fromScene == null) {
- if (toScene == null) {
- throw new RuntimeException("No matching fromScene or toScene " +
- "for transition ID " + transitionId);
- } else {
- transitionManager.setTransition(toScene, transition);
- }
- } else if (toScene == null) {
- transitionManager.setExitTransition(fromScene, transition);
+ transitionManager.setTransition(toScene, transition);
} else {
transitionManager.setTransition(fromScene, toScene, transition);
}
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 1614d34..ce3cc2f 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -70,7 +70,6 @@
private static final String[] EMPTY_STRINGS = new String[0];
ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
- ArrayMap<Scene, Transition> mExitSceneTransitions = new ArrayMap<Scene, Transition>();
ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
@@ -119,21 +118,6 @@
}
/**
- * Sets a specific transition to occur when the given scene is exited. This
- * has the lowest priority -- if a Scene-to-Scene transition or
- * Scene enter transition can be applied, it will.
- *
- * @param scene The scene which, when exited, will cause the given
- * transition to run.
- * @param transition The transition that will play when the given scene is
- * exited. A value of null will result in the default behavior of
- * using the default transition instead.
- */
- public void setExitTransition(Scene scene, Transition transition) {
- mExitSceneTransitions.put(scene, transition);
- }
-
- /**
* Sets a specific transition to occur when the given pair of scenes is
* exited/entered.
*
@@ -181,9 +165,6 @@
}
}
transition = mSceneTransitions.get(scene);
- if (transition == null && sceneRoot != null) {
- transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot));
- }
return (transition != null) ? transition : sDefaultTransition;
}
@@ -239,34 +220,6 @@
}
/**
- * Retrieve the transition to a target defined scene if one has been
- * associated with this TransitionManager.
- *
- * @param toScene Target scene that this transition will move to
- * @return Transition corresponding to the given toScene or null
- * if no association exists in this TransitionManager
- *
- * @see #setTransition(Scene, Transition)
- * @hide
- */
- public Transition getEnterTransition(Scene toScene) {
- return mSceneTransitions.get(toScene);
- }
-
- /**
- * Retrieve the transition from a defined scene to a target named scene if one has been
- * associated with this TransitionManager.
- *
- * @param fromScene Scene that this transition starts from
- * @return Transition corresponding to the given fromScene or null
- * if no association exists in this TransitionManager
- * @hide
- */
- public Transition getExitTransition(Scene fromScene) {
- return mExitSceneTransitions.get(fromScene);
- }
-
- /**
* This private utility class is used to listen for both OnPreDraw and
* OnAttachStateChange events. OnPreDraw events are the main ones we care
* about since that's what triggers the transition to take place.
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 7783b6f..526803a 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -109,14 +109,14 @@
final VisibilityInfo visInfo = new VisibilityInfo();
visInfo.visibilityChange = false;
visInfo.fadeIn = false;
- if (startValues != null) {
+ if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) {
visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
} else {
visInfo.startVisibility = -1;
visInfo.startParent = null;
}
- if (endValues != null) {
+ if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) {
visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
} else {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d2c6302..a64bdc7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2305,11 +2305,13 @@
/**
* Changes whether or not this ViewGroup should be treated as a single entity during
- * ActivityTransitions.
+ * Activity Transitions.
* @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit
* in Activity transitions. If false, the ViewGroup won't transition,
* only its children. If true, the entire ViewGroup will transition
* together.
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ * android.app.ActivityOptions.ActivityTransitionListener)
*/
public void setTransitionGroup(boolean isTransitionGroup) {
mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7bd1f56..9c44bd1 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -32,8 +32,6 @@
import android.transition.TransitionManager;
import android.view.accessibility.AccessibilityEvent;
-import java.util.Map;
-
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
@@ -1385,86 +1383,132 @@
}
/**
- * Set options that can affect the transition behavior within this window.
- * @param options Options to set or null for none
- * @hide
+ * Sets the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected.
+ * @param transition The Transition to use to move Views into the initial Scene.
*/
- public void setTransitionOptions(Bundle options, SceneTransitionListener listener) {
- }
+ public void setEnterTransition(Transition transition) {}
/**
- * A callback for Window transitions to be told when the shared element is ready to be shown
- * and start the transition to its target location.
- * @hide
+ * Sets the Transition that will be used to move Views out of the scene when starting a
+ * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @param transition The Transition to use to move Views out of the scene when calling a
+ * new Activity.
*/
- public interface SceneTransitionListener {
- void nullPendingTransition();
- void convertFromTranslucent();
- void convertToTranslucent();
- void sharedElementStart(Transition transition);
- void sharedElementEnd();
- }
+ public void setExitTransition(Transition transition) {}
/**
- * Controls how the Activity's start Scene is faded in and when the enter scene
- * is triggered to start.
- * <p>When allow is true, the enter Scene will begin as soon as possible
- * and the background will fade in when all shared elements are ready to begin
- * transitioning. If allow is false, the Activity enter Scene and
- * background fade will be triggered when the calling Activity's exit transition
- * completes.</p>
- * @param allow Set to true to have the Activity enter scene transition in
- * as early as possible or set to false to wait for the calling
- * Activity to exit first. The default value is true.
+ * Returns the transition used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @return the Transition to use to move Views into the initial Scene.
*/
- public void setAllowOverlappingEnterTransition(boolean allow) {
- }
+ public Transition getEnterTransition() { return null; }
/**
- * Controls how the Activity's Scene fades out and when the calling Activity's
- * enter scene is triggered when finishing to return to a calling Activity.
- * <p>When allow is true, the Scene will fade out quickly
- * and inform the calling Activity to transition in when the fade completes.
- * When allow is false, the calling Activity will transition in after
- * the Activity's Scene has exited.
- * </p>
- * @param allow Set to true to have the Activity fade out as soon as possible
- * and transition in the calling Activity. The default value is
- * true.
+ * Returns the Transition that will be used to move Views out of the scene when starting a
+ * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @return the Transition to use to move Views out of the scene when calling a
+ * new Activity.
*/
- public void setAllowOverlappingExitTransition(boolean allow) {
- }
+ public Transition getExitTransition() { return null; }
/**
- * Start the exit transition.
- * @hide
+ * Sets the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.MoveImage} and {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @param transition The Transition to use for shared elements transferred into the content
+ * Scene.
*/
- public Bundle startExitTransitionToCallee(Bundle options) {
- return null;
- }
+ public void setSharedElementEnterTransition(Transition transition) {}
/**
- * Starts the transition back to the calling Activity.
- * onTransitionEnd will be called on the current thread if there is no exit transition.
- * @hide
+ * Returns the Transition that will be used for shared elements transferred into the content
+ * Scene. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @return Transition to use for sharend elements transferred into the content Scene.
*/
- public void startExitTransitionToCaller(Runnable onTransitionEnd) {
- onTransitionEnd.run();
- }
-
- /** @hide */
- public void restoreViewVisibilityAfterTransitionToCallee() {
- }
+ public Transition getSharedElementEnterTransition() { return null; }
/**
- * On entering Activity Scene transitions, shared element names may be mapped from a
- * source Activity's specified name to a unique shared element name in the View hierarchy.
- * Under most circumstances, mapping is not necessary - a single View will have the
- * shared element name given by the calling Activity. However, if there are several similar
- * Views (e.g. in a ListView), the correct shared element must be mapped.
- * @param sharedElementNames A mapping from the calling Activity's assigned shared element
- * name to a unique shared element name in the View hierarchy.
+ * Sets the Transition that will be used for shared elements after starting a new Activity
+ * before the shared elements are transferred to the called Activity. If the shared elements
+ * must animate during the exit transition, this Transition should be used. Upon completion,
+ * the shared elements may be transferred to the started Activity.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @param transition The Transition to use for shared elements in the launching Window
+ * prior to transferring to the launched Activity's Window.
*/
- public void mapTransitionTargets(Map<String, String> sharedElementNames) {
- }
+ public void setSharedElementExitTransition(Transition transition) {}
+
+ /**
+ * Returns the Transition to use for shared elements in the launching Window prior
+ * to transferring to the launched Activity's Window.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @return the Transition to use for shared elements in the launching Window prior
+ * to transferring to the launched Activity's Window.
+ */
+ public Transition getSharedElementExitTransition() { return null; }
+
+ /**
+ * Controls how the transition set in
+ * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the calling Activity. When true, the transition will start as soon as possible.
+ * When false, the transition will wait until the remote exiting transition completes before
+ * starting.
+ * @param allow true to start the enter transition when possible or false to
+ * wait until the exiting transition completes.
+ */
+ public void setAllowEnterTransitionOverlap(boolean allow) {}
+
+ /**
+ * Returns how the transition set in
+ * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the calling Activity. When true, the transition will start as soon as possible.
+ * When false, the transition will wait until the remote exiting transition completes before
+ * starting.
+ * @return true when the enter transition should start as soon as possible or false to
+ * when it should wait until the exiting transition completes.
+ */
+ public boolean getAllowEnterTransitionOverlap() { return true; }
+
+ /**
+ * Controls how the transition set in
+ * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the called Activity when reentering after if finishes. When true,
+ * the transition will start as soon as possible. When false, the transition will wait
+ * until the called Activity's exiting transition completes before starting.
+ * @param allow true to start the transition when possible or false to wait until the
+ * called Activity's exiting transition completes.
+ */
+ public void setAllowExitTransitionOverlap(boolean allow) {}
+
+ /**
+ * Returns how the transition set in
+ * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the called Activity when reentering after if finishes. When true,
+ * the transition will start as soon as possible. When false, the transition will wait
+ * until the called Activity's exiting transition completes before starting.
+ * @return true when the transition should start when possible or false when it should wait
+ * until the called Activity's exiting transition completes.
+ */
+ public boolean getAllowExitTransitionOverlap() { return true; }
}
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 34156e5..afb6f7c 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -444,9 +444,4 @@
mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
}
}
-
- @Override
- public void captureSharedElements(Map<String, View> sharedElements) {
- mToolbar.findSharedElements(sharedElements);
- }
}
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index fb93ddd..131f828 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -362,10 +362,6 @@
setSubtitle(mContext.getString(resId));
}
- public void captureSharedElements(Map<String, View> sharedElements) {
- mContainerView.findSharedElements(sharedElements);
- }
-
public void setSelectedNavigationItem(int position) {
switch (mActionView.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
diff --git a/core/res/res/transition/no_transition.xml b/core/res/res/transition/no_transition.xml
new file mode 100644
index 0000000..5679738
--- /dev/null
+++ b/core/res/res/transition/no_transition.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<transitionSet/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f364bd0..53fed98 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -462,6 +462,41 @@
transitions between different window content. -->
<attr name="windowContentTransitionManager" format="reference" />
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used to move Views into the initial Window's content Scene. Corresponds to
+ {@link android.view.Window#setEnterTransition(android.transition.Transition)}. -->
+ <attr name="windowEnterTransition" format="reference"/>
+
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used to move Views out of the Window's content Scene when launching a new Activity.
+ Corresponds to
+ {@link android.view.Window#setExitTransition(android.transition.Transition)}. -->
+ <attr name="windowExitTransition" format="reference"/>
+
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used to move shared elements transferred into the Window's initial content Scene.
+ Corresponds to {@link android.view.Window#setSharedElementEnterTransition(
+ android.transition.Transition)}. -->
+ <attr name="windowSharedElementEnterTransition" format="reference"/>
+
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used when starting a new Activity to move shared elements prior to transferring
+ to the called Activity.
+ Corresponds to {@link android.view.Window#setSharedElementExitTransition(
+ android.transition.Transition)}. -->
+ <attr name="windowSharedElementExitTransition" format="reference"/>
+
+ <!-- Flag indicating whether this Window's transition should overlap with
+ the exiting transition of the calling Activity. Corresponds to
+ {@link android.view.Window#setAllowEnterTransitionOverlap(boolean)}. -->
+ <attr name="windowAllowEnterTransitionOverlap" format="boolean"/>
+
+ <!-- Flag indicating whether this Window's transition should overlap with
+ the exiting transition of the called Activity when the called Activity
+ finishes. Corresponds to
+ {@link android.view.Window#setAllowExitTransitionOverlap(boolean)}. -->
+ <attr name="windowAllowExitTransitionOverlap" format="boolean"/>
+
<!-- ============ -->
<!-- Alert Dialog styles -->
<!-- ============ -->
@@ -1690,6 +1725,41 @@
or a fraction of the screen size in that dimension. -->
<attr name="windowFixedHeightMajor" format="dimension|fraction" />
<attr name="windowOutsetBottom" format="dimension" />
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used to move Views into the initial Window's content Scene. Corresponds to
+ {@link android.view.Window#setEnterTransition(android.transition.Transition)}. -->
+ <attr name="windowEnterTransition"/>
+
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used to move Views out of the Window's content Scene when launching a new Activity.
+ Corresponds to
+ {@link android.view.Window#setExitTransition(android.transition.Transition)}. -->
+ <attr name="windowExitTransition"/>
+
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used to move shared elements transferred into the Window's initial content Scene.
+ Corresponds to {@link android.view.Window#setSharedElementEnterTransition(
+ android.transition.Transition)}. -->
+ <attr name="windowSharedElementEnterTransition"/>
+
+ <!-- Reference to a TransitionManager XML resource defining the desired Transition
+ used when starting a new Activity to move shared elements prior to transferring
+ to the called Activity.
+ Corresponds to {@link android.view.Window#setSharedElementExitTransition(
+ android.transition.Transition)}. -->
+ <attr name="windowSharedElementExitTransition"/>
+
+
+ <!-- Flag indicating whether this Window's transition should overlap with
+ the exiting transition of the calling Activity. Corresponds to
+ {@link android.view.Window#setAllowEnterTransitionOverlap(boolean)}. -->
+ <attr name="windowAllowEnterTransitionOverlap"/>
+
+ <!-- Flag indicating whether this Window's transition should overlap with
+ the exiting transition of the called Activity when the called Activity
+ finishes. Corresponds to
+ {@link android.view.Window#setAllowExitTransitionOverlap(boolean)}. -->
+ <attr name="windowAllowExitTransitionOverlap"/>
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -2383,8 +2453,9 @@
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.View, String)}
- for more information. -->
+ {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ android.view.View, String)} for more information. Corresponds to
+ {@link android.view.ViewGroup#setTransitionGroup(boolean)}.-->
<attr name="transitionGroup" format="boolean" />
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 32b674e..9712c03 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2389,4 +2389,11 @@
<public type="interpolator" name="linear_out_slow_in" />
<!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
<public type="interpolator" name="fast_out_linear_in" />
+ <public type="attr" name="windowEnterTransition" />
+ <public type="attr" name="windowExitTransition" />
+ <public type="attr" name="windowSharedElementEnterTransition" />
+ <public type="attr" name="windowSharedElementExitTransition" />
+ <public type="attr" name="windowAllowExitTransitionOverlap" />
+ <public type="attr" name="windowAllowEnterTransitionOverlap" />
+ <public type="transition" name="no_transition" id="0x010f0000"/>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 563272b..b0f19ec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1839,5 +1839,6 @@
<java-symbol type="drawable" name="ic_lock_bugreport" />
<java-symbol type="id" name="icon_frame" />
<java-symbol type="style" name="Animation.VolumePanel" />
+ <java-symbol type="transition" name="no_transition" />
</resources>
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 79ed866..2cf94d0 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -22,19 +22,6 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.*;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.app.ActivityOptions;
-import android.os.Looper;
-import android.transition.Fade;
-import android.transition.Scene;
-import android.transition.Transition;
-import android.transition.TransitionInflater;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.util.ArrayMap;
-import android.view.ViewConfiguration;
-
import com.android.internal.R;
import com.android.internal.view.RootViewSurfaceTaker;
import com.android.internal.view.StandaloneActionMode;
@@ -51,6 +38,9 @@
import com.android.internal.widget.ActionBarView;
import com.android.internal.widget.SwipeDismissLayout;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -65,11 +55,22 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.transition.ChangeBounds;
+import android.transition.Explode;
+import android.transition.Fade;
+import android.transition.MoveImage;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -90,6 +91,7 @@
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewManager;
import android.view.ViewParent;
@@ -124,16 +126,6 @@
private final static String TAG = "PhoneWindow";
private final static boolean SWEEP_OPEN_MENU = false;
- private static final long MAX_TRANSITION_START_WAIT = 500;
- private static final long MAX_TRANSITION_FINISH_WAIT = 1000;
-
- private static final String KEY_SCREEN_X = "shared_element:screenX";
- private static final String KEY_SCREEN_Y = "shared_element:screenY";
- private static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
- private static final String KEY_WIDTH = "shared_element:width";
- private static final String KEY_HEIGHT = "shared_element:height";
- private static final String KEY_NAME = "shared_element:name";
-
/**
* Simple callback used by the context menu and its submenus. The options
* menu submenus do not use this (their behavior is more complex).
@@ -252,12 +244,12 @@
}
};
- private ActivityOptions mActivityOptions;
- private SceneTransitionListener mSceneTransitionListener;
- private boolean mAllowEnterOverlap = true;
- private boolean mAllowExitOverlap = true;
- private Map<String, String> mSharedElementsMap;
- private ArrayList<View> mTransitioningViews;
+ private Transition mEnterTransition;
+ private Transition mExitTransition;
+ private Transition mSharedElementEnterTransition;
+ private Transition mSharedElementExitTransition;
+ private Boolean mAllowExitTransitionOverlap;
+ private Boolean mAllowEnterTransitionOverlap;
static class WindowManagerHolder {
static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -410,9 +402,6 @@
private void transitionTo(Scene scene) {
if (mContentScene == null) {
scene.enter();
- if (mActivityOptions != null) {
- new EnterScene().start();
- }
} else {
mTransitionManager.transitionTo(scene);
}
@@ -3307,20 +3296,55 @@
// Only inflate or create a new TransitionManager if the caller hasn't
// already set a custom one.
- if (hasFeature(FEATURE_CONTENT_TRANSITIONS) && mTransitionManager == null) {
- final int transitionRes = getWindowStyle().getResourceId(
- com.android.internal.R.styleable.Window_windowContentTransitionManager, 0);
- if (transitionRes != 0) {
- final TransitionInflater inflater = TransitionInflater.from(getContext());
- mTransitionManager = inflater.inflateTransitionManager(transitionRes,
- mContentParent);
- } else {
- mTransitionManager = new TransitionManager();
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ if (mTransitionManager == null) {
+ final int transitionRes = getWindowStyle().getResourceId(
+ com.android.internal.R.styleable.Window_windowContentTransitionManager,
+ 0);
+ if (transitionRes != 0) {
+ final TransitionInflater inflater = TransitionInflater.from(getContext());
+ mTransitionManager = inflater.inflateTransitionManager(transitionRes,
+ mContentParent);
+ } else {
+ mTransitionManager = new TransitionManager();
+ }
+ }
+
+ mEnterTransition = getTransition(mEnterTransition,
+ com.android.internal.R.styleable.Window_windowEnterTransition);
+ mExitTransition = getTransition(mExitTransition,
+ com.android.internal.R.styleable.Window_windowExitTransition);
+ mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition,
+ com.android.internal.R.styleable.Window_windowSharedElementEnterTransition);
+ mSharedElementExitTransition = getTransition(mSharedElementExitTransition,
+ com.android.internal.R.styleable.Window_windowSharedElementExitTransition);
+ if (mAllowEnterTransitionOverlap == null) {
+ mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
+ com.android.internal.R.styleable.
+ Window_windowAllowEnterTransitionOverlap, true);
+ }
+ if (mAllowExitTransitionOverlap == null) {
+ mAllowExitTransitionOverlap = getWindowStyle().getBoolean(
+ com.android.internal.R.styleable.
+ Window_windowAllowExitTransitionOverlap, true);
}
}
}
}
+ private Transition getTransition(Transition currentValue, int id) {
+ if (currentValue != null) {
+ return currentValue;
+ }
+ int transitionId = getWindowStyle().getResourceId(id, -1);
+ Transition transition = null;
+ if (transitionId != -1 && transitionId != com.android.internal.R.transition.no_transition) {
+ TransitionInflater inflater = TransitionInflater.from(getContext());
+ transition = inflater.inflateTransition(transitionId);
+ }
+ return transition;
+ }
+
private Drawable loadImageURI(Uri uri) {
try {
final Context context = getContext();
@@ -3649,6 +3673,66 @@
com.android.internal.R.styleable.Window_windowIsTranslucent, 0), false);
}
+ @Override
+ public void setEnterTransition(Transition enterTransition) {
+ mEnterTransition = enterTransition;
+ }
+
+ @Override
+ public void setExitTransition(Transition exitTransition) {
+ mExitTransition = exitTransition;
+ }
+
+ @Override
+ public void setSharedElementEnterTransition(Transition sharedElementEnterTransition) {
+ mSharedElementEnterTransition = sharedElementEnterTransition;
+ }
+
+ @Override
+ public void setSharedElementExitTransition(Transition sharedElementExitTransition) {
+ mSharedElementExitTransition = sharedElementExitTransition;
+ }
+
+ @Override
+ public Transition getEnterTransition() {
+ return mEnterTransition;
+ }
+
+ @Override
+ public Transition getExitTransition() {
+ return mExitTransition;
+ }
+
+ @Override
+ public Transition getSharedElementEnterTransition() {
+ return mSharedElementEnterTransition;
+ }
+
+ @Override
+ public Transition getSharedElementExitTransition() {
+ return mSharedElementExitTransition;
+ }
+
+ @Override
+ public void setAllowEnterTransitionOverlap(boolean allow) {
+ mAllowEnterTransitionOverlap = allow;
+ }
+
+ @Override
+ public boolean getAllowEnterTransitionOverlap() {
+ return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+ }
+
+ @Override
+ public void setAllowExitTransitionOverlap(boolean allowExitTransitionOverlap) {
+ mAllowExitTransitionOverlap = allowExitTransitionOverlap;
+ }
+
+ @Override
+ public boolean getAllowExitTransitionOverlap() {
+ return (mAllowExitTransitionOverlap == null) ? true : mAllowExitTransitionOverlap;
+ }
+
private static final class DrawableFeatureState {
DrawableFeatureState(int _featureId) {
featureId = _featureId;
@@ -4082,670 +4166,4 @@
void sendCloseSystemWindows(String reason) {
PhoneWindowManager.sendCloseSystemWindows(getContext(), reason);
}
-
- @Override
- public void setTransitionOptions(Bundle options, SceneTransitionListener listener) {
- mSceneTransitionListener = listener;
- ActivityOptions activityOptions = null;
- if (options != null) {
- activityOptions = new ActivityOptions(options);
- if (activityOptions.getAnimationType() != ActivityOptions.ANIM_SCENE_TRANSITION) {
- activityOptions = null;
- }
- }
- mActivityOptions = activityOptions;
- }
-
- @Override
- public void setAllowOverlappingEnterTransition(boolean allow) {
- mAllowEnterOverlap = allow;
- }
-
- @Override
- public void setAllowOverlappingExitTransition(boolean allow) {
- mAllowExitOverlap = allow;
- }
-
- @Override
- public void mapTransitionTargets(Map<String, String> sharedElementNames) {
- mSharedElementsMap = sharedElementNames;
- }
-
- @Override
- public void restoreViewVisibilityAfterTransitionToCallee() {
- if (mTransitioningViews != null) {
- setViewVisibility(mTransitioningViews, View.VISIBLE);
- }
- }
-
- @Override
- public void startExitTransitionToCaller(final Runnable onTransitionEnd) {
- Transition transition;
- if (mContentScene == null || mTransitionManager == null || mActivityOptions == null
- || (transition = mTransitionManager.getEnterTransition(mContentScene)) == null) {
- onTransitionEnd.run();
- return;
- }
- if (mAllowExitOverlap) {
- TransitionSet transitionSet = new TransitionSet();
- transitionSet.addTransition(transition);
- Fade fade = new Fade();
- transitionSet.addTransition(fade);
- transition = transitionSet;
- }
-
- final ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
- mapSharedElements(sharedElements);
- final Bundle sharedElementArgs = new Bundle();
- captureTerminalSharedElementState(sharedElements, sharedElementArgs);
-
- final ArrayList<View> transitioningViews = new ArrayList<View>();
- mDecor.captureTransitioningViews(transitioningViews);
- transitioningViews.removeAll(sharedElements.values());
-
- mSceneTransitionListener.convertToTranslucent();
- transition = transition.clone();
- Rect epicenter = calcEpicenter(sharedElements, mActivityOptions.getSharedElementNames());
- transition.setEpicenterCallback(new FixedEpicenterCallback(epicenter));
- ExitSceneBack exitScene =
- new ExitSceneBack(onTransitionEnd, sharedElementArgs, sharedElements.values());
- exitScene.start(transition);
- mTransitionManager.beginDelayedTransition(mDecor, transition);
- setViewVisibility(transitioningViews, View.INVISIBLE);
- }
-
- @Override
- public Bundle startExitTransitionToCallee(Bundle options) {
- if (mContentScene == null) {
- return null;
- }
- Transition transition = mTransitionManager.getExitTransition(mContentScene);
- if (transition == null) {
- return null;
- }
-
- ActivityOptions activityOptions = new ActivityOptions(options);
- ArrayMap<String, View> sharedElements = findSharedElements(activityOptions);
-
- // Find exiting Views and shared elements
- ArrayList<View> transitioningViews = captureTransitioningViews(sharedElements.values());
-
- Transition exitTransition = addTransitionTargets(transition,
- transitioningViews, true);
- Transition sharedElementTransition = addTransitionTargets(transition,
- transitioningViews, false);
-
- // transitionSet is the total exit transition, including hero animation.
- TransitionSet transitionSet = new TransitionSet();
- transitionSet.addTransition(exitTransition);
- transitionSet.addTransition(sharedElementTransition);
-
- Rect epicenter = calcEpicenter(sharedElements, activityOptions.getSharedElementNames());
- FixedEpicenterCallback epicenterCallback = new FixedEpicenterCallback(epicenter);
- transitionSet.setEpicenterCallback(epicenterCallback);
-
- updateExitActivityOptions(activityOptions, sharedElements,
- sharedElementTransition, transitioningViews, exitTransition, epicenterCallback);
-
- // Start exiting the Views that need to exit
- TransitionManager.beginDelayedTransition(mDecor, transitionSet);
- setViewVisibility(transitioningViews, View.INVISIBLE);
-
- return activityOptions.toBundle();
- }
-
- private ArrayList<View> captureTransitioningViews(Collection<View> sharedElements) {
- mTransitioningViews = new ArrayList<View>();
- mDecor.captureTransitioningViews(mTransitioningViews);
- ArrayList<View> transitioningViews = (ArrayList<View>) mTransitioningViews.clone();
- transitioningViews.removeAll(sharedElements);
- return transitioningViews;
- }
-
- private ArrayMap<String, View> findSharedElements(ActivityOptions activityOptions) {
- ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
- mDecor.findSharedElements(sharedElements);
- ArrayList<String> localNames = activityOptions.getLocalElementNames();
- sharedElements.keySet().retainAll(localNames);
-
- ArrayList<String> targetNames = activityOptions.getSharedElementNames();
- for (int i = 0; i < localNames.size(); i++) {
- String localName = localNames.get(i);
- View sharedElement = sharedElements.remove(localName);
- String targetName = targetNames.get(i);
- sharedElements.put(targetName, sharedElement);
- }
- return sharedElements;
- }
-
- private static void runOnUiThread(Handler handler, Runnable runnable) {
- if (handler.getLooper() != Looper.myLooper()) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- private void updateExitActivityOptions(ActivityOptions activityOptions,
- final Map<String, View> sharedElements, Transition sharedElementTransition,
- final ArrayList<View> transitioningViews, Transition exitTransition,
- final Transition.EpicenterCallback epicenterCallback) {
-
- // Schedule capturing of the shared element state
- final Bundle sharedElementArgs = new Bundle();
- captureTerminalSharedElementState(sharedElements, sharedElementArgs);
-
- ActivityOptions.SharedElementSource sharedElementSource
- = new ActivityOptions.SharedElementSource() {
- private Handler mHandler = new Handler();
-
- @Override
- public Bundle getSharedElementExitState() {
- return sharedElementArgs;
- }
-
- @Override
- public void acceptedSharedElements(final ArrayList<String> sharedElementNames) {
- if (sharedElementNames.size() == sharedElements.size()) {
- return; // They were all accepted
- }
- runOnUiThread(mHandler, new Runnable() {
- @Override
- public void run() {
- Transition transition = mTransitionManager.getExitTransition(mContentScene);
- transition = transition.clone();
- transition.setEpicenterCallback(epicenterCallback);
- TransitionManager.beginDelayedTransition(mDecor, transition);
- for (String name : sharedElements.keySet()) {
- if (!sharedElementNames.contains(name)) {
- sharedElements.get(name).setVisibility(View.INVISIBLE);
- }
- }
- sharedElements.keySet().retainAll(sharedElementNames);
- }
- });
- }
-
- @Override
- public void hideSharedElements() {
- if (sharedElements != null) {
- runOnUiThread(mHandler, new Runnable() {
- @Override
- public void run() {
- setViewVisibility(sharedElements.values(), View.INVISIBLE);
- }
- });
- }
- }
-
- @Override
- public void restore(final Bundle sharedElementState) {
- runOnUiThread(mHandler, new Runnable() {
- @Override
- public void run() {
- mTransitioningViews = null;
- Transition transition = mTransitionManager.getExitTransition(mContentScene);
- transition = transition.clone();
- transition.setEpicenterCallback(epicenterCallback);
- setSharedElementState(sharedElements, sharedElementState);
- setViewVisibility(sharedElements.values(), View.VISIBLE);
- if (mSceneTransitionListener != null) {
- mSceneTransitionListener.sharedElementStart(transition);
- mDecor.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mDecor.getViewTreeObserver().removeOnPreDrawListener(this);
- mSceneTransitionListener.sharedElementEnd();
- return true;
- }
- });
- }
- TransitionManager.beginDelayedTransition(mDecor, transition);
- setViewVisibility(transitioningViews, View.VISIBLE);
- for (View sharedElement: sharedElements.values()) {
- sharedElement.requestLayout();
- }
- }
- });
- }
-
- @Override
- public void prepareForRestore() {
- if (mTransitioningViews != null) {
- runOnUiThread(mHandler, new Runnable() {
- @Override
- public void run() {
- setViewVisibility(mTransitioningViews, View.INVISIBLE);
- }
- });
- }
- }
- };
-
- activityOptions.updateSceneTransitionAnimation(
- exitTransition, sharedElementTransition, sharedElementSource);
- }
-
- private void captureTerminalSharedElementState(final Map<String, View> sharedElements,
- final Bundle sharedElementArgs) {
- mDecor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mDecor.getViewTreeObserver().removeOnPreDrawListener(this);
- int[] tempLoc = new int[2];
- for (String name : sharedElements.keySet()) {
- View sharedElement = sharedElements.get(name);
- captureSharedElementState(sharedElement, name, sharedElementArgs, tempLoc);
- }
- return true;
- }
- });
- }
-
- private static Transition addTransitionTargets(Transition transition, Collection<View> views,
- boolean add) {
- TransitionSet set = new TransitionSet();
- set.addTransition(transition.clone());
- for (View view: views) {
- if (add) {
- set.addTarget(view);
- } else {
- set.excludeTarget(view, true);
- }
- }
- return set;
- }
-
- private static void setViewVisibility(Collection<View> views, int visibility) {
- for (View view : views) {
- view.setVisibility(visibility);
- }
- }
-
- private static void setSharedElementState(Map<String, View> sharedElements,
- Bundle sharedElementState) {
- int[] tempLoc = new int[2];
- for (Map.Entry<String, View> entry: sharedElements.entrySet()) {
- setSharedElementState(entry.getValue(), entry.getKey(), sharedElementState, tempLoc);
- }
- }
-
- /**
- * Sets the captured values from a previous
- * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
- * @param view The View to apply placement changes to.
- * @param name The shared element name given from the source Activity.
- * @param transitionArgs A <code>Bundle</code> containing all placementinformation for named
- * shared elements in the scene.
- * @param tempLoc A temporary int[2] for capturing the current location of views.
- */
- private static void setSharedElementState(View view, String name, Bundle transitionArgs,
- int[] tempLoc) {
- Bundle sharedElementBundle = transitionArgs.getBundle(name);
- if (sharedElementBundle == null) {
- return;
- }
-
- 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);
-
- ViewGroup parent = (ViewGroup) view.getParent();
- parent.getLocationOnScreen(tempLoc);
- int left = x - tempLoc[0];
- int top = y - tempLoc[1];
- int right = left + width;
- int bottom = top + height;
- view.layout(left, top, right, bottom);
-
- view.requestLayout();
- }
-
- /**
- * Captures placement information for Views with a shared element name for
- * Activity Transitions.
- * @param view The View to capture the placement information for.
- * @param name The shared element name in the target Activity to apply the placement
- * information for.
- * @param transitionArgs Bundle to store shared element placement information.
- * @param tempLoc A temporary int[2] for capturing the current location of views.
- * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[])
- */
- private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
- int[] tempLoc) {
- Bundle sharedElementBundle = new Bundle();
- view.getLocationOnScreen(tempLoc);
- float scaleX = view.getScaleX();
- sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
- int width = Math.round(view.getWidth() * scaleX);
- sharedElementBundle.putInt(KEY_WIDTH, width);
-
- float scaleY = view.getScaleY();
- sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
- int height= Math.round(view.getHeight() * scaleY);
- sharedElementBundle.putInt(KEY_HEIGHT, height);
-
- sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
-
- sharedElementBundle.putString(KEY_NAME, view.getSharedElementName());
-
- transitionArgs.putBundle(name, sharedElementBundle);
- }
-
- private void mapSharedElements(ArrayMap<String, View> sharedElements) {
- ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames();
- if (sharedElementNames != null) {
- mDecor.findSharedElements(sharedElements);
- if (mSharedElementsMap != null) {
- for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) {
- View sharedElement = sharedElements.remove(entry.getValue());
- if (sharedElement != null) {
- sharedElements.put(entry.getKey(), sharedElement);
- }
- }
- }
- sharedElements.keySet().retainAll(sharedElementNames);
- }
- }
-
- private static Rect calcEpicenter(ArrayMap<String, View> sharedElements,
- ArrayList<String> sharedElementNames) {
- if (sharedElementNames != null) {
- for (String name: sharedElementNames) {
- if (name.startsWith("android:")) {
- return null;
- }
- View view = sharedElements.get(name);
- if (view != null) {
- 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 null;
- }
-
- private class ExitSceneBack extends Transition.TransitionListenerAdapter implements
- Animator.AnimatorListener {
- private boolean mExitTransitionComplete;
- private boolean mBackgroundFadeComplete;
- private boolean mOnCompleteExecuted;
- private boolean mSharedElementTransitioned;
- private Runnable mOnComplete;
- private Bundle mSharedElementArgs;
- private Collection<View> mSharedElements;
-
- public ExitSceneBack(Runnable onComplete, Bundle sharedElementArgs,
- Collection<View> sharedElements) {
- mOnComplete = onComplete;
- mSharedElementArgs = sharedElementArgs;
- mSharedElements = sharedElements;
- }
-
- public void start(Transition exitTransition) {
- if (mActivityOptions != null) {
- mActivityOptions.dispatchPrepareRestore();
- }
- exitTransition.addListener(this);
- Drawable background = mDecor.getBackground();
- if (background != null) {
- ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 0);
- animator.addListener(this);
- animator.start();
- } else {
- mBackgroundFadeComplete = true;
- startCalledActivityEnter();
- }
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- mExitTransitionComplete = true;
- notifyComplete();
- if (!mAllowExitOverlap) {
- startCalledActivityEnter();
- }
- }
-
- private void notifyComplete() {
- if (mExitTransitionComplete && mBackgroundFadeComplete
- && mSharedElementTransitioned && !mOnCompleteExecuted) {
- mOnComplete.run();
- mSceneTransitionListener.nullPendingTransition();
- mOnCompleteExecuted = true;
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mBackgroundFadeComplete = true;
- if (mAllowExitOverlap) {
- startCalledActivityEnter();
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
-
- private void startCalledActivityEnter() {
- mActivityOptions.dispatchRestore(mSharedElementArgs);
- setViewVisibility(mSharedElements, View.INVISIBLE);
- mSharedElementTransitioned = true;
- notifyComplete();
- }
- }
-
- /**
- * Provides code for handling the Activity transition entering scene.
- * When the first scene is laid out (onPreDraw), it makes views invisible.
- * It then starts the entering transition by making non-shared elements visible. When
- * the entering transition is started, the calling Activity is notified that
- * this Activity is ready to receive the shared element. When the calling Activity notifies
- * that the shared element is ready, this Activity is notified through the
- * SceneTransitionListener.
- *
- * This class also takes into account fading the background -- either waiting until the
- * shared element is ready or the calling Activity's exit transition is complete.
- */
- private class EnterScene implements ViewTreeObserver.OnPreDrawListener, Runnable,
- ActivityOptions.ActivityTransitionTarget, Animator.AnimatorListener {
- private boolean mSharedElementReadyReceived;
- private boolean mAllDone;
- private Handler mHandler = new Handler();
- private boolean mEnterTransitionStarted;
- private ArrayMap<String, View> mSharedElementTargets = new ArrayMap<String, View>();
- private ArrayList<View> mEnteringViews = new ArrayList<View>();
- private Transition.EpicenterCallback mEpicenterCallback;
-
- public EnterScene() {
- mSceneTransitionListener.nullPendingTransition();
- Drawable background = getDecorView().getBackground();
- if (background != null) {
- background.setAlpha(0);
- mDecor.drawableChanged();
- }
- mSceneTransitionListener.convertToTranslucent();
- }
-
- @Override
- public boolean onPreDraw() {
- ViewTreeObserver observer = mDecor.getViewTreeObserver();
- observer.removeOnPreDrawListener(this);
- if (!mEnterTransitionStarted && mSceneTransitionListener != null) {
- mEnterTransitionStarted = true;
- mDecor.captureTransitioningViews(mEnteringViews);
- mapSharedElements(mSharedElementTargets);
- mEnteringViews.removeAll(mSharedElementTargets.values());
- Rect epicenter = calcEpicenter(mSharedElementTargets,
- mActivityOptions.getSharedElementNames());
- mEpicenterCallback = new FixedEpicenterCallback(epicenter);
-
- setViewVisibility(mEnteringViews, View.INVISIBLE);
- setViewVisibility(mSharedElementTargets.values(), View.INVISIBLE);
- if (mAllowEnterOverlap) {
- beginEnterScene();
- }
- observer.addOnPreDrawListener(this);
- return false;
- } else {
- mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT);
- mActivityOptions.dispatchSceneTransitionStarted(this,
- new ArrayList<String>(mSharedElementTargets.keySet()));
- return !mSharedElementReadyReceived;
- }
- }
-
- public void start() {
- ViewTreeObserver observer = mDecor.getViewTreeObserver();
- observer.addOnPreDrawListener(this);
- }
-
- @Override
- public void run() {
- exitTransitionComplete();
- }
-
- @Override
- public void sharedElementTransitionComplete(final Bundle transitionArgs) {
- if (!mSharedElementReadyReceived) {
- mSharedElementReadyReceived = true;
- mHandler.removeCallbacks(this);
- mHandler.postDelayed(this, MAX_TRANSITION_FINISH_WAIT);
- if (!mSharedElementTargets.isEmpty()) {
- runOnUiThread(mHandler, new Runnable() {
- @Override
- public void run() {
- Transition transition = getTransitionManager().getEnterTransition(
- mContentScene);
- if (transition == null) {
- transition = TransitionManager.getDefaultTransition();
- }
- transition = addTransitionTargets(transition,
- mSharedElementTargets.values(),
- true);
- transition.setEpicenterCallback(mEpicenterCallback);
- if (transitionArgs == null) {
- TransitionManager.beginDelayedTransition(mDecor, transition);
- setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
- } else {
- mSceneTransitionListener.sharedElementStart(transition);
- setSharedElementState(mSharedElementTargets, transitionArgs);
- setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
- mDecor.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mDecor.getViewTreeObserver()
- .removeOnPreDrawListener(this);
- mSceneTransitionListener.sharedElementEnd();
- mActivityOptions.dispatchSharedElementsReady();
- return true;
- }
- });
- TransitionManager.beginDelayedTransition(mDecor, transition);
- }
- }
- });
- }
- if (mAllowEnterOverlap) {
- fadeInBackground();
- }
- }
- }
-
- private void fadeInBackground() {
- Drawable background = getDecorView().getBackground();
- if (background == null) {
- mSceneTransitionListener.convertFromTranslucent();
- } else {
- ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
- animator.addListener(this);
- animator.start();
- }
- }
-
- @Override
- public void exitTransitionComplete() {
- if (mAllDone) {
- return;
- }
- mAllDone = true;
- sharedElementTransitionComplete(null);
- mHandler.removeCallbacks(this);
- if (!mAllowEnterOverlap) {
- runOnUiThread(mHandler, new Runnable() {
- @Override
- public void run() {
- beginEnterScene();
- fadeInBackground();
- }
- });
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mSceneTransitionListener.convertFromTranslucent();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
-
- private void beginEnterScene() {
- Transition transition = getTransitionManager().getEnterTransition(mContentScene);
- if (transition == null) {
- transition = TransitionManager.getDefaultTransition();
- }
- transition = addTransitionTargets(transition, mEnteringViews, true);
- transition.setEpicenterCallback(mEpicenterCallback);
- TransitionManager.beginDelayedTransition(mDecor, transition);
- setViewVisibility(mEnteringViews, View.VISIBLE);
- }
- }
-
- private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
- private Rect mEpicenter;
-
- public FixedEpicenterCallback(Rect epicenter) {
- mEpicenter = epicenter;
- }
-
- @Override
- public Rect getEpicenter(Transition transition) {
- return mEpicenter;
- }
- };
}