Add support for cross-activity scenes and transitions

* Add theme attributes for specifying a top-level TransitionManager
  for an activity window.

* Add window feature for automatic content transitions. This
  automatically assigns/creates a Scene for setContentView calls.

* Add named transitions. This allows apps to define APIs for
  handshake-agreements about which exit/entrance transitions to play.

* Add new transition type for ActivityOptions. This lets the system
  use ActivityOptions to communicate transition specifics and
  arguments to the called activity.

* Have ActivityManager pass appropriate ActivityOptions through to the
  called Activity. Have the called activity call back into the caller
  to let it know which transition of a possible requested set was
  chosen.

Still to do:

* Define and pass arguments for transitions. This will require
  defining a Parcelable version of TransitionValues and deciding how
  much leeway apps should have for these things.

* Determine how to appropriately filter the ActivityOptions bundle so
  that only appropriate data reaches the target.

* Determine if generalizing the auto-Scenes functionality to
  ViewGroups is appropriate.

Change-Id: I10684b926129ab2fbc1adec9ef31767237acae79
This commit is contained in:
Adam Powell
2013-11-06 14:58:58 -08:00
parent f254ed9a98
commit cfbe9be5b3
13 changed files with 462 additions and 43 deletions

View File

@@ -17,6 +17,9 @@
package android.app;
import android.annotation.NonNull;
import android.transition.Scene;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
import com.android.internal.app.ActionBarImpl;
@@ -1989,6 +1992,41 @@ public class Activity extends ContextThemeWrapper
initActionBar();
}
/**
* Retrieve the {@link TransitionManager} responsible for default transitions in this window.
* Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
*
* <p>This method will return non-null after content has been initialized (e.g. by using
* {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
*
* @return This window's content TransitionManager or null if none is set.
*/
public TransitionManager getContentTransitionManager() {
return getWindow().getTransitionManager();
}
/**
* Set the {@link TransitionManager} to use for default transitions in this window.
* Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
*
* @param tm The TransitionManager to use for scene changes.
*/
public void setContentTransitionManager(TransitionManager tm) {
getWindow().setTransitionManager(tm);
}
/**
* Retrieve the {@link Scene} representing this window's current content.
* Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
*
* <p>This method will return null if the current content is not represented by a Scene.</p>
*
* @return Current Scene being shown or null
*/
public Scene getContentScene() {
return getWindow().getContentScene();
}
/**
* Sets whether this activity is finished when touched outside its window's
* bounds.
@@ -3408,7 +3446,29 @@ public class Activity extends ContextThemeWrapper
* @see #startActivity
*/
public void startActivityForResult(Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
final TransitionManager tm = getWindow().getTransitionManager();
final Scene currScene = getWindow().getContentScene();
final String[] targetSceneNames = currScene != null && tm != null ?
tm.getTargetSceneNames(currScene) : null;
if (targetSceneNames == null || targetSceneNames.length == 0) {
startActivityForResult(intent, requestCode, null);
} else {
// TODO Capture the scene transition args and send along
final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
targetSceneNames, null,
new ActivityOptions.OnSceneTransitionStartedListener() {
@Override public void onSceneTransitionStarted(String destSceneName) {
final Transition t = tm.getNamedTransition(currScene, destSceneName);
// TODO Fill this in to notify the outgoing activity that it should
// treat this as a sync point for the transition - the target
// transition has started.
Log.d(TAG, "Scene transition to scene " + destSceneName +
" transition " + t);
}
}, mHandler);
startActivityForResult(intent, requestCode, opts.toBundle());
}
}
/**
@@ -5229,6 +5289,16 @@ public class Activity extends ContextThemeWrapper
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
lastNonConfigurationInstances, config, null);
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, Bundle options) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5265,6 +5335,7 @@ public class Activity extends ContextThemeWrapper
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mWindow.setTransitionOptions(options);
mCurrentConfig = config;
}

View File

@@ -22,6 +22,8 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
/**
@@ -30,6 +32,8 @@ import android.view.View;
* Context.startActivity(Intent, Bundle)} and related methods.
*/
public class ActivityOptions {
private static final String TAG = "ActivityOptions";
/**
* The package name that created the options.
* @hide
@@ -481,8 +485,19 @@ public class ActivityOptions {
}
/** @hide */
public IRemoteCallback getOnSceneTransitionStartedListener() {
return mSceneTransitionStartedListener;
public void dispatchSceneTransitionStarted(String destScene) {
if (mSceneTransitionStartedListener != null) {
Bundle data = null;
if (!TextUtils.isEmpty(destScene)) {
data = new Bundle();
data.putString(KEY_DEST_SCENE_NAME_CHOSEN, destScene);
}
try {
mSceneTransitionStartedListener.sendResult(data);
} catch (RemoteException e) {
Log.e(TAG, "Caught exception dispatching scene transition start", e);
}
}
}
/** @hide */
@@ -493,6 +508,12 @@ public class ActivityOptions {
} catch (RemoteException e) {
}
}
if (mSceneTransitionStartedListener != null) {
try {
mSceneTransitionStartedListener.sendResult(null);
} catch (RemoteException e) {
}
}
}
/** @hide */
@@ -572,6 +593,8 @@ public class ActivityOptions {
}
mSceneTransitionStartedListener = otherOptions.mSceneTransitionStartedListener;
mDestSceneNames = otherOptions.mDestSceneNames;
mTransitionArgs = otherOptions.mTransitionArgs;
mThumbnail = null;
mAnimationStartedListener = null;
break;
}
@@ -595,7 +618,7 @@ public class ActivityOptions {
b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCALE_UP:
@@ -611,10 +634,31 @@ public class ActivityOptions {
b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCENE_TRANSITION:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putStringArray(KEY_DEST_SCENE_NAMES, mDestSceneNames);
b.putBundle(KEY_SCENE_TRANSITION_ARGS, mTransitionArgs);
b.putBinder(KEY_SCENE_TRANSITION_START_LISTENER, mSceneTransitionStartedListener
!= null ? mSceneTransitionStartedListener.asBinder() : null);
break;
}
return b;
}
/**
* Return the filtered options only meant to be seen by the target activity itself
* @hide
*/
public ActivityOptions forTargetActivity() {
if (mAnimationType == ANIM_SCENE_TRANSITION) {
final ActivityOptions result = new ActivityOptions();
result.update(this);
return result;
}
return null;
}
}

View File

@@ -56,6 +56,7 @@ import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
@@ -68,12 +69,15 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.transition.Scene;
import android.transition.TransitionManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SuperNotCalledException;
@@ -284,6 +288,7 @@ public final class ActivityThread {
boolean isForward;
int pendingConfigChanges;
boolean onlyLocalRequest;
Bundle activityOptions;
View mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -576,9 +581,10 @@ public final class ActivityThread {
}
public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward) {
boolean isForward, Bundle resumeArgs) {
updateProcessState(processState, false);
sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
sendMessage(H.RESUME_ACTIVITY, new Pair<IBinder, Bundle>(token, resumeArgs),
isForward ? 1 : 0);
}
public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
@@ -594,7 +600,8 @@ public final class ActivityThread {
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle resumeArgs) {
updateProcessState(procState, false);
@@ -616,6 +623,7 @@ public final class ActivityThread {
r.profileFile = profileName;
r.profileFd = profileFd;
r.autoStopProfiler = autoStopProfiler;
r.activityOptions = resumeArgs;
updatePendingConfiguration(curConfig);
@@ -1189,7 +1197,7 @@ public final class ActivityThread {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
@@ -1235,7 +1243,8 @@ public final class ActivityThread {
break;
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
handleResumeActivity((IBinder)msg.obj, true,
final Pair<IBinder, Bundle> resumeArgs = (Pair<IBinder, Bundle>) msg.obj;
handleResumeActivity(resumeArgs.first, resumeArgs.second, true,
msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
@@ -2032,7 +2041,7 @@ public final class ActivityThread {
+ ", comp=" + name
+ ", token=" + token);
}
return performLaunchActivity(r, null);
return performLaunchActivity(r, null, null);
}
public final Activity getActivity(IBinder token) {
@@ -2085,7 +2094,8 @@ public final class ActivityThread {
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent,
Bundle options) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
@@ -2143,7 +2153,7 @@ public final class ActivityThread {
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
r.embeddedID, r.lastNonConfigurationInstances, config, options);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -2242,12 +2252,13 @@ public final class ActivityThread {
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
Activity a = performLaunchActivity(r, customIntent);
Activity a = performLaunchActivity(r, customIntent, r.activityOptions);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
handleResumeActivity(r.token, r.activityOptions, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2808,12 +2819,13 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager = null;
}
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
final void handleResumeActivity(IBinder token, Bundle resumeArgs,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
@@ -3734,6 +3746,7 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
r.activityOptions = null;
handleLaunchActivity(r, currentIntent);
}

View File

@@ -113,7 +113,8 @@ public abstract class ApplicationThreadNative extends Binder
IBinder b = data.readStrongBinder();
int procState = data.readInt();
boolean isForward = data.readInt() != 0;
scheduleResumeActivity(b, procState, isForward);
Bundle resumeArgs = data.readBundle();
scheduleResumeActivity(b, procState, isForward, resumeArgs);
return true;
}
@@ -145,8 +146,10 @@ public abstract class ApplicationThreadNative extends Binder
ParcelFileDescriptor profileFd = data.readInt() != 0
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
Bundle resumeArgs = data.readBundle();
scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler);
ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
resumeArgs);
return true;
}
@@ -696,13 +699,15 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
Bundle resumeArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
data.writeInt(procState);
data.writeInt(isForward ? 1 : 0);
data.writeBundle(resumeArgs);
mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
@@ -722,9 +727,10 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
throws RemoteException {
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle resumeArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
@@ -747,6 +753,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(0);
}
data.writeInt(autoStopProfiler ? 1 : 0);
data.writeBundle(resumeArgs);
mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();

View File

@@ -50,15 +50,16 @@ public interface IApplicationThread extends IInterface {
int configChanges) throws RemoteException;
void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException;
void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException;
void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
void scheduleResumeActivity(IBinder token, int procState, boolean isForward, Bundle resumeArgs)
throws RemoteException;
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
throws RemoteException;
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle resumeArgs)
throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, int configChanges,
boolean notResumed, Configuration config) throws RemoteException;

View File

@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.InflateException;
@@ -291,19 +292,40 @@ public class TransitionInflater {
int toId = a.getResourceId(
com.android.internal.R.styleable.TransitionManager_toScene, -1);
if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext);
String fromName = a.getString(
com.android.internal.R.styleable.TransitionManager_fromSceneName);
String toName = a.getString(
com.android.internal.R.styleable.TransitionManager_toSceneName);
if (transitionId >= 0) {
Transition transition = inflateTransition(transitionId);
if (transition != null) {
if (fromScene != null) {
if (toScene == null){
throw new RuntimeException("No matching toScene for given fromScene " +
"for transition ID " + transitionId);
} else {
boolean hasDest = false;
if (toScene != null) {
transitionManager.setTransition(fromScene, toScene, transition);
hasDest = true;
}
if (!TextUtils.isEmpty(toName)) {
transitionManager.setTransition(fromScene, toName, transition);
hasDest = true;
}
if (!hasDest) {
throw new RuntimeException("No matching toScene or toSceneName for given " +
"fromScene for transition ID " + transitionId);
}
} else if (toId >= 0) {
transitionManager.setTransition(toScene, transition);
}
if (fromName != null) {
if (toScene != null) {
transitionManager.setTransition(fromName, toScene, transition);
} else {
throw new RuntimeException("No matching toScene for given fromSceneName " +
"for transition ID " + transitionId);
}
}
}
}
a.recycle();

View File

@@ -67,9 +67,15 @@ public class TransitionManager {
private static Transition sDefaultTransition = new AutoTransition();
private static final String[] EMPTY_STRINGS = new String[0];
ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
ArrayMap<Scene, ArrayMap<String, Transition>> mSceneNameTransitions =
new ArrayMap<Scene, ArrayMap<String, Transition>>();
ArrayMap<String, ArrayMap<Scene, Transition>> mNameSceneTransitions =
new ArrayMap<String, ArrayMap<Scene, Transition>>();
private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
sRunningTransitions =
new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
@@ -217,6 +223,141 @@ public class TransitionManager {
}
}
/**
* Retrieve the transition from a named scene to a target defined scene if one has been
* associated with this TransitionManager.
*
* <p>A named scene is an indirect link for a transition. Fundamentally a named
* scene represents a potentially arbitrary intersection point of two otherwise independent
* transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
* while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
* In this way applications may define an API for more sophisticated transitions between
* caller and called activities very similar to the way that <code>Intent</code> extras
* define APIs for arguments and data propagation between activities.</p>
*
* @param fromName Named scene that this transition corresponds to
* @param toScene Target scene that this transition will move to
* @return Transition corresponding to the given fromName and toScene or null
* if no association exists in this TransitionManager
*
* @see #setTransition(String, Scene, Transition)
*/
public Transition getNamedTransition(String fromName, Scene toScene) {
ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName);
if (m != null) {
return m.get(toScene);
}
return null;
}
/**
* Retrieve the transition from a defined scene to a target named scene if one has been
* associated with this TransitionManager.
*
* <p>A named scene is an indirect link for a transition. Fundamentally a named
* scene represents a potentially arbitrary intersection point of two otherwise independent
* transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
* while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
* In this way applications may define an API for more sophisticated transitions between
* caller and called activities very similar to the way that <code>Intent</code> extras
* define APIs for arguments and data propagation between activities.</p>
*
* @param fromScene Scene that this transition starts from
* @param toName Name of the target scene
* @return Transition corresponding to the given fromScene and toName or null
* if no association exists in this TransitionManager
*/
public Transition getNamedTransition(Scene fromScene, String toName) {
ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
if (m != null) {
return m.get(toName);
}
return null;
}
/**
* Retrieve the supported target named scenes when transitioning away from the given scene.
*
* <p>A named scene is an indirect link for a transition. Fundamentally a named
* scene represents a potentially arbitrary intersection point of two otherwise independent
* transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
* while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
* In this way applications may define an API for more sophisticated transitions between
* caller and called activities very similar to the way that <code>Intent</code> extras
* define APIs for arguments and data propagation between activities.</p>
*
* @param fromScene Scene to transition from
* @return An array of Strings naming each supported transition starting from
* <code>fromScene</code>. If no transitions to a named scene from the given
* scene are supported this function will return a String[] of length 0.
*
* @see #setTransition(Scene, String, Transition)
*/
public String[] getTargetSceneNames(Scene fromScene) {
final ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
if (m == null) {
return EMPTY_STRINGS;
}
final int count = m.size();
final String[] result = new String[count];
for (int i = 0; i < count; i++) {
result[i] = m.keyAt(i);
}
return result;
}
/**
* Set a transition from a specific scene to a named scene.
*
* <p>A named scene is an indirect link for a transition. Fundamentally a named
* scene represents a potentially arbitrary intersection point of two otherwise independent
* transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
* while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
* In this way applications may define an API for more sophisticated transitions between
* caller and called activities very similar to the way that <code>Intent</code> extras
* define APIs for arguments and data propagation between activities.</p>
*
* @param fromScene Scene to transition from
* @param toName Named scene to transition to
* @param transition Transition to use
*
* @see #getTargetSceneNames(Scene)
*/
public void setTransition(Scene fromScene, String toName, Transition transition) {
ArrayMap<String, Transition> m = mSceneNameTransitions.get(fromScene);
if (m == null) {
m = new ArrayMap<String, Transition>();
mSceneNameTransitions.put(fromScene, m);
}
m.put(toName, transition);
}
/**
* Set a transition from a named scene to a concrete scene.
*
* <p>A named scene is an indirect link for a transition. Fundamentally a named
* scene represents a potentially arbitrary intersection point of two otherwise independent
* transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
* while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
* In this way applications may define an API for more sophisticated transitions between
* caller and called activities very similar to the way that <code>Intent</code> extras
* define APIs for arguments and data propagation between activities.</p>
*
* @param fromName Named scene to transition from
* @param toScene Scene to transition to
* @param transition Transition to use
*
* @see #getNamedTransition(String, Scene)
*/
public void setTransition(String fromName, Scene toScene, Transition transition) {
ArrayMap<Scene, Transition> m = mNameSceneTransitions.get(fromName);
if (m == null) {
m = new ArrayMap<Scene, Transition>();
mNameSceneTransitions.put(fromName, m);
}
m.put(toScene, transition);
}
/**
* This private utility class is used to listen for both OnPreDraw and
* OnAttachStateChange events. OnPreDraw events are the main ones we care
@@ -329,7 +470,6 @@ public class TransitionManager {
// Auto transition if there is no transition declared for the Scene, but there is
// a root or parent view
changeScene(scene, getTransition(scene));
}
/**

View File

@@ -27,6 +27,8 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
import android.transition.Scene;
import android.transition.TransitionManager;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -1333,4 +1335,47 @@ public abstract class Window {
* @param event A key or touch event to inject to this window.
*/
public void injectInputEvent(InputEvent event) { }
/**
* Retrieve the {@link TransitionManager} responsible for for default transitions
* in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
*
* <p>This method will return non-null after content has been initialized (e.g. by using
* {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
*
* @return This window's content TransitionManager or null if none is set.
*/
public TransitionManager getTransitionManager() {
return null;
}
/**
* Set the {@link TransitionManager} to use for default transitions in this window.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
*
* @param tm The TransitionManager to use for scene changes.
*/
public void setTransitionManager(TransitionManager tm) {
throw new UnsupportedOperationException();
}
/**
* Retrieve the {@link Scene} representing this window's current content.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
*
* <p>This method will return null if the current content is not represented by a Scene.</p>
*
* @return Current Scene being shown or null
*/
public Scene getContentScene() {
return null;
}
/**
* Set options that can affect the transition behavior within this window.
* @param options Options to set or null for none
*/
public void setTransitionOptions(Bundle options) {
throw new UnsupportedOperationException();
}
}