Merge "Support Activity Transitions when activity stopped."

This commit is contained in:
George Mount
2014-05-15 14:49:55 +00:00
committed by Android (Google) Code Review
10 changed files with 1183 additions and 1254 deletions

View File

@@ -3257,6 +3257,7 @@ package android.app {
method public boolean navigateUpToFromChild(android.app.Activity, android.content.Intent);
method public void onActionModeFinished(android.view.ActionMode);
method public void onActionModeStarted(android.view.ActionMode);
method protected void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
method public void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
@@ -3335,7 +3336,6 @@ package android.app {
method public final boolean requestWindowFeature(int);
method public final void runOnUiThread(java.lang.Runnable);
method public void setActionBar(android.widget.Toolbar);
method public void setActivityTransitionListener(android.app.ActivityOptions.ActivityTransitionListener);
method public void setContentTransitionManager(android.transition.TransitionManager);
method public void setContentView(int);
method public void setContentView(android.view.View);
@@ -3357,6 +3357,7 @@ package android.app {
method public final void setResult(int);
method public final void setResult(int, android.content.Intent);
method public final void setSecondaryProgress(int);
method public void setSharedElementListener(android.app.SharedElementListener);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -3576,28 +3577,13 @@ package android.app {
public class ActivityOptions {
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.Window, android.view.View, java.lang.String);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.Window, android.app.ActivityOptions.ActivityTransitionListener);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View, java.lang.String>...);
method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
}
public static class ActivityOptions.ActivityTransitionListener {
ctor public ActivityOptions.ActivityTransitionListener();
method public android.util.Pair<android.view.View, java.lang.String>[] getSharedElementsMapping();
method public boolean handleRejectedSharedElements(java.util.List<android.view.View>);
method public void onCaptureSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void onCaptureSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void onEnterReady();
method public void onExitTransitionComplete();
method public void onRemoteExitComplete();
method public void onSharedElementExitTransitionComplete();
method public void onSharedElementTransferred(java.util.List<java.lang.String>, java.util.List<android.view.View>);
method public void onStartEnterTransition(java.util.List<java.lang.String>, java.util.List<android.view.View>);
method public void onStartExitTransition(java.util.List<java.lang.String>, java.util.List<android.view.View>);
}
public class AlarmManager {
method public void cancel(android.app.PendingIntent);
method public void set(int, long, android.app.PendingIntent);
@@ -4821,6 +4807,14 @@ package android.app {
field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0
}
public class SharedElementListener {
ctor public SharedElementListener();
method public void handleRejectedSharedElements(java.util.List<android.view.View>);
method public void remapSharedElements(java.util.List<java.lang.String>, java.util.Map<java.lang.String, android.view.View>);
method public void setSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void setSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
}
public deprecated class TabActivity extends android.app.ActivityGroup {
ctor public TabActivity();
method public android.widget.TabHost getTabHost();

View File

@@ -777,8 +777,9 @@ public class Activity extends ContextThemeWrapper
private Thread mUiThread;
final Handler mHandler = new Handler();
private ActivityOptions mCalledActivityOptions;
private EnterTransitionCoordinator mEnterTransitionCoordinator;
private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
SharedElementListener mTransitionListener = new SharedElementListener();
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -1100,9 +1101,6 @@ public class Activity extends ContextThemeWrapper
mTitleReady = true;
onTitleChanged(getTitle(), getTitleColor());
}
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.readyToEnter();
}
mCalled = true;
}
@@ -1149,12 +1147,6 @@ public class Activity extends ContextThemeWrapper
}
getApplication().dispatchActivityStarted(this);
final ActivityOptions activityOptions = getActivityOptions();
if (activityOptions != null &&
activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this);
}
}
/**
@@ -1204,7 +1196,6 @@ public class Activity extends ContextThemeWrapper
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
mCalledActivityOptions = null;
mCalled = true;
}
@@ -1279,6 +1270,7 @@ public class Activity extends ContextThemeWrapper
final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
@@ -1549,10 +1541,7 @@ public class Activity extends ContextThemeWrapper
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
if (mCalledActivityOptions != null) {
mCalledActivityOptions.dispatchActivityStopped();
mCalledActivityOptions = null;
}
mActivityTransitionState.onStop();
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
@@ -3650,7 +3639,7 @@ public class Activity extends ContextThemeWrapper
public void startActivityForResult(Intent intent, int requestCode) {
Bundle options = null;
if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
options = ActivityOptions.makeSceneTransitionAnimation(mWindow, null).toBundle();
options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
}
startActivityForResult(intent, requestCode, options);
}
@@ -3691,9 +3680,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (options != null) {
ActivityOptions activityOptions = new ActivityOptions(options);
activityOptions.dispatchStartExit();
mCalledActivityOptions = activityOptions;
mActivityTransitionState.startExitOutTransition(this, options);
}
if (mParent == null) {
Instrumentation.ActivityResult ar =
@@ -4559,13 +4546,10 @@ public class Activity extends ContextThemeWrapper
* to reverse its exit Transition. When the exit Transition completes,
* {@link #finish()} is called. If no entry Transition was used, finish() is called
* immediately and the Activity exit Transition is run.
* @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
* android.app.ActivityOptions.ActivityTransitionListener)
* @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[])
*/
public void finishWithTransition() {
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.startExit();
} else {
if (!mActivityTransitionState.startExitBackTransition(this)) {
finish();
}
}
@@ -4642,6 +4626,27 @@ public class Activity extends ContextThemeWrapper
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
/**
* Called when an activity you launched with an activity transition exposes this
* Activity through a returning activity transition, giving you the resultCode
* and any additional data from it. This method will only be called if the activity
* set a result code other than {@link #RESULT_CANCELED} and it supports activity
* transitions with {@link Window#FEATURE_CONTENT_TRANSITIONS}.
*
* <p>The purpose of this function is to let the called Activity send a hint about
* its state so that this underlying Activity can prepare to be exposed. A call to
* this method does not guarantee that the called Activity has or will be exiting soon.
* It only indicates that it will expose this Activity's Window and it has
* some data to pass to prepare it.</p>
*
* @param resultCode The integer result code returned by the child activity
* through its setResult().
* @param data An Intent, which can return result data to the caller
* (various data can be attached to Intent "extras").
*/
protected void onActivityReenter(int resultCode, Intent data) {
}
/**
* Create a new PendingIntent object which you can hand to others
* for them to use to send result data back to your
@@ -5246,7 +5251,8 @@ public class Activity extends ContextThemeWrapper
* This call has no effect on non-translucent activities or on activities with the
* {@link android.R.attr#windowIsFloating} attribute.
*
* @see #convertToTranslucent(TranslucentConversionListener)
* @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,
* ActivityOptions)
* @see TranslucentConversionListener
*
* @hide
@@ -5544,18 +5550,18 @@ public class Activity extends ContextThemeWrapper
}
/**
* When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
* android.app.ActivityOptions.ActivityTransitionListener)} was used to start an Activity,
* the Window will be triggered to enter with a Transition. <code>listener</code> allows
* The Activity to listen to events of the entering transition and control the mapping of
* shared elements. This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
* When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
* android.view.View, String)} was used to start an Activity, <var>listener</var>
* will be called to handle shared elements. This requires
* {@link Window#FEATURE_CONTENT_TRANSITIONS}.
*
* @param listener Used to listen to events in the entering transition.
* @param listener Used to manipulate how shared element transitions function.
*/
public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.setActivityTransitionListener(listener);
public void setSharedElementListener(SharedElementListener listener) {
if (listener == null) {
listener = new SharedElementListener();
}
mTransitionListener = listener;
}
// ------------------ Internal API ------------------
@@ -5621,19 +5627,23 @@ public class Activity extends ContextThemeWrapper
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
final void performCreate(Bundle icicle) {
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
onCreate(icicle, persistentState);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
final void performStart() {
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
@@ -5656,6 +5666,7 @@ public class Activity extends ContextThemeWrapper
lm.doReportStart();
}
}
mActivityTransitionState.enterReady(this);
}
final void performRestart() {

View File

@@ -17,18 +17,18 @@
package android.app;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.View;
import android.view.Window;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -108,6 +108,12 @@ public class ActivityOptions {
private static final String KEY_TRANSITION_COMPLETE_LISTENER
= "android:transitionCompleteListener";
private static final String KEY_TRANSITION_IS_RETURNING = "android:transitionIsReturning";
private static final String KEY_TRANSITION_SHARED_ELEMENTS = "android:sharedElementNames";
private static final String KEY_LOCAL_SHARED_ELEMENTS = "android:localSharedElementNames";
private static final String KEY_RESULT_DATA = "android:resultData";
private static final String KEY_RESULT_CODE = "android:resultCode";
/** @hide */
public static final int ANIM_NONE = 0;
/** @hide */
@@ -131,7 +137,12 @@ public class ActivityOptions {
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
private ResultReceiver mExitReceiver;
private ResultReceiver mTransitionReceiver;
private boolean mIsReturning;
private ArrayList<String> mSharedElementNames;
private ArrayList<String> mLocalSharedElementNames;
private Intent mResultData;
private int mResultCode;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -334,7 +345,7 @@ public class ActivityOptions {
* <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
* enabled on the calling Activity to cause an exit transition. The same must be in
* the called Activity to get an entering transition.</p>
* @param window The window containing shared elements.
* @param activity The Activity whose window contains the shared elements.
* @param sharedElement The View to transition to the started Activity. sharedElement must
* have a non-null sharedElementName.
* @param sharedElementName The shared element name as used in the target Activity. This may
@@ -344,40 +355,70 @@ public class ActivityOptions {
* @see android.transition.Transition#setEpicenterCallback(
* android.transition.Transition.EpicenterCallback)
*/
public static ActivityOptions makeSceneTransitionAnimation(Window window,
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
View sharedElement, String sharedElementName) {
return makeSceneTransitionAnimation(window,
new SharedElementMappingListener(sharedElement, sharedElementName));
return makeSceneTransitionAnimation(activity, Pair.create(sharedElement, sharedElementName));
}
/**
* Create an ActivityOptions to transition between Activities using cross-Activity scene
* animations. This method carries the position of multiple shared elements to the started
* Activity. The position of the first element in the value returned from
* {@link android.app.ActivityOptions.ActivityTransitionListener#getSharedElementsMapping()}
* Activity. The position of the first element in sharedElements
* will be used as the epicenter for the exit Transition. The position of the associated
* shared element in the launched Activity will be the epicenter of its entering Transition.
*
* <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
* enabled on the calling Activity to cause an exit transition. The same must be in
* the called Activity to get an entering transition.</p>
* @param window The window containing shared elements.
* @param listener The listener to use to monitor activity transition events.
* @param activity The Activity whose window contains the shared elements.
* @param sharedElements The names of the shared elements to transfer to the called
* Activity and their associated Views. The Views must each have
* a unique shared element name.
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
* Returns null if the Window does not have {@link Window#FEATURE_CONTENT_TRANSITIONS}.
* @see android.transition.Transition#setEpicenterCallback(
* android.transition.Transition.EpicenterCallback)
*/
public static ActivityOptions makeSceneTransitionAnimation(Window window,
ActivityTransitionListener listener) {
if (!window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
Pair<View, String>... sharedElements) {
if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
return null;
}
ActivityOptions opts = new ActivityOptions();
opts.mAnimationType = ANIM_SCENE_TRANSITION;
ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener);
opts.mExitReceiver = exit;
ArrayList<String> names = new ArrayList<String>();
ArrayList<String> mappedNames = new ArrayList<String>();
if (sharedElements != null) {
for (int i = 0; i < sharedElements.length; i++) {
Pair<View, String> sharedElement = sharedElements[i];
names.add(sharedElement.second);
mappedNames.add(sharedElement.first.getViewName());
}
}
ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
mappedNames, false);
opts.mTransitionReceiver = exit;
opts.mSharedElementNames = names;
opts.mLocalSharedElementNames = mappedNames;
opts.mIsReturning = false;
return opts;
}
/** @hide */
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
int resultCode, Intent resultData) {
ActivityOptions opts = new ActivityOptions();
opts.mAnimationType = ANIM_SCENE_TRANSITION;
opts.mSharedElementNames = sharedElementNames;
opts.mTransitionReceiver = exitCoordinator;
opts.mIsReturning = true;
opts.mResultCode = resultCode;
opts.mResultData = resultData;
return opts;
}
@@ -413,7 +454,12 @@ public class ActivityOptions {
break;
case ANIM_SCENE_TRANSITION:
mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
mLocalSharedElementNames = opts.getStringArrayList(KEY_LOCAL_SHARED_ELEMENTS);
mResultData = opts.getParcelable(KEY_RESULT_DATA);
mResultCode = opts.getInt(KEY_RESULT_CODE);
break;
}
}
@@ -470,15 +516,15 @@ public class ActivityOptions {
/** @hide */
public void dispatchActivityStopped() {
if (mExitReceiver != null) {
mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
if (mTransitionReceiver != null) {
mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
}
}
/** @hide */
public void dispatchStartExit() {
if (mExitReceiver != null) {
mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
if (mTransitionReceiver != null) {
mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
}
}
@@ -492,6 +538,33 @@ public class ActivityOptions {
}
}
/** @hide */
public void setReturning() {
mIsReturning = true;
}
/** @hide */
public boolean isReturning() {
return mIsReturning;
}
/** @hide */
public ArrayList<String> getSharedElementNames() {
return mSharedElementNames;
}
/** @hide */
public ArrayList<String> getLocalSharedElementNames() { return mLocalSharedElementNames; }
/** @hide */
public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
/** @hide */
public int getResultCode() { return mResultCode; }
/** @hide */
public Intent getResultData() { return mResultData; }
/** @hide */
public static void abort(Bundle options) {
if (options != null) {
@@ -499,15 +572,6 @@ public class ActivityOptions {
}
}
/** @hide */
public EnterTransitionCoordinator createEnterActivityTransition(Activity activity) {
EnterTransitionCoordinator coordinator = null;
if (mAnimationType == ANIM_SCENE_TRANSITION) {
coordinator = new EnterTransitionCoordinator(activity, mExitReceiver);
}
return coordinator;
}
/**
* Update the current values in this ActivityOptions from those supplied
* in <var>otherOptions</var>. Any values
@@ -517,7 +581,12 @@ public class ActivityOptions {
if (otherOptions.mPackageName != null) {
mPackageName = otherOptions.mPackageName;
}
mExitReceiver = null;
mTransitionReceiver = null;
mSharedElementNames = null;
mLocalSharedElementNames = null;
mIsReturning = false;
mResultData = null;
mResultCode = 0;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
mAnimationType = otherOptions.mAnimationType;
@@ -562,9 +631,14 @@ public class ActivityOptions {
break;
case ANIM_SCENE_TRANSITION:
mAnimationType = otherOptions.mAnimationType;
mExitReceiver = otherOptions.mExitReceiver;
mTransitionReceiver = otherOptions.mTransitionReceiver;
mSharedElementNames = otherOptions.mSharedElementNames;
mLocalSharedElementNames = otherOptions.mLocalSharedElementNames;
mIsReturning = otherOptions.mIsReturning;
mThumbnail = null;
mAnimationStartedListener = null;
mResultData = otherOptions.mResultData;
mResultCode = otherOptions.mResultCode;
break;
}
}
@@ -608,9 +682,14 @@ public class ActivityOptions {
break;
case ANIM_SCENE_TRANSITION:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
if (mExitReceiver != null) {
b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver);
if (mTransitionReceiver != null) {
b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
}
b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
b.putStringArrayList(KEY_LOCAL_SHARED_ELEMENTS, mLocalSharedElementNames);
b.putParcelable(KEY_RESULT_DATA, mResultData);
b.putInt(KEY_RESULT_CODE, mResultCode);
break;
}
return b;
@@ -630,126 +709,4 @@ public class ActivityOptions {
return null;
}
/**
* Listener provided in
* {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
* android.app.ActivityOptions.ActivityTransitionListener)} or in
* {@link android.app.Activity#setActivityTransitionListener(
* android.app.ActivityOptions.ActivityTransitionListener)} to monitor the Activity transitions.
* The events can be used to customize or override Activity Transition behavior.
*/
public static class ActivityTransitionListener {
/**
* Called when the enter Transition is ready to start, but hasn't started yet. If
* {@link android.view.Window#getEnterTransition()} is non-null,
* The entering views will be {@link View#INVISIBLE}.
*/
public void onEnterReady() {}
/**
* Called when the remote exiting transition completes.
*/
public void onRemoteExitComplete() {}
/**
* Called when the start state for shared elements is captured on enter.
*
* @param sharedElementNames The names of the shared elements that were accepted into
* the View hierarchy.
* @param sharedElements The shared elements that are part of the View hierarchy.
* @param sharedElementSnapshots The Views containing snap shots of the shared element
* from the launching Window. These elements will not
* be part of the scene, but will be positioned relative
* to the Window decor View.
*/
public void onCaptureSharedElementStart(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {}
/**
* Called when the end state for shared elements is captured on enter.
*
* @param sharedElementNames The names of the shared elements that were accepted into
* the View hierarchy.
* @param sharedElements The shared elements that are part of the View hierarchy.
* @param sharedElementSnapshots The Views containing snap shots of the shared element
* from the launching Window. These elements will not
* be part of the scene, but will be positioned relative
* to the Window decor View.
*/
public void onCaptureSharedElementEnd(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {}
/**
* Called when the enter Transition has been started.
* @param sharedElementNames The names of shared elements that were transferred.
* @param sharedElements The shared elements that were transferred.
*/
public void onStartEnterTransition(List<String> sharedElementNames,
List<View> sharedElements) {}
/**
* Called when the exit Transition has been started.
* @param sharedElementNames The names of all shared elements that will be transferred.
* @param sharedElements All shared elements that will be transferred.
*/
public void onStartExitTransition(List<String> sharedElementNames,
List<View> sharedElements) {}
/**
* Called when the exiting shared element transition completes.
*/
public void onSharedElementExitTransitionComplete() {}
/**
* Called on exit when the shared element has been transferred.
* @param sharedElementNames The names of all shared elements that were transferred.
* @param sharedElements All shared elements that will were transferred.
*/
public void onSharedElementTransferred(List<String> sharedElementNames,
List<View> sharedElements) {}
/**
* Called when the exit transition has completed.
*/
public void onExitTransitionComplete() {}
/**
* Returns a mapping from a View in the View hierarchy to the shared element name used
* in the call. This is called twice -- once when the view is
* entering and again when it exits. A null return value indicates that the
* View hierachy can be trusted without any remapping.
* @return A map from a View in the hierarchy to the shared element name used in the
* call.
*/
public Pair<View, String>[] getSharedElementsMapping() { return null; }
/**
* Returns <code>true</code> if the ActivityTransitionListener will handle removing
* rejected shared elements from the scene. If <code>false</code> is returned, a default
* animation will be used to remove the rejected shared elements from the scene.
*
* @param rejectedSharedElements Views containing visual information of shared elements
* that are not part of the entering scene. These Views
* are positioned relative to the Window decor View.
* @return <code>false</code> if the default animation should be used to remove the
* rejected shared elements from the scene or <code>true</code> if the listener provides
* custom handling.
*/
public boolean handleRejectedSharedElements(List<View> rejectedSharedElements) {
return false;
}
}
private static class SharedElementMappingListener extends ActivityTransitionListener {
Pair<View, String>[] mSharedElementsMapping = new Pair[1];
public SharedElementMappingListener(View view, String name) {
mSharedElementsMapping[0] = Pair.create(view, name);
}
@Override
public Pair<View, String>[] getSharedElementsMapping() {
return mSharedElementsMapping;
}
}
}

View File

@@ -15,27 +15,14 @@
*/
package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
@@ -83,14 +70,13 @@ import java.util.Collection;
* - onActivityStopped() is called and all exited Views are made VISIBLE.
*
* Typical finishWithTransition goes like this:
* 1) finishWithTransition() calls startExit()
* - The Window start transitioning to Translucent
* 1) finishWithTransition() creates an ExitTransitionCoordinator and calls startExit()
* - The Window start transitioning to Translucent with a new ActivityOptions.
* - If no background exists, a black background is substituted
* - MSG_PREPARE_RESTORE is sent to the ExitTransitionCoordinator
* - The shared elements in the scene are matched against those shared elements
* that were sent by comparing the names.
* - The exit transition is started by setting Views to INVISIBLE.
* 2) MSG_PREPARE_RESTORE is received by the EnterTransitionCoordinator
* 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created.
* - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
* was called
* 3) The Window is made translucent and a callback is received
@@ -98,52 +84,46 @@ import java.util.Collection;
* 4) The background alpha animation completes
* 5) The shared element transition completes
* - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
* ExitTransitionCoordinator
* 6) MSG_TAKE_SHARED_ELEMENTS is received by ExitTransitionCoordinator
* EnterTransitionCoordinator
* 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator
* - Shared elements are made VISIBLE
* - Shared elements positions and size are set to match the end state of the calling
* Activity.
* - The shared element transition is started
* - If the window allows overlapping transitions, the views transition is started by setting
* the entering Views to VISIBLE.
* - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
* 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator
* - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
* 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
* - The shared elements are made INVISIBLE
* 8) The exit transition completes in the finishing Activity.
* - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator.
* - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
* - finish() is called on the exiting Activity
* 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator.
* 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
* - If the window doesn't allow overlapping enter transitions, the enter transition is started
* by setting entering views to VISIBLE.
*/
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";
static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver";
private static final String KEY_SCREEN_X = "shared_element:screenX";
private static final String KEY_SCREEN_Y = "shared_element:screenY";
private static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
private static final String KEY_WIDTH = "shared_element:width";
private static final String KEY_HEIGHT = "shared_element:height";
private static final String KEY_NAME = "shared_element:name";
private static final String KEY_BITMAP = "shared_element:bitmap";
private static final String KEY_SCALE_TYPE = "shared_element:scaleType";
private static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
protected static final String KEY_SCREEN_X = "shared_element:screenX";
protected static final String KEY_SCREEN_Y = "shared_element:screenY";
protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
protected static final String KEY_WIDTH = "shared_element:width";
protected static final String KEY_HEIGHT = "shared_element:height";
protected static final String KEY_BITMAP = "shared_element:bitmap";
protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
private static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
// The background fade in/out duration. 150ms is pretty quick, but not abrupt.
public static final int FADE_BACKGROUND_DURATION_MS = 150;
protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
/**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
@@ -154,7 +134,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
* until this message is received, but may wait for
* MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
*/
public static final int MSG_SET_LISTENER = 100;
public static final int MSG_SET_REMOTE_RECEIVER = 100;
/**
* Sent by the entering coordinator to tell the exiting coordinator
@@ -164,18 +144,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
*/
public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
/**
* Sent by the EnterTransitionCoordinator to tell the
* ExitTransitionCoordinator to hide all of its exited views after
* MSG_ACTIVITY_STOPPED has caused them all to show.
*/
public static final int MSG_PREPARE_RESTORE = 102;
/**
* Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped
* to leave the Activity in a good state after it has been hidden.
*/
public static final int MSG_ACTIVITY_STOPPED = 103;
public static final int MSG_ACTIVITY_STOPPED = 102;
/**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
@@ -186,7 +159,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
* until this message is received, but may wait for
* MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
*/
public static final int MSG_TAKE_SHARED_ELEMENTS = 104;
public static final int MSG_TAKE_SHARED_ELEMENTS = 103;
/**
* Sent by the exiting coordinator (either
@@ -196,309 +169,41 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
* remote coordinator. If it is false, it will trigger the enter
* transition to start.
*/
public static final int MSG_EXIT_TRANSITION_COMPLETE = 105;
public static final int MSG_EXIT_TRANSITION_COMPLETE = 104;
/**
* Sent by Activity#startActivity to begin the exit transition.
*/
public static final int MSG_START_EXIT_TRANSITION = 106;
public static final int MSG_START_EXIT_TRANSITION = 105;
private Window mWindow;
private ArrayList<View> mSharedElements = new ArrayList<View>();
private ArrayList<String> mTargetSharedNames = new ArrayList<String>();
private ActivityOptions.ActivityTransitionListener mListener =
new ActivityOptions.ActivityTransitionListener();
private ArrayList<View> mEnteringViews;
private ResultReceiver mRemoteResultReceiver;
private boolean mNotifiedSharedElementTransitionComplete;
private boolean mNotifiedExitTransitionComplete;
private boolean mSharedElementTransitionStarted;
/**
* It took too long for a message from the entering Activity, so we canceled the transition.
*/
public static final int MSG_CANCEL = 106;
private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
final private Window mWindow;
final protected ArrayList<String> mAllSharedElementNames;
final protected ArrayList<View> mSharedElements = new ArrayList<View>();
final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
final protected ArrayList<View> mTransitioningViews = new ArrayList<View>();
final protected SharedElementListener mListener;
protected ResultReceiver mResultReceiver;
final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
private Transition.TransitionListener mSharedElementListener =
new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
onSharedElementTransitionEnd();
}
};
private Transition.TransitionListener mExitListener =
new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
onExitTransitionEnd();
}
};
public ActivityTransitionCoordinator(Window window)
{
public ActivityTransitionCoordinator(Window window,
ArrayList<String> allSharedElementNames,
ArrayList<String> accepted, ArrayList<String> localNames,
SharedElementListener listener) {
super(new Handler());
mWindow = window;
}
// -------------------- ResultsReceiver Overrides ----------------------
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_SET_LISTENER:
ResultReceiver resultReceiver
= resultData.getParcelable(KEY_TRANSITION_RESULTS_RECEIVER);
setRemoteResultReceiver(resultReceiver);
onSetResultReceiver();
break;
case MSG_HIDE_SHARED_ELEMENTS:
onHideSharedElements();
break;
case MSG_PREPARE_RESTORE:
onPrepareRestore();
break;
case MSG_EXIT_TRANSITION_COMPLETE:
if (!mSharedElementTransitionStarted) {
send(resultCode, resultData);
} else {
onRemoteSceneExitComplete();
}
break;
case MSG_TAKE_SHARED_ELEMENTS:
ArrayList<String> sharedElementNames
= resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
Bundle sharedElementState = resultData.getBundle(KEY_SHARED_ELEMENT_STATE);
onTakeSharedElements(sharedElementNames, sharedElementState);
break;
case MSG_ACTIVITY_STOPPED:
onActivityStopped();
break;
case MSG_START_EXIT_TRANSITION:
startExit();
break;
}
}
// -------------------- calls that can be overridden by subclasses --------------------
/**
* Called when MSG_SET_LISTENER is received. This will only be received by
* ExitTransitionCoordinator.
*/
protected void onSetResultReceiver() {}
/**
* Called when MSG_HIDE_SHARED_ELEMENTS is received
*/
protected void onHideSharedElements() {
setViewVisibility(getSharedElements(), View.INVISIBLE);
mListener.onSharedElementTransferred(getSharedElementNames(), getSharedElements());
}
/**
* Called when MSG_PREPARE_RESTORE is called. This will only be received by
* ExitTransitionCoordinator.
*/
protected void onPrepareRestore() {
mListener.onEnterReady();
}
/**
* Called when MSG_EXIT_TRANSITION_COMPLETE is received -- the remote coordinator has
* completed its exit transition. This can be called by the ExitTransitionCoordinator when
* starting an Activity or EnterTransitionCoordinator when called with finishWithTransition.
*/
protected void onRemoteSceneExitComplete() {
if (!allowOverlappingTransitions()) {
Transition transition = beginTransition(mEnteringViews, false, true, true);
onStartEnterTransition(transition, mEnteringViews);
}
mListener.onRemoteExitComplete();
}
/**
* Called when MSG_TAKE_SHARED_ELEMENTS is received. This means that the shared elements are
* in a stable state and ready to move to the Window.
* @param sharedElementNames The names of the shared elements to move.
* @param state Contains the shared element states (size & position)
*/
protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
setSharedElements();
reconcileSharedElements(sharedElementNames);
mEnteringViews.removeAll(mSharedElements);
final ArrayList<View> accepted = new ArrayList<View>();
final ArrayList<View> rejected = new ArrayList<View>();
createSharedElementImages(accepted, rejected, sharedElementNames, state);
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
setSharedElementState(state, accepted);
handleRejected(rejected);
mListener = listener;
mAllSharedElementNames = allSharedElementNames;
setSharedElements(accepted, localNames);
if (getViewsTransition() != null) {
setViewVisibility(mEnteringViews, View.INVISIBLE);
getDecor().captureTransitioningViews(mTransitioningViews);
mTransitioningViews.removeAll(mSharedElements);
}
setViewVisibility(mSharedElements, View.VISIBLE);
Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(),
true);
setOriginalImageViewState(originalImageViewState);
if (allowOverlappingTransitions()) {
onStartEnterTransition(transition, mEnteringViews);
}
mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
}
/**
* Called when MSG_ACTIVITY_STOPPED is received. This is received when Activity.onStop is
* called after running startActivity* is called using an Activity Transition.
*/
protected void onActivityStopped() {}
/**
* Called when the start transition is ready to run. This may be immediately after
* MSG_TAKE_SHARED_ELEMENTS or MSG_EXIT_TRANSITION_COMPLETE, depending on whether
* overlapping transitions are allowed.
* @param transition The transition currently started.
* @param enteringViews The views entering the scene. This won't include shared elements.
*/
protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
if (getViewsTransition() != null) {
setViewVisibility(enteringViews, View.VISIBLE);
}
mEnteringViews = null;
mListener.onStartEnterTransition(getSharedElementNames(), getSharedElements());
}
/**
* Called when the exit transition has started.
* @param exitingViews The views leaving the scene. This won't include shared elements.
*/
protected void onStartExitTransition(ArrayList<View> exitingViews) {}
/**
* Called during the exit when the shared element transition has completed.
*/
protected void onSharedElementTransitionEnd() {
Bundle bundle = new Bundle();
int[] tempLoc = new int[2];
for (int i = 0; i < mSharedElements.size(); i++) {
View sharedElement = mSharedElements.get(i);
String name = mTargetSharedNames.get(i);
captureSharedElementState(sharedElement, name, bundle, tempLoc);
}
Bundle allValues = new Bundle();
allValues.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, getSharedElementNames());
allValues.putBundle(KEY_SHARED_ELEMENT_STATE, bundle);
sharedElementTransitionComplete(allValues);
mListener.onSharedElementExitTransitionComplete();
}
/**
* Called after the shared element transition is complete to pass the shared element state
* to the remote coordinator.
* @param bundle The Bundle to send to the coordinator containing the shared element state.
*/
protected abstract void sharedElementTransitionComplete(Bundle bundle);
/**
* Called when the exit transition finishes.
*/
protected void onExitTransitionEnd() {
mListener.onExitTransitionComplete();
}
/**
* Called to start the exit transition. Launched from ActivityOptions#dispatchStartExit
*/
protected abstract void startExit();
/**
* A non-null transition indicates that the Views of the Window should be made INVISIBLE.
* @return The Transition used to cause transitioning views to either enter or exit the scene.
*/
protected abstract Transition getViewsTransition();
/**
* @return The Transition used to move the shared elements from the start position and size
* to the end position and size.
*/
protected abstract Transition getSharedElementTransition();
/**
* @return When the enter transition should overlap with the exit transition of the
* remote controller.
*/
protected abstract boolean allowOverlappingTransitions();
// called by subclasses
protected void notifySharedElementTransitionComplete(Bundle sharedElements) {
if (!mNotifiedSharedElementTransitionComplete) {
mNotifiedSharedElementTransitionComplete = true;
mRemoteResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElements);
}
}
protected void notifyExitTransitionComplete() {
if (!mNotifiedExitTransitionComplete) {
mNotifiedExitTransitionComplete = true;
mRemoteResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
}
}
protected void notifyPrepareRestore() {
mRemoteResultReceiver.send(MSG_PREPARE_RESTORE, null);
}
protected void setRemoteResultReceiver(ResultReceiver resultReceiver) {
mRemoteResultReceiver = resultReceiver;
}
protected void notifySetListener() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_TRANSITION_RESULTS_RECEIVER, this);
mRemoteResultReceiver.send(MSG_SET_LISTENER, bundle);
}
protected void setEnteringViews(ArrayList<View> views) {
mEnteringViews = views;
}
protected void setSharedElements() {
Pair<View, String>[] sharedElements = mListener.getSharedElementsMapping();
mSharedElements.clear();
mTargetSharedNames.clear();
if (sharedElements == null) {
ArrayMap<String, View> map = new ArrayMap<String, View>();
if (getViewsTransition() != null) {
setViewVisibility(mEnteringViews, View.VISIBLE);
}
getDecor().findNamedViews(map);
if (getViewsTransition() != null) {
setViewVisibility(mEnteringViews, View.INVISIBLE);
}
for (int i = 0; i < map.size(); i++) {
View view = map.valueAt(i);
String name = map.keyAt(i);
mSharedElements.add(view);
mTargetSharedNames.add(name);
}
} else {
for (int i = 0; i < sharedElements.length; i++) {
Pair<View, String> viewStringPair = sharedElements[i];
View view = viewStringPair.first;
String name = viewStringPair.second;
mSharedElements.add(view);
mTargetSharedNames.add(name);
}
}
}
protected ArrayList<View> getSharedElements() {
return mSharedElements;
}
protected ArrayList<String> getSharedElementNames() {
return mTargetSharedNames;
setEpicenter();
}
protected Window getWindow() {
@@ -509,238 +214,43 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
}
protected void startExitTransition(ArrayList<String> sharedElements) {
setSharedElements();
reconcileSharedElements(sharedElements);
ArrayList<View> transitioningViews = captureTransitioningViews();
beginTransition(transitioningViews, true, true, false);
onStartExitTransition(transitioningViews);
if (getViewsTransition() != null) {
setViewVisibility(transitioningViews, View.INVISIBLE);
/**
* Sets the transition epicenter to the position of the first shared element.
*/
protected void setEpicenter() {
View epicenter = null;
if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty() &&
mAllSharedElementNames.get(0).equals(mSharedElementNames.get(0))) {
epicenter = mSharedElements.get(0);
}
mListener.onStartExitTransition(getSharedElementNames(), getSharedElements());
setEpicenter(epicenter);
}
protected void clearConnections() {
mRemoteResultReceiver = null;
}
// public API
public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
if (listener == null) {
mListener = new ActivityOptions.ActivityTransitionListener();
private void setEpicenter(View view) {
if (view == null) {
mEpicenterCallback.setEpicenter(null);
} else {
mListener = listener;
int[] loc = new int[2];
view.getLocationOnScreen(loc);
int left = loc[0] + Math.round(view.getTranslationX());
int top = loc[1] + Math.round(view.getTranslationY());
int right = left + view.getWidth();
int bottom = top + view.getHeight();
Rect epicenter = new Rect(left, top, right, bottom);
mEpicenterCallback.setEpicenter(epicenter);
}
}
// private methods
private Transition configureTransition(Transition transition) {
if (transition != null) {
transition = transition.clone();
transition.setEpicenterCallback(mEpicenterCallback);
}
return transition;
public ArrayList<String> getAcceptedNames() {
return mSharedElementNames;
}
private void reconcileSharedElements(ArrayList<String> sharedElementNames) {
// keep only those that are in sharedElementNames.
int numSharedElements = sharedElementNames.size();
int targetIndex = 0;
for (int i = 0; i < numSharedElements; i++) {
String name = sharedElementNames.get(i);
int index = mTargetSharedNames.indexOf(name);
if (index >= 0) {
// Swap the items at the indexes if necessary.
if (index != targetIndex) {
View temp = mSharedElements.get(index);
mSharedElements.set(index, mSharedElements.get(targetIndex));
mSharedElements.set(targetIndex, temp);
mTargetSharedNames.set(index, mTargetSharedNames.get(targetIndex));
mTargetSharedNames.set(targetIndex, name);
}
targetIndex++;
}
public ArrayList<String> getMappedNames() {
ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
for (int i = 0; i < mSharedElements.size(); i++) {
names.add(mSharedElements.get(i).getViewName());
}
for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) {
mSharedElements.remove(i);
mTargetSharedNames.remove(i);
}
Rect epicenter = null;
if (!mTargetSharedNames.isEmpty()
&& mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) {
epicenter = calcEpicenter(mSharedElements.get(0));
}
mEpicenterCallback.setEpicenter(epicenter);
}
private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
Bundle sharedElementState, final ArrayList<View> acceptedOverlayViews) {
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
final int[] tempLoc = new int[2];
if (sharedElementState != null) {
for (int i = 0; i < mSharedElements.size(); i++) {
View sharedElement = mSharedElements.get(i);
String name = mTargetSharedNames.get(i);
Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
name, sharedElementState);
if (originalState != null) {
originalImageState.put((ImageView) sharedElement, originalState);
}
View parent = (View) sharedElement.getParent();
parent.getLocationOnScreen(tempLoc);
setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
sharedElement.requestLayout();
}
}
mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements,
acceptedOverlayViews);
getDecor().getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements,
acceptedOverlayViews);
mSharedElementTransitionStarted = true;
return true;
}
}
);
return originalImageState;
}
private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
Bundle transitionArgs) {
if (!(view instanceof ImageView)) {
return null;
}
Bundle bundle = transitionArgs.getBundle(name);
int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
if (scaleTypeInt < 0) {
return null;
}
ImageView imageView = (ImageView) view;
ImageView.ScaleType originalScaleType = imageView.getScaleType();
Matrix originalMatrix = null;
if (originalScaleType == ImageView.ScaleType.MATRIX) {
originalMatrix = new Matrix(imageView.getImageMatrix());
}
return Pair.create(originalScaleType, originalMatrix);
}
/**
* Sets the captured values from a previous
* {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
* @param view The View to apply placement changes to.
* @param name The shared element name given from the source Activity.
* @param transitionArgs A <code>Bundle</code> containing all placementinformation for named
* shared elements in the scene.
* @param parentLoc The x and y coordinates of the parent's screen position.
*/
private static void setSharedElementState(View view, String name, Bundle transitionArgs,
int[] parentLoc) {
Bundle sharedElementBundle = transitionArgs.getBundle(name);
if (sharedElementBundle == null) {
return;
}
if (view instanceof ImageView) {
int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
if (scaleTypeInt >= 0) {
ImageView imageView = (ImageView) view;
ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
imageView.setScaleType(scaleType);
if (scaleType == ImageView.ScaleType.MATRIX) {
float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
Matrix matrix = new Matrix();
matrix.setValues(matrixValues);
imageView.setImageMatrix(matrix);
}
}
}
float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
view.setTranslationZ(z);
int x = sharedElementBundle.getInt(KEY_SCREEN_X);
int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
int width = sharedElementBundle.getInt(KEY_WIDTH);
int height = sharedElementBundle.getInt(KEY_HEIGHT);
int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.measure(widthSpec, heightSpec);
int left = x - parentLoc[0];
int top = y - parentLoc[1];
int right = left + width;
int bottom = top + height;
view.layout(left, top, right, bottom);
}
/**
* Captures placement information for Views with a shared element name for
* Activity Transitions.
* @param view The View to capture the placement information for.
* @param name The shared element name in the target Activity to apply the placement
* information for.
* @param transitionArgs Bundle to store shared element placement information.
* @param tempLoc A temporary int[2] for capturing the current location of views.
* @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[])
*/
private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
int[] tempLoc) {
Bundle sharedElementBundle = new Bundle();
view.getLocationOnScreen(tempLoc);
float scaleX = view.getScaleX();
sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
int width = Math.round(view.getWidth() * scaleX);
sharedElementBundle.putInt(KEY_WIDTH, width);
float scaleY = view.getScaleY();
sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
int height= Math.round(view.getHeight() * scaleY);
sharedElementBundle.putInt(KEY_HEIGHT, height);
sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
sharedElementBundle.putString(KEY_NAME, view.getViewName());
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
if (view instanceof ImageView) {
ImageView imageView = (ImageView) view;
int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
float[] matrix = new float[9];
imageView.getImageMatrix().getValues(matrix);
sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
}
}
transitionArgs.putBundle(name, sharedElementBundle);
}
private static Rect calcEpicenter(View view) {
int[] loc = new int[2];
view.getLocationOnScreen(loc);
int left = loc[0] + Math.round(view.getTranslationX());
int top = loc[1] + Math.round(view.getTranslationY());
int right = left + view.getWidth();
int bottom = top + view.getHeight();
return new Rect(left, top, right, bottom);
return names;
}
public static void setViewVisibility(Collection<View> views, int visibility) {
@@ -751,12 +261,12 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
}
}
private static Transition addTransitionTargets(Transition transition, Collection<View> views) {
protected static Transition addTargets(Transition transition, Collection<View> views) {
if (transition == null || views == null || views.isEmpty()) {
return null;
}
TransitionSet set = new TransitionSet();
set.addTransition(transition.clone());
set.addTransition(transition);
if (views != null) {
for (View view: views) {
set.addTarget(view);
@@ -765,152 +275,62 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
return set;
}
private ArrayList<View> captureTransitioningViews() {
if (getViewsTransition() == null) {
return null;
}
ArrayList<View> transitioningViews = new ArrayList<View>();
getDecor().captureTransitioningViews(transitioningViews);
transitioningViews.removeAll(getSharedElements());
return transitioningViews;
}
private Transition getSharedElementTransition(boolean isEnter) {
Transition transition = getSharedElementTransition();
if (transition == null) {
return null;
}
transition = configureTransition(transition);
if (!isEnter) {
transition.addListener(mSharedElementListener);
}
return transition;
}
private Transition getViewsTransition(ArrayList<View> transitioningViews, boolean isEnter) {
Transition transition = getViewsTransition();
if (transition == null) {
return null;
}
transition = configureTransition(transition);
if (!isEnter) {
transition.addListener(mExitListener);
}
return addTransitionTargets(transition, transitioningViews);
}
private Transition beginTransition(ArrayList<View> transitioningViews,
boolean transitionSharedElement, boolean transitionViews, boolean isEnter) {
Transition sharedElementTransition = null;
if (transitionSharedElement) {
sharedElementTransition = getSharedElementTransition(isEnter);
if (!isEnter && sharedElementTransition == null) {
onSharedElementTransitionEnd();
}
}
Transition viewsTransition = null;
if (transitionViews) {
viewsTransition = getViewsTransition(transitioningViews, isEnter);
if (!isEnter && viewsTransition == null) {
onExitTransitionEnd();
}
}
Transition transition = null;
if (sharedElementTransition == null) {
transition = viewsTransition;
} else if (viewsTransition == null) {
transition = sharedElementTransition;
} else {
TransitionSet set = new TransitionSet();
set.addTransition(sharedElementTransition);
set.addTransition(viewsTransition);
transition = set;
}
protected Transition configureTransition(Transition transition) {
if (transition != null) {
TransitionManager.beginDelayedTransition(getDecor(), transition);
if (transitionSharedElement && !mSharedElements.isEmpty()) {
mSharedElements.get(0).invalidate();
} else if (transitionViews && !transitioningViews.isEmpty()) {
transitioningViews.get(0).invalidate();
}
transition = transition.clone();
transition.setEpicenterCallback(mEpicenterCallback);
}
return transition;
}
private void handleRejected(final ArrayList<View> rejected) {
int numRejected = rejected.size();
if (numRejected == 0) {
return;
protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
if (transition1 == null) {
return transition2;
} else if (transition2 == null) {
return transition1;
} else {
TransitionSet transitionSet = new TransitionSet();
transitionSet.addTransition(transition1);
transitionSet.addTransition(transition2);
return transitionSet;
}
boolean rejectionHandled = mListener.handleRejectedSharedElements(rejected);
if (rejectionHandled) {
return;
}
ViewGroupOverlay overlay = getDecor().getOverlay();
ObjectAnimator animator = null;
for (int i = 0; i < numRejected; i++) {
View view = rejected.get(i);
overlay.add(view);
animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
animator.start();
}
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewGroupOverlay overlay = getDecor().getOverlay();
for (int i = rejected.size() - 1; i >= 0; i--) {
overlay.remove(rejected.get(i));
}
}
});
}
private void createSharedElementImages(ArrayList<View> accepted, ArrayList<View> rejected,
ArrayList<String> sharedElementNames, Bundle state) {
int numSharedElements = sharedElementNames.size();
Context context = getWindow().getContext();
int[] parentLoc = new int[2];
getDecor().getLocationOnScreen(parentLoc);
for (int i = 0; i < numSharedElements; i++) {
String name = sharedElementNames.get(i);
Bundle sharedElementBundle = state.getBundle(name);
if (sharedElementBundle != null) {
Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
ImageView imageView = new ImageView(context);
imageView.setId(com.android.internal.R.id.shared_element);
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setImageBitmap(bitmap);
imageView.setViewName(name);
setSharedElementState(imageView, name, state, parentLoc);
if (mTargetSharedNames.contains(name)) {
accepted.add(imageView);
} else {
rejected.add(imageView);
private void setSharedElements(ArrayList<String> accepted, ArrayList<String> localNames) {
if (!mAllSharedElementNames.isEmpty()) {
ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
getDecor().findNamedViews(sharedElements);
if (accepted != null) {
for (int i = 0; i < localNames.size(); i++) {
String localName = localNames.get(i);
String acceptedName = accepted.get(i);
if (!localName.equals(acceptedName)) {
View view = sharedElements.remove(localName);
if (view != null) {
sharedElements.put(acceptedName, view);
}
}
}
}
sharedElements.retainAll(mAllSharedElementNames);
mListener.remapSharedElements(mAllSharedElementNames, sharedElements);
sharedElements.retainAll(mAllSharedElementNames);
for (int i = 0; i < mAllSharedElementNames.size(); i++) {
String name = mAllSharedElementNames.get(i);
View sharedElement = sharedElements.get(name);
if (sharedElement != null) {
mSharedElementNames.add(name);
mSharedElements.add(sharedElement);
}
}
}
}
private static void setOriginalImageViewState(
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
for (int i = 0; i < originalState.size(); i++) {
ImageView imageView = originalState.keyAt(i);
Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
imageView.setScaleType(state.first);
imageView.setImageMatrix(state.second);
}
protected void setResultReceiver(ResultReceiver resultReceiver) {
mResultReceiver = resultReceiver;
}
private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
if (scaleType == SCALE_TYPE_VALUES[i]) {
return i;
}
}
return -1;
}
protected abstract Transition getViewsTransition();
private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
private Rect mEpicenter;

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.ArrayMap;
import android.view.View;
import android.view.Window;
import java.util.ArrayList;
/**
* This class contains all persistence-related functionality for Activity Transitions.
* Activities start exit and enter Activity Transitions through this class.
*/
class ActivityTransitionState {
private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
private static final String ENTERING_MAPPED_FROM = "android:enteringMappedFrom";
private static final String ENTERING_MAPPED_TO = "android:enteringMappedTo";
private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
/**
* The shared elements that the calling Activity has said that they transferred to this
* Activity.
*/
private ArrayList<String> mEnteringNames;
/**
* The shared elements that this Activity as accepted and mapped to local Views.
*/
private ArrayList<String> mEnteringFrom;
/**
* The names of local Views that are mapped to those elements in mEnteringFrom.
*/
private ArrayList<String> mEnteringTo;
/**
* The names of shared elements that were shared to the called Activity.
*/
private ArrayList<String> mExitingFrom;
/**
* The names of local Views that were shared out, mapped to those elements in mExitingFrom.
*/
private ArrayList<String> mExitingTo;
/**
* The ActivityOptions used to call an Activity. Used to make the elements restore
* Visibility of exited Views.
*/
private ActivityOptions mCalledActivityOptions;
/**
* We must be able to cancel entering transitions to stop changing the Window to
* opaque when we exit before making the Window opaque.
*/
private EnterTransitionCoordinator mEnterTransitionCoordinator;
/**
* ActivityOptions used on entering this Activity.
*/
private ActivityOptions mEnterActivityOptions;
/**
* Has an exit transition been started? If so, we don't want to double-exit.
*/
private boolean mHasExited;
public ActivityTransitionState() {
}
public void readState(Bundle bundle) {
if (bundle != null) {
if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
mEnteringFrom = bundle.getStringArrayList(ENTERING_MAPPED_FROM);
mEnteringTo = bundle.getStringArrayList(ENTERING_MAPPED_TO);
}
if (mEnterTransitionCoordinator == null) {
mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
}
}
}
public void saveState(Bundle bundle) {
if (mEnteringNames != null) {
bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
bundle.putStringArrayList(ENTERING_MAPPED_FROM, mEnteringFrom);
bundle.putStringArrayList(ENTERING_MAPPED_TO, mEnteringTo);
}
if (mExitingFrom != null) {
bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
}
}
public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)
&& options != null && mEnterActivityOptions == null
&& options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
mEnterActivityOptions = options;
if (mEnterActivityOptions.isReturning()) {
int result = mEnterActivityOptions.getResultCode();
if (result != 0) {
activity.onActivityReenter(result, mEnterActivityOptions.getResultData());
}
}
}
}
public void enterReady(Activity activity) {
if (mEnterActivityOptions == null) {
return;
}
mHasExited = false;
ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
if (mEnterActivityOptions.isReturning()) {
if (mCalledActivityOptions != null) {
mCalledActivityOptions.dispatchActivityStopped();
mCalledActivityOptions = null;
}
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
resultReceiver, sharedElementNames, mExitingFrom, mExitingTo);
} else {
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
resultReceiver, sharedElementNames, null, null);
mEnteringNames = sharedElementNames;
mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames();
mEnteringTo = mEnterTransitionCoordinator.getMappedNames();
}
mExitingFrom = null;
mExitingTo = null;
mEnterActivityOptions = null;
}
public void onStop() {
if (mCalledActivityOptions != null) {
mCalledActivityOptions.dispatchActivityStopped();
mCalledActivityOptions = null;
}
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.stop();
mEnterTransitionCoordinator = null;
}
}
public boolean startExitBackTransition(Activity activity) {
if (mEnteringNames == null) {
return false;
} else {
if (!mHasExited) {
mHasExited = true;
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.stop();
mEnterTransitionCoordinator = null;
}
ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
activity.getWindow().getDecorView().findNamedViews(sharedElements);
ExitTransitionCoordinator exitCoordinator =
new ExitTransitionCoordinator(activity, mEnteringNames, mEnteringFrom,
mEnteringTo, true);
exitCoordinator.startExit(activity.mResultCode, activity.mResultData);
}
return true;
}
}
public void startExitOutTransition(Activity activity, Bundle options) {
if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
return;
}
mCalledActivityOptions = new ActivityOptions(options);
if (mCalledActivityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
mExitingFrom = mCalledActivityOptions.getSharedElementNames();
mExitingTo = mCalledActivityOptions.getLocalSharedElementNames();
mCalledActivityOptions.dispatchStartExit();
}
}
}

View File

@@ -18,270 +18,414 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.drawable.ColorDrawable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Collection;
/**
* This ActivityTransitionCoordinator is created by the Activity to manage
* the enter scene and shared element transfer as well as Activity#finishWithTransition
* exiting the Scene and transferring shared elements back to the called Activity.
* the enter scene and shared element transfer into the Scene, either during
* launch of an Activity or returning from a launched Activity.
*/
class EnterTransitionCoordinator extends ActivityTransitionCoordinator
implements ViewTreeObserver.OnPreDrawListener {
class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
private static final String TAG = "EnterTransitionCoordinator";
// The background fade in/out duration. 150ms is pretty quick, but not abrupt.
private static final int FADE_BACKGROUND_DURATION_MS = 150;
private static final long MAX_WAIT_MS = 1500;
/**
* The shared element names sent by the ExitTransitionCoordinator and may be
* shared when exiting back.
*/
private ArrayList<String> mEnteringSharedElementNames;
/**
* The Activity that has created this coordinator. This is used solely to make the
* Window translucent/opaque.
*/
private boolean mSharedElementTransitionStarted;
private boolean mIsReturning;
private Activity mActivity;
private boolean mHasStopped;
private Handler mHandler;
private boolean mIsCanceled;
/**
* True if the Window was opaque at the start and we should make it opaque again after
* enter transitions have completed.
*/
private boolean mWasOpaque;
/**
* During exit, is the background alpha == 0?
*/
private boolean mBackgroundFadedOut;
/**
* During exit, has the shared element transition completed?
*/
private boolean mSharedElementTransitionComplete;
/**
* Has the exit started? We don't want to accidentally exit multiple times. e.g. when
* back is hit twice during the exit animation.
*/
private boolean mExitTransitionStarted;
/**
* Has the exit transition ended?
*/
private boolean mExitTransitionComplete;
/**
* We only want to make the Window transparent and set the background alpha once. After that,
* the Activity won't want the same enter transition.
*/
private boolean mMadeReady;
/**
* True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that
* enter and exit transitions should be active.
*/
private boolean mSupportsTransition;
/**
* Background alpha animations may complete prior to receiving the callback for
* onTranslucentConversionComplete. If so, we need to immediately call to make the Window
* opaque.
*/
private boolean mMakeOpaque;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) {
super(activity.getWindow());
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
ArrayList<String> sharedElementNames,
ArrayList<String> acceptedNames, ArrayList<String> mappedNames) {
super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames,
activity.mTransitionListener);
mActivity = activity;
setRemoteResultReceiver(resultReceiver);
}
public void readyToEnter() {
if (!mMadeReady) {
mMadeReady = true;
mSupportsTransition = getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS);
if (mSupportsTransition) {
Window window = getWindow();
window.getDecorView().getViewTreeObserver().addOnPreDrawListener(this);
mActivity.overridePendingTransition(0, 0);
mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
mWasOpaque = true;
if (mMakeOpaque) {
mActivity.convertFromTranslucent();
}
}
}, null);
Drawable background = getDecor().getBackground();
if (background != null) {
window.setBackgroundDrawable(null);
background.setAlpha(0);
window.setBackgroundDrawable(background);
mIsReturning = acceptedNames != null;
setResultReceiver(resultReceiver);
prepareEnter();
Bundle resultReceiverBundle = new Bundle();
resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
if (mIsReturning) {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
cancel();
}
}
};
mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
}
}
@Override
protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
mEnteringSharedElementNames = new ArrayList<String>();
mEnteringSharedElementNames.addAll(sharedElementNames);
super.onTakeSharedElements(sharedElementNames, state);
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_TAKE_SHARED_ELEMENTS:
if (!mIsCanceled) {
if (mHandler != null) {
mHandler.removeMessages(MSG_CANCEL);
}
onTakeSharedElements(resultData);
}
break;
case MSG_EXIT_TRANSITION_COMPLETE:
if (!mIsCanceled) {
if (!mSharedElementTransitionStarted) {
send(resultCode, resultData);
} else {
onRemoteExitTransitionComplete();
}
}
break;
case MSG_CANCEL:
cancel();
break;
}
}
@Override
protected void sharedElementTransitionComplete(Bundle bundle) {
notifySharedElementTransitionComplete(bundle);
exitAfterSharedElementTransition();
private void cancel() {
if (!mIsCanceled) {
mIsCanceled = true;
if (getViewsTransition() == null) {
setViewVisibility(mSharedElements, View.VISIBLE);
} else {
mTransitioningViews.addAll(mSharedElements);
}
mSharedElementNames.clear();
mSharedElements.clear();
mAllSharedElementNames.clear();
onTakeSharedElements(null);
onRemoteExitTransitionComplete();
}
}
@Override
public boolean onPreDraw() {
getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
setEnteringViews(readyEnteringViews());
notifySetListener();
onPrepareRestore();
return false;
public boolean isReturning() {
return mIsReturning;
}
@Override
public void startExit() {
if (!mExitTransitionStarted) {
mExitTransitionStarted = true;
startExitTransition(mEnteringSharedElementNames);
protected void prepareEnter() {
setViewVisibility(mSharedElements, View.INVISIBLE);
if (getViewsTransition() != null) {
setViewVisibility(mTransitioningViews, View.INVISIBLE);
}
mActivity.overridePendingTransition(0, 0);
if (!mIsReturning) {
mActivity.convertToTranslucent(null, null);
Drawable background = getDecor().getBackground();
if (background != null) {
getWindow().setBackgroundDrawable(null);
background = background.mutate();
background.setAlpha(0);
getWindow().setBackgroundDrawable(background);
}
} else {
mActivity = null; // all done with it now.
}
}
protected void onTakeSharedElements(Bundle sharedElementState) {
setEpicenter();
// Remove rejected shared elements
ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
rejectedNames.removeAll(mSharedElementNames);
ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
mListener.handleRejectedSharedElements(rejectedSnapshots);
startRejectedAnimations(rejectedSnapshots);
// Now start shared element transition
ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
mSharedElementNames);
setViewVisibility(mSharedElements, View.VISIBLE);
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
setSharedElementState(sharedElementState, sharedElementSnapshots);
boolean startEnterTransition = allowOverlappingTransitions();
boolean startSharedElementTransition = true;
Transition transition = beginTransition(startEnterTransition, startSharedElementTransition);
if (startEnterTransition) {
startEnterTransition(transition);
}
setOriginalImageViewState(originalImageViewState);
if (mResultReceiver != null) {
mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
}
mResultReceiver = null; // all done sending messages.
}
private Transition beginTransition(boolean startEnterTransition,
boolean startSharedElementTransition) {
Transition sharedElementTransition = null;
if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
sharedElementTransition = configureTransition(getSharedElementTransition());
}
Transition viewsTransition = null;
if (startEnterTransition && !mTransitioningViews.isEmpty()) {
viewsTransition = configureTransition(getViewsTransition());
viewsTransition = addTargets(viewsTransition, mTransitioningViews);
}
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
if (transition != null) {
TransitionManager.beginDelayedTransition(getDecor(), transition);
if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
mSharedElements.get(0).invalidate();
} else if (startEnterTransition && !mTransitioningViews.isEmpty()) {
mTransitioningViews.get(0).invalidate();
}
}
return transition;
}
private void startEnterTransition(Transition transition) {
setViewVisibility(mTransitioningViews, View.VISIBLE);
if (!mIsReturning) {
Drawable background = getDecor().getBackground();
if (background != null) {
background = background.mutate();
ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
animator.setDuration(FADE_BACKGROUND_DURATION_MS);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
makeOpaque();
}
});
animator.start();
} else if (transition != null) {
transition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
makeOpaque();
}
});
} else {
makeOpaque();
}
}
}
public void stop() {
mHasStopped = true;
mActivity = null;
mIsCanceled = true;
mResultReceiver = null;
}
private void makeOpaque() {
if (!mHasStopped) {
mActivity.convertFromTranslucent();
mActivity = null;
}
}
private boolean allowOverlappingTransitions() {
return mIsReturning ? getWindow().getAllowExitTransitionOverlap()
: getWindow().getAllowEnterTransitionOverlap();
}
private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
return;
}
ViewGroupOverlay overlay = getDecor().getOverlay();
ObjectAnimator animator = null;
int numRejected = rejectedSnapshots.size();
for (int i = 0; i < numRejected; i++) {
View snapshot = rejectedSnapshots.get(i);
overlay.add(snapshot);
animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
animator.start();
}
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewGroupOverlay overlay = getDecor().getOverlay();
int numRejected = rejectedSnapshots.size();
for (int i = 0; i < numRejected; i++) {
overlay.remove(rejectedSnapshots.get(i));
}
}
});
}
protected void onRemoteExitTransitionComplete() {
if (!allowOverlappingTransitions()) {
boolean startEnterTransition = true;
boolean startSharedElementTransition = false;
Transition transition = beginTransition(startEnterTransition,
startSharedElementTransition);
startEnterTransition(transition);
}
}
private ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
int numSharedElements = names.size();
if (numSharedElements == 0) {
return null;
}
ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
Context context = getWindow().getContext();
int[] parentLoc = new int[2];
getDecor().getLocationOnScreen(parentLoc);
for (String name: names) {
Bundle sharedElementBundle = state.getBundle(name);
if (sharedElementBundle != null) {
Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
View snapshot = new View(context);
snapshot.setId(com.android.internal.R.id.shared_element);
Resources resources = getWindow().getContext().getResources();
snapshot.setBackground(new BitmapDrawable(resources, bitmap));
snapshot.setViewName(name);
setSharedElementState(snapshot, name, state, parentLoc);
snapshots.add(snapshot);
}
}
return snapshots;
}
private static void setSharedElementState(View view, String name, Bundle transitionArgs,
int[] parentLoc) {
Bundle sharedElementBundle = transitionArgs.getBundle(name);
if (sharedElementBundle == null) {
return;
}
if (view instanceof ImageView) {
int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
if (scaleTypeInt >= 0) {
ImageView imageView = (ImageView) view;
ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
imageView.setScaleType(scaleType);
if (scaleType == ImageView.ScaleType.MATRIX) {
float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
Matrix matrix = new Matrix();
matrix.setValues(matrixValues);
imageView.setImageMatrix(matrix);
}
}
}
float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
view.setTranslationZ(z);
int x = sharedElementBundle.getInt(KEY_SCREEN_X);
int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
int width = sharedElementBundle.getInt(KEY_WIDTH);
int height = sharedElementBundle.getInt(KEY_HEIGHT);
int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.measure(widthSpec, heightSpec);
int left = x - parentLoc[0];
int top = y - parentLoc[1];
int right = left + width;
int bottom = top + height;
view.layout(left, top, right, bottom);
}
private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
Bundle sharedElementState, final ArrayList<View> snapshots) {
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
if (sharedElementState != null) {
int[] tempLoc = new int[2];
for (int i = 0; i < mSharedElementNames.size(); i++) {
View sharedElement = mSharedElements.get(i);
String name = mSharedElementNames.get(i);
Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
name, sharedElementState);
if (originalState != null) {
originalImageState.put((ImageView) sharedElement, originalState);
}
View parent = (View) sharedElement.getParent();
parent.getLocationOnScreen(tempLoc);
setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
sharedElement.requestLayout();
}
}
mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
getDecor().getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
mListener.setSharedElementEnd(mSharedElementNames, mSharedElements,
snapshots);
mSharedElementTransitionStarted = true;
return true;
}
}
);
return originalImageState;
}
private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
Bundle transitionArgs) {
if (!(view instanceof ImageView)) {
return null;
}
Bundle bundle = transitionArgs.getBundle(name);
int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
if (scaleTypeInt < 0) {
return null;
}
ImageView imageView = (ImageView) view;
ImageView.ScaleType originalScaleType = imageView.getScaleType();
Matrix originalMatrix = null;
if (originalScaleType == ImageView.ScaleType.MATRIX) {
originalMatrix = new Matrix(imageView.getImageMatrix());
}
return Pair.create(originalScaleType, originalMatrix);
}
private static void setOriginalImageViewState(
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
for (int i = 0; i < originalState.size(); i++) {
ImageView imageView = originalState.keyAt(i);
Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
imageView.setScaleType(state.first);
imageView.setImageMatrix(state.second);
}
}
@Override
protected Transition getViewsTransition() {
if (!mSupportsTransition) {
return null;
}
return getWindow().getEnterTransition();
}
@Override
protected Transition getSharedElementTransition() {
if (!mSupportsTransition) {
return null;
}
return getWindow().getSharedElementEnterTransition();
}
@Override
protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
Drawable background = getDecor().getBackground();
if (background != null) {
ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
animator.setDuration(FADE_BACKGROUND_DURATION_MS);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mMakeOpaque = true;
if (mWasOpaque) {
mActivity.convertFromTranslucent();
}
}
});
animator.start();
} else if (mWasOpaque) {
transition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
mMakeOpaque = true;
mActivity.convertFromTranslucent();
}
});
}
super.onStartEnterTransition(transition, enteringViews);
}
public ArrayList<View> readyEnteringViews() {
ArrayList<View> enteringViews = new ArrayList<View>();
getDecor().captureTransitioningViews(enteringViews);
if (getViewsTransition() != null) {
setViewVisibility(enteringViews, View.INVISIBLE);
}
return enteringViews;
}
@Override
protected void startExitTransition(ArrayList<String> sharedElements) {
mMakeOpaque = false;
notifyPrepareRestore();
if (getDecor().getBackground() == null) {
ColorDrawable black = new ColorDrawable(0xFF000000);
getWindow().setBackgroundDrawable(black);
}
if (mWasOpaque) {
mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
fadeOutBackground();
}
}, null);
} else {
fadeOutBackground();
}
super.startExitTransition(sharedElements);
}
private void fadeOutBackground() {
ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(),
"alpha", 0);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBackgroundFadedOut = true;
if (mSharedElementTransitionComplete) {
EnterTransitionCoordinator.super.onSharedElementTransitionEnd();
}
}
});
animator.setDuration(FADE_BACKGROUND_DURATION_MS);
animator.start();
}
@Override
protected void onExitTransitionEnd() {
mExitTransitionComplete = true;
exitAfterSharedElementTransition();
super.onExitTransitionEnd();
}
@Override
protected void onSharedElementTransitionEnd() {
mSharedElementTransitionComplete = true;
if (mBackgroundFadedOut) {
super.onSharedElementTransitionEnd();
}
}
@Override
protected boolean allowOverlappingTransitions() {
return getWindow().getAllowEnterTransitionOverlap();
}
private void exitAfterSharedElementTransition() {
if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) {
mActivity.finish();
if (mSupportsTransition) {
mActivity.overridePendingTransition(0, 0);
}
notifyExitTransitionComplete();
clearConnections();
}
}
}

View File

@@ -15,11 +15,20 @@
*/
package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.transition.Transition;
import android.util.Pair;
import android.transition.TransitionManager;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import java.util.ArrayList;
@@ -30,142 +39,245 @@ import java.util.ArrayList;
*/
class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
private static final String TAG = "ExitTransitionCoordinator";
private static final long MAX_WAIT_MS = 1500;
/**
* The Views that have exited and need to be restored to VISIBLE when returning to the
* normal state.
*/
private ArrayList<View> mTransitioningViews;
/**
* Has the exit started? We don't want to accidentally exit multiple times.
*/
private boolean mExitStarted;
/**
* Has the called Activity's ResultReceiver been set?
*/
private boolean mIsResultReceiverSet;
/**
* Has the exit transition completed? If so, we can notify as soon as the ResultReceiver
* has been set.
*/
private boolean mExitComplete;
/**
* Has the shared element transition completed? If so, we can notify as soon as the
* ResultReceiver has been set.
*/
private Bundle mSharedElements;
private Bundle mSharedElementBundle;
/**
* Has the shared element transition completed?
*/
private boolean mSharedElementsComplete;
private boolean mExitNotified;
public ExitTransitionCoordinator(Window window,
ActivityOptions.ActivityTransitionListener listener) {
super(window);
setActivityTransitionListener(listener);
private boolean mSharedElementNotified;
private Activity mActivity;
private boolean mIsBackgroundReady;
private boolean mIsReturning;
private boolean mIsCanceled;
private Handler mHandler;
public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
super(activity.getWindow(), names, accepted, mapped, activity.mTransitionListener);
mIsReturning = isReturning;
mIsBackgroundReady = !mIsReturning;
mActivity = activity;
}
@Override
protected void onSetResultReceiver() {
mIsResultReceiverSet = true;
notifyCompletions();
}
@Override
protected void onPrepareRestore() {
makeTransitioningViewsInvisible();
setEnteringViews(mTransitioningViews);
mTransitioningViews = null;
super.onPrepareRestore();
}
@Override
protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
super.onTakeSharedElements(sharedElementNames, state);
clearConnections();
}
@Override
protected void onActivityStopped() {
if (getViewsTransition() != null) {
setViewVisibility(mTransitioningViews, View.VISIBLE);
}
super.onActivityStopped();
}
@Override
protected void sharedElementTransitionComplete(Bundle bundle) {
mSharedElements = bundle;
mSharedElementsComplete = true;
notifyCompletions();
}
@Override
protected void onExitTransitionEnd() {
mExitComplete = true;
notifyCompletions();
super.onExitTransitionEnd();
}
private void notifyCompletions() {
if (mIsResultReceiverSet && mSharedElementsComplete) {
if (mSharedElements != null) {
notifySharedElementTransitionComplete(mSharedElements);
mSharedElements = null;
}
if (mExitComplete) {
notifyExitTransitionComplete();
}
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_SET_REMOTE_RECEIVER:
mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
if (mIsCanceled) {
mResultReceiver.send(MSG_CANCEL, null);
mResultReceiver = null;
} else {
if (mHandler != null) {
mHandler.removeMessages(MSG_CANCEL);
}
notifyComplete();
}
break;
case MSG_HIDE_SHARED_ELEMENTS:
if (!mIsCanceled) {
hideSharedElements();
}
break;
case MSG_START_EXIT_TRANSITION:
startExit();
break;
case MSG_ACTIVITY_STOPPED:
setViewVisibility(mTransitioningViews, View.VISIBLE);
setViewVisibility(mSharedElements, View.VISIBLE);
break;
}
}
@Override
private void hideSharedElements() {
setViewVisibility(mSharedElements, View.INVISIBLE);
}
public void startExit() {
if (!mExitStarted) {
mExitStarted = true;
setSharedElements();
startExitTransition(getSharedElementNames());
beginTransition();
setViewVisibility(mTransitioningViews, View.INVISIBLE);
}
public void startExit(int resultCode, Intent data) {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mIsCanceled = true;
mActivity.finish();
mActivity = null;
}
};
mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
if (getDecor().getBackground() == null) {
ColorDrawable black = new ColorDrawable(0xFF000000);
black.setAlpha(0);
getWindow().setBackgroundDrawable(black);
black.setAlpha(255);
}
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
mAllSharedElementNames, resultCode, data);
mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
if (!mIsCanceled) {
fadeOutBackground();
}
}
}, options);
startExit();
}
private void fadeOutBackground() {
ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(),
"alpha", 0);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIsBackgroundReady = true;
notifyComplete();
}
});
animator.setDuration(FADE_BACKGROUND_DURATION_MS);
animator.start();
}
private void beginTransition() {
Transition sharedElementTransition = configureTransition(getSharedElementTransition());
Transition viewsTransition = configureTransition(getViewsTransition());
viewsTransition = addTargets(viewsTransition, mTransitioningViews);
if (sharedElementTransition == null) {
sharedElementTransitionComplete();
} else {
sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
sharedElementTransitionComplete();
}
});
}
if (viewsTransition == null) {
exitTransitionComplete();
} else {
viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
exitTransitionComplete();
}
});
}
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
TransitionManager.beginDelayedTransition(getDecor(), transition);
}
private void exitTransitionComplete() {
mExitComplete = true;
notifyComplete();
}
protected boolean isReadyToNotify() {
return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
}
private void sharedElementTransitionComplete() {
Bundle bundle = new Bundle();
int[] tempLoc = new int[2];
for (int i = 0; i < mSharedElementNames.size(); i++) {
View sharedElement = mSharedElements.get(i);
String name = mSharedElementNames.get(i);
captureSharedElementState(sharedElement, name, bundle, tempLoc);
}
mSharedElementBundle = bundle;
notifyComplete();
}
protected void notifyComplete() {
if (isReadyToNotify()) {
if (!mSharedElementNotified) {
mSharedElementNotified = true;
mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
}
if (!mExitNotified && mExitComplete) {
mExitNotified = true;
mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
mResultReceiver = null; // done talking
if (mIsReturning) {
mActivity.finish();
mActivity.overridePendingTransition(0, 0);
}
mActivity = null;
}
}
}
/**
* Captures placement information for Views with a shared element name for
* Activity Transitions.
*
* @param view The View to capture the placement information for.
* @param name The shared element name in the target Activity to apply the placement
* information for.
* @param transitionArgs Bundle to store shared element placement information.
* @param tempLoc A temporary int[2] for capturing the current location of views.
*/
private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
int[] tempLoc) {
Bundle sharedElementBundle = new Bundle();
view.getLocationOnScreen(tempLoc);
float scaleX = view.getScaleX();
sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
int width = Math.round(view.getWidth() * scaleX);
sharedElementBundle.putInt(KEY_WIDTH, width);
float scaleY = view.getScaleY();
sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
int height = Math.round(view.getHeight() * scaleY);
sharedElementBundle.putInt(KEY_HEIGHT, height);
sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
if (view instanceof ImageView) {
ImageView imageView = (ImageView) view;
int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
float[] matrix = new float[9];
imageView.getImageMatrix().getValues(matrix);
sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
}
}
transitionArgs.putBundle(name, sharedElementBundle);
}
private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
if (scaleType == SCALE_TYPE_VALUES[i]) {
return i;
}
}
return -1;
}
@Override
protected Transition getViewsTransition() {
if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
return null;
}
return getWindow().getExitTransition();
}
@Override
protected Transition getSharedElementTransition() {
if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
return null;
}
return getWindow().getSharedElementExitTransition();
}
private void makeTransitioningViewsInvisible() {
if (getViewsTransition() != null) {
setViewVisibility(mTransitioningViews, View.INVISIBLE);
}
}
@Override
protected void onStartExitTransition(ArrayList<View> exitingViews) {
mTransitioningViews = new ArrayList<View>();
if (exitingViews != null) {
mTransitioningViews.addAll(exitingViews);
}
mTransitioningViews.addAll(getSharedElements());
}
@Override
protected boolean allowOverlappingTransitions() {
return getWindow().getAllowExitTransitionOverlap();
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.view.View;
import java.util.List;
import java.util.Map;
/**
* Listener provided in
* {@link Activity#setSharedElementListener(SharedElementListener)}
* to monitor the Activity transitions. The events can be used to customize or override Activity
* Transition behavior.
*/
public class SharedElementListener {
/**
* Called to allow the listener to customize the start state of the shared element for
* the shared element entering transition. By default, the shared element is placed in
* the position and with the size of the shared element in the calling Activity or Fragment.
*
* @param sharedElementNames The names of the shared elements that were accepted into
* the View hierarchy.
* @param sharedElements The shared elements that are part of the View hierarchy.
* @param sharedElementSnapshots The Views containing snap shots of the shared element
* from the launching Window. These elements will not
* be part of the scene, but will be positioned relative
* to the Window decor View.
*/
public void setSharedElementStart(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {}
/**
* Called to allow the listener to customize the end state of the shared element for
* the shared element entering transition.
*
* @param sharedElementNames The names of the shared elements that were accepted into
* the View hierarchy.
* @param sharedElements The shared elements that are part of the View hierarchy.
* @param sharedElementSnapshots The Views containing snap shots of the shared element
* from the launching Window. These elements will not
* be part of the scene, but will be positioned relative
* to the Window decor View.
*/
public void setSharedElementEnd(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {}
/**
* If nothing is done, all shared elements that were not accepted by
* {@link #remapSharedElements(java.util.List, java.util.Map)} will be Transitioned
* out of the entering scene automatically. Any elements removed from
* rejectedSharedElements must be handled by the ActivityTransitionListener.
* <p>Views in rejectedSharedElements will have their position and size set to the
* position of the calling shared element, relative to the Window decor View. This
* view may be safely added to the decor View's overlay to remain in position.</p>
*
* @param rejectedSharedElements Views containing visual information of shared elements
* that are not part of the entering scene. These Views
* are positioned relative to the Window decor View. A
* View removed from this list will not be transitioned
* automatically.
*/
public void handleRejectedSharedElements(List<View> rejectedSharedElements) {}
/**
* Lets the ActivityTransitionListener adjust the mapping of shared element names to
* Views.
* @param names The names of all shared elements transferred from the calling Activity
* to the started Activity.
* @param sharedElements The mapping of shared element names to Views. The best guess
* will be filled into sharedElements based on the View names.
*/
public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {}
}

View File

@@ -2318,8 +2318,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* in Activity transitions. If false, the ViewGroup won't transition,
* only its children. If true, the entire ViewGroup will transition
* together.
* @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
* android.app.ActivityOptions.ActivityTransitionListener)
* @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
* android.util.Pair[])
*/
public void setTransitionGroup(boolean isTransitionGroup) {
mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;

View File

@@ -2477,7 +2477,7 @@
when doing an Activity transition. Typically, the elements inside a
ViewGroup are each transitioned from the scene individually. The default
for a ViewGroup is false unless it has a background. See
{@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
{@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
android.view.View, String)} for more information. Corresponds to
{@link android.view.ViewGroup#setTransitionGroup(boolean)}.-->
<attr name="transitionGroup" format="boolean" />