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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user