Make Fragment Transitions match Acitivty Transitions API
Bug 17188255
Change-Id: I506a097be4010d7156caf465c95295c58612c16e
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 67863a5..59f010c 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -23,15 +23,17 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.transition.Transition;
-import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
+import android.transition.TransitionUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LogWriter;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -42,8 +44,6 @@
final int[] mOps;
final int mTransition;
final int mTransitionStyle;
- final int mCustomTransition;
- final int mSceneRoot;
final String mName;
final int mIndex;
final int mBreadCrumbTitleRes;
@@ -96,8 +96,6 @@
mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
- mCustomTransition = bse.mCustomTransition;
- mSceneRoot = bse.mSceneRoot;
mSharedElementSourceNames = bse.mSharedElementSourceNames;
mSharedElementTargetNames = bse.mSharedElementTargetNames;
}
@@ -112,8 +110,6 @@
mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mBreadCrumbShortTitleRes = in.readInt();
mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- mCustomTransition = in.readInt();
- mSceneRoot = in.readInt();
mSharedElementSourceNames = in.createStringArrayList();
mSharedElementTargetNames = in.createStringArrayList();
}
@@ -164,8 +160,6 @@
bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
- bse.mCustomTransition = mCustomTransition;
- bse.mSceneRoot = mSceneRoot;
bse.mSharedElementSourceNames = mSharedElementSourceNames;
bse.mSharedElementTargetNames = mSharedElementTargetNames;
bse.bumpBackStackNesting(1);
@@ -186,8 +180,6 @@
TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
dest.writeInt(mBreadCrumbShortTitleRes);
TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
- dest.writeInt(mCustomTransition);
- dest.writeInt(mSceneRoot);
dest.writeStringList(mSharedElementSourceNames);
dest.writeStringList(mSharedElementTargetNames);
}
@@ -254,8 +246,6 @@
int mBreadCrumbShortTitleRes;
CharSequence mBreadCrumbShortTitleText;
- int mCustomTransition;
- int mSceneRoot;
ArrayList<String> mSharedElementSourceNames;
ArrayList<String> mSharedElementTargetNames;
@@ -573,13 +563,6 @@
}
@Override
- public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) {
- mSceneRoot = sceneRootId;
- mCustomTransition = transitionId;
- return this;
- }
-
- @Override
public FragmentTransaction addSharedElement(View sharedElement, String name) {
String transitionName = sharedElement.getTransitionName();
if (transitionName == null) {
@@ -760,8 +743,15 @@
bumpBackStackNesting(1);
- TransitionState state = beginTransition(mSharedElementSourceNames,
- mSharedElementTargetNames);
+ SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
+ SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
+
+ calculateFragments(firstOutFragments, lastInFragments);
+
+ TransitionState state = null;
+ if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
+ state = beginTransition(firstOutFragments, lastInFragments, false);
+ }
Op op = mHead;
while (op != null) {
@@ -854,142 +844,606 @@
}
if (state != null) {
- updateTransitionEndState(state, mSharedElementTargetNames);
+ updateTransitionEndState(state, firstOutFragments, lastInFragments, false);
}
}
- private TransitionState beginTransition(ArrayList<String> sourceNames,
- ArrayList<String> targetNames) {
- if (mCustomTransition <= 0 || mSceneRoot <= 0) {
- return null;
+ private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
+ if (fragment != null) {
+ int containerId = fragment.mContainerId;
+ if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
+ fragment.getView() != null && fragments.get(containerId) == null) {
+ fragments.put(containerId, fragment);
+ }
}
- View rootView = mManager.mContainer.findViewById(mSceneRoot);
- if (!(rootView instanceof ViewGroup)) {
- throw new IllegalArgumentException("SceneRoot is not a ViewGroup");
- }
- TransitionState state = new TransitionState();
- // get Transition scene root and create Transitions
- state.sceneRoot = (ViewGroup) rootView;
- state.sceneRoot.captureTransitioningViews(state.transitioningViews);
+ }
- state.exitTransition = TransitionInflater.from(mManager.mActivity)
- .inflateTransition(mCustomTransition);
- state.sharedElementTransition = TransitionInflater.from(mManager.mActivity)
- .inflateTransition(mCustomTransition);
- state.enterTransition = TransitionInflater.from(mManager.mActivity)
- .inflateTransition(mCustomTransition);
+ private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
+ if (fragment != null) {
+ int containerId = fragment.mContainerId;
+ if (containerId != 0) {
+ fragments.put(containerId, fragment);
+ }
+ }
+ }
+
+ /**
+ * Finds the first removed fragment and last added fragments when going forward.
+ * If none of the fragments have transitions, then both lists will be empty.
+ *
+ * @param firstOutFragments The list of first fragments to be removed, keyed on the
+ * container ID. This list will be modified by the method.
+ * @param lastInFragments The list of last fragments to be added, keyed on the
+ * container ID. This list will be modified by the method.
+ */
+ private void calculateFragments(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments) {
+ Op op = mHead;
+ while (op != null) {
+ switch (op.cmd) {
+ case OP_ADD:
+ setLastIn(lastInFragments, op.fragment);
+ break;
+ case OP_REPLACE: {
+ Fragment f = op.fragment;
+ if (mManager.mAdded != null) {
+ for (int i = 0; i < mManager.mAdded.size(); i++) {
+ Fragment old = mManager.mAdded.get(i);
+ if (f == null || old.mContainerId == f.mContainerId) {
+ if (old == f) {
+ f = null;
+ } else {
+ setFirstOut(firstOutFragments, old);
+ }
+ }
+ }
+ }
+ setLastIn(lastInFragments, f);
+ break;
+ }
+ case OP_REMOVE:
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ case OP_HIDE:
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ case OP_SHOW:
+ setLastIn(lastInFragments, op.fragment);
+ break;
+ case OP_DETACH:
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ case OP_ATTACH:
+ setLastIn(lastInFragments, op.fragment);
+ break;
+ }
+
+ op = op.next;
+ }
+
+ if (!haveTransitions(firstOutFragments, lastInFragments, false)) {
+ firstOutFragments.clear();
+ lastInFragments.clear();
+ }
+ }
+
+ /**
+ * @return true if custom transitions exist on any fragment in firstOutFragments or
+ * lastInFragments or false otherwise.
+ */
+ private static boolean haveTransitions(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, boolean isBack) {
+ for (int i = firstOutFragments.size() - 1; i >= 0; i--) {
+ Fragment f = firstOutFragments.valueAt(i);
+ if (isBack) {
+ if (f.getReturnTransition() != null ||
+ f.getSharedElementReturnTransition() != null) {
+ return true;
+ }
+ } else if (f.getExitTransition() != null) {
+ return true;
+ }
+ }
+
+ for (int i = lastInFragments.size() - 1; i >= 0; i--) {
+ Fragment f = lastInFragments.valueAt(i);
+ if (isBack) {
+ if (f.getReenterTransition() != null) {
+ return true;
+ }
+ } else if (f.getEnterTransition() != null ||
+ f.getSharedElementEnterTransition() != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Finds the first removed fragment and last added fragments when popping the back stack.
+ * If none of the fragments have transitions, then both lists will be empty.
+ *
+ * @param firstOutFragments The list of first fragments to be removed, keyed on the
+ * container ID. This list will be modified by the method.
+ * @param lastInFragments The list of last fragments to be added, keyed on the
+ * container ID. This list will be modified by the method.
+ */
+ public void calculateBackFragments(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments) {
+ Op op = mHead;
+ while (op != null) {
+ switch (op.cmd) {
+ case OP_ADD:
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ case OP_REPLACE:
+ if (op.removed != null) {
+ for (int i = op.removed.size() - 1; i >= 0; i--) {
+ setLastIn(lastInFragments, op.removed.get(i));
+ }
+ }
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ case OP_REMOVE:
+ setLastIn(lastInFragments, op.fragment);
+ break;
+ case OP_HIDE:
+ setLastIn(lastInFragments, op.fragment);
+ break;
+ case OP_SHOW:
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ case OP_DETACH:
+ setLastIn(lastInFragments, op.fragment);
+ break;
+ case OP_ATTACH:
+ setFirstOut(firstOutFragments, op.fragment);
+ break;
+ }
+
+ op = op.next;
+ }
+
+ if (!haveTransitions(firstOutFragments, lastInFragments, true)) {
+ firstOutFragments.clear();
+ lastInFragments.clear();
+ }
+ }
+
+ /**
+ * When custom fragment transitions are used, this sets up the state for each transition
+ * and begins the transition. A different transition is started for each fragment container
+ * and consists of up to 3 different transitions: the exit transition, a shared element
+ * transition and an enter transition.
+ *
+ * <p>The exit transition operates against the leaf nodes of the first fragment
+ * with a view that was removed. If no such fragment was removed, then no exit
+ * transition is executed. The exit transition comes from the outgoing fragment.</p>
+ *
+ * <p>The enter transition operates against the last fragment that was added. If
+ * that fragment does not have a view or no fragment was added, then no enter
+ * transition is executed. The enter transition comes from the incoming fragment.</p>
+ *
+ * <p>The shared element transition operates against all views and comes either
+ * from the outgoing fragment or the incoming fragment, depending on whether this
+ * is going forward or popping the back stack. When going forward, the incoming
+ * fragment's enter shared element transition is used, but when going back, the
+ * outgoing fragment's return shared element transition is used. Shared element
+ * transitions only operate if there is both an incoming and outgoing fragment.</p>
+ *
+ * @param firstOutFragments The list of first fragments to be removed, keyed on the
+ * container ID.
+ * @param lastInFragments The list of last fragments to be added, keyed on the
+ * container ID.
+ * @param isBack true if this is popping the back stack or false if this is a
+ * forward operation.
+ * @return The TransitionState used to complete the operation of the transition
+ * in {@link #updateTransitionEndState(android.app.BackStackRecord.TransitionState,
+ * android.util.SparseArray, android.util.SparseArray, boolean)}.
+ */
+ private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, boolean isBack) {
+ TransitionState state = new TransitionState();
+
// Adding a non-existent target view makes sure that the transitions don't target
// any views by default. They'll only target the views we tell add. If we don't
// add any, then no views will be targeted.
- View nonExistentView = new View(mManager.mActivity);
- state.enterTransition.addTarget(nonExistentView);
- state.exitTransition.addTarget(nonExistentView);
- state.sharedElementTransition.addTarget(nonExistentView);
+ state.nonExistentView = new View(mManager.mActivity);
- setSharedElementEpicenter(state.enterTransition, state);
+ ArrayMap<String, View> tempViews1 = new ArrayMap<String, View>();
+ ArrayMap<String, View> tempViews2 = new ArrayMap<String, View>();
+ ArrayList<String> tempNames = new ArrayList<String>();
+ ArrayList<View> tempViewList = new ArrayList<View>();
- state.excludingTransition = new TransitionSet()
- .addTransition(state.exitTransition)
- .addTransition(state.enterTransition);
-
- if (sourceNames != null) {
- // Map shared elements.
- state.sceneRoot.findNamedViews(state.namedViews);
- state.namedViews.retainAll(sourceNames);
- View epicenterView = state.namedViews.get(sourceNames.get(0));
- if (epicenterView != null) {
- // The epicenter is only the first shared element.
- setEpicenter(state.exitTransition, epicenterView);
- setEpicenter(state.sharedElementTransition, epicenterView);
- }
- state.transitioningViews.removeAll(state.namedViews.values());
- state.excludingTransition.addTransition(state.sharedElementTransition);
- addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+ // Go over all leaving fragments.
+ for (int i = 0; i < firstOutFragments.size(); i++) {
+ int containerId = firstOutFragments.keyAt(i);
+ configureTransitions(containerId, state, isBack, firstOutFragments,
+ lastInFragments, tempViews1, tempViews2, tempNames, tempViewList);
}
- // Adds the (maybe) exiting views, not including the shared element.
- // If some stay, that's ok.
- addTransitioningViews(state.exitTransition, state.transitioningViews);
+ // Now go over all entering fragments that didn't have a leaving fragment.
+ for (int i = 0; i < lastInFragments.size(); i++) {
+ int containerId = lastInFragments.keyAt(i);
+ if (firstOutFragments.get(containerId) == null) {
+ configureTransitions(containerId, state, isBack, firstOutFragments,
+ lastInFragments, tempViews1, tempViews2, tempNames, tempViewList);
+ }
+ }
- // Prepare for shared element name mapping. This could be chained in the case
- // of popping several back stack states.
- state.excludingTransition.setNameOverrides(new ArrayMap<String, String>());
- setNameOverrides(state, sourceNames, targetNames);
-
- // Don't include any subtree in the views that are hidden when capturing the
- // view hierarchy transitions. They should be as if not there.
- excludeHiddenFragments(state, true);
-
- TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition);
+ if (state.overallTransitions.size() == 0) {
+ state = null;
+ }
return state;
}
- private void updateTransitionEndState(TransitionState state, ArrayList<String> names) {
- // Find all views that are entering.
- ArrayList<View> enteringViews = new ArrayList<View>();
- state.sceneRoot.captureTransitioningViews(enteringViews);
- enteringViews.removeAll(state.transitioningViews);
+ private static Transition getEnterTransition(Fragment inFragment, boolean isBack) {
+ if (inFragment == null) {
+ return null;
+ }
+ return isBack ? inFragment.getReenterTransition() : inFragment.getEnterTransition();
+ }
- if (names != null) {
- // find all shared elements.
- state.namedViews.clear();
- state.sceneRoot.findNamedViews(state.namedViews);
- state.namedViews.retainAll(names);
- if (!state.namedViews.isEmpty()) {
- enteringViews.removeAll(state.namedViews.values());
- addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
- // now we know the epicenter of the entering transition.
- state.mEnteringEpicenterView = state.namedViews.get(names.get(0));
+ private static Transition getExitTransition(Fragment outFragment, boolean isBack) {
+ if (outFragment == null) {
+ return null;
+ }
+ return isBack ? outFragment.getReturnTransition() : outFragment.getExitTransition();
+ }
+
+ private static Transition getSharedElementTransition(Fragment inFragment, Fragment outFragment,
+ boolean isBack) {
+ if (inFragment == null || outFragment == null) {
+ return null;
+ }
+ return isBack ? outFragment.getSharedElementReturnTransition() :
+ inFragment.getSharedElementEnterTransition();
+ }
+
+ private static Transition captureExitingViews(Transition exitTransition, Fragment outFragment,
+ ArrayList<View> viewList) {
+ if (exitTransition != null) {
+ View root = outFragment.getView();
+ viewList.clear();
+ root.captureTransitioningViews(viewList);
+ if (viewList.isEmpty()) {
+ exitTransition = null;
+ } else {
+ addTransitioningViews(exitTransition, viewList);
+ }
+ }
+ return exitTransition;
+ }
+
+ private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
+ ArrayMap<String, View> namedViews, ArrayMap<String, View> tempViews2, boolean isBack) {
+ if (mSharedElementSourceNames != null) {
+ outFragment.getView().findNamedViews(namedViews);
+ if (isBack) {
+ namedViews.retainAll(mSharedElementTargetNames);
+ } else {
+ namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
+ namedViews, tempViews2);
}
}
- // Add all entering views to the enter transition.
- addTransitioningViews(state.enterTransition, enteringViews);
-
- // Don't allow capturing state for the newly-hidden fragments.
- excludeHiddenFragments(state, false);
-
- // Allow capturing state for the newly-shown fragments
- includeVisibleFragments(state.excludingTransition);
- }
-
- private void addTransitioningViews(Transition transition, Collection<View> views) {
- if (views.isEmpty()) {
- // Add a view so that we can modify the valid views at the end of the
- // fragment transaction.
- transition.addTarget(new View(mManager.mActivity));
+ if (isBack) {
+ outFragment.mEnterTransitionListener.remapSharedElements(
+ mSharedElementTargetNames, namedViews);
+ setBackNameOverrides(state, namedViews, false);
} else {
- for (View view : views) {
- transition.addTarget(view);
+ outFragment.mExitTransitionListener.remapSharedElements(
+ mSharedElementTargetNames, namedViews);
+ setNameOverrides(state, namedViews, false);
+ }
+
+ return namedViews;
+ }
+
+ /**
+ * Prepares the enter transition by adding a non-existent view to the transition's target list
+ * and setting it epicenter callback. By adding a non-existent view to the target list,
+ * we can prevent any view from being targeted at the beginning of the transition.
+ * We will add to the views before the end state of the transition is captured so that the
+ * views will appear. At the start of the transition, we clear the list of targets so that
+ * we can restore the state of the transition and use it again.
+ */
+ private void prepareEnterTransition(TransitionState state, final Transition enterTransition,
+ final View container, final Fragment inFragment) {
+ if (enterTransition != null) {
+ final ArrayList<View> enteringViews = new ArrayList<View>();
+ final View nonExistentView = state.nonExistentView;
+ enterTransition.addTarget(state.nonExistentView);
+ enterTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ transition.removeListener(this);
+ transition.removeTarget(nonExistentView);
+ int numViews = enteringViews.size();
+ for (int i = 0; i < numViews; i++) {
+ transition.removeTarget(enteringViews.get(i));
+ }
+ }
+ });
+ container.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ container.getViewTreeObserver().removeOnPreDrawListener(this);
+ View view = inFragment.getView();
+ if (view != null) {
+ view.captureTransitioningViews(enteringViews);
+ addTransitioningViews(enterTransition, enteringViews);
+ }
+ return true;
+ }
+ });
+ setSharedElementEpicenter(enterTransition, state);
+ }
+ }
+
+ private static Transition mergeTransitions(Transition enterTransition,
+ Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
+ boolean isBack) {
+ boolean overlap = true;
+ if (enterTransition != null && exitTransition != null) {
+ overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
+ inFragment.getAllowEnterTransitionOverlap();
+ }
+
+ Transition transition;
+ if (overlap) {
+ transition = TransitionUtils.mergeTransitions(enterTransition, exitTransition,
+ sharedElementTransition);
+ } else {
+ TransitionSet staggered = new TransitionSet()
+ .addTransition(exitTransition)
+ .addTransition(enterTransition)
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+ transition = TransitionUtils.mergeTransitions(staggered, sharedElementTransition);
+ }
+ return transition;
+ }
+
+ /**
+ * Configures custom transitions for a specific fragment container.
+ *
+ * @param containerId The container ID of the fragments to configure the transition for.
+ * @param state The Transition State to be shared with {@link #updateTransitionEndState(
+ * android.app.BackStackRecord.TransitionState, android.util.SparseArray,
+ * android.util.SparseArray, boolean)} later.
+ * @param firstOutFragments The list of first fragments to be removed, keyed on the
+ * container ID.
+ * @param lastInFragments The list of last fragments to be added, keyed on the
+ * container ID.
+ * @param isBack true if this is popping the back stack or false if this is a
+ * forward operation.
+ * @param tempViews1 A temporary mapping of names to Views, used to avoid allocation
+ * inside a loop.
+ * @param tempViews2 A temporary mapping of names to Views, used to avoid allocation
+ * inside a loop.
+ * @param tempNames A temporary list of Strings, used to avoid allocation inside a loop.
+ * @param tempViewList A temporary list of Views, used to avoid allocation inside a loop.
+ */
+ private void configureTransitions(int containerId, TransitionState state, boolean isBack,
+ SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments,
+ ArrayMap<String, View> tempViews1, ArrayMap<String, View> tempViews2,
+ ArrayList<String> tempNames, ArrayList<View> tempViewList) {
+ ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.findViewById(containerId);
+ if (sceneRoot != null) {
+ Fragment inFragment = lastInFragments.get(containerId);
+ Fragment outFragment = firstOutFragments.get(containerId);
+
+ Transition enterTransition = getEnterTransition(inFragment, isBack);
+ Transition sharedElementTransition = getSharedElementTransition(inFragment, outFragment,
+ isBack);
+ Transition exitTransition = getExitTransition(outFragment, isBack);
+ exitTransition = captureExitingViews(exitTransition, outFragment, tempViewList);
+
+ ArrayMap<String, View> namedViews = tempViews1;
+ namedViews.clear();
+ if (sharedElementTransition != null) {
+ namedViews = remapSharedElements(state,
+ outFragment, namedViews, tempViews2, isBack);
+ }
+
+ // Notify the start of the transition.
+ SharedElementListener listener = isBack ?
+ outFragment.mEnterTransitionListener :
+ inFragment.mEnterTransitionListener;
+ tempNames.clear();
+ tempNames.addAll(namedViews.keySet());
+ tempViewList.clear();
+ tempViewList.addAll(namedViews.values());
+ listener.setSharedElementStart(tempNames, tempViewList, null);
+
+ // Set the epicenter of the exit transition
+ if (mSharedElementTargetNames != null && exitTransition != null) {
+ View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
+ if (epicenterView != null) {
+ setEpicenter(exitTransition, epicenterView);
+ }
+ }
+
+ prepareEnterTransition(state, enterTransition, sceneRoot, inFragment);
+
+ Transition transition = mergeTransitions(enterTransition, exitTransition,
+ sharedElementTransition, inFragment, isBack);
+
+ if (transition != null) {
+ state.overallTransitions.put(containerId, transition);
+ transition.setNameOverrides(state.nameOverrides);
+ // We want to exclude hidden views later, so we need a non-null list in the
+ // transition now.
+ transition.excludeTarget(state.nonExistentView, true);
+ // Now exclude all currently hidden fragments.
+ excludeHiddenFragments(state, containerId, transition);
+ cleanupHiddenFragments(transition, state);
+ TransitionManager.beginDelayedTransition(sceneRoot, transition);
}
}
}
- private void excludeHiddenFragments(TransitionState state, boolean forceExclude) {
- if (mManager.mAdded != null) {
- for (int i = 0; i < mManager.mAdded.size(); i++) {
- Fragment fragment = mManager.mAdded.get(i);
- if (fragment.mView != null && fragment.mHidden
- && (forceExclude || !state.hiddenViews.contains(fragment.mView))) {
- state.excludingTransition.excludeTarget(fragment.mView, true);
- state.hiddenViews.add(fragment.mView);
+ /**
+ * Remaps a name-to-View map, substituting different names for keys.
+ *
+ * @param inMap A list of keys found in the map, in the order in toGoInMap
+ * @param toGoInMap A list of keys to use for the new map, in the order of inMap
+ * @param namedViews The current mapping
+ * @param tempMap A temporary mapping that will be filled with the new values.
+ * @return tempMap after it has been mapped with the new names as keys.
+ */
+ private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
+ ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews,
+ ArrayMap<String, View> tempMap) {
+ tempMap.clear();
+ if (!namedViews.isEmpty()) {
+ int numKeys = inMap.size();
+ for (int i = 0; i < numKeys; i++) {
+ View view = namedViews.get(inMap.get(i));
+ if (view != null) {
+ tempMap.put(toGoInMap.get(i), view);
}
}
}
- if (forceExclude && state.hiddenViews.isEmpty()) {
- state.excludingTransition.excludeTarget(new View(mManager.mActivity), true);
+ return tempMap;
+ }
+
+ /**
+ * After making all fragment changes, this updates the custom transitions to take into
+ * account the entering views and any remapping.
+ *
+ * @param state The transition State as returned from {@link #beginTransition(
+ * android.util.SparseArray, android.util.SparseArray, boolean)}.
+ * @param outFragments The list of first fragments to be removed, keyed on the
+ * container ID.
+ * @param inFragments The list of last fragments to be added, keyed on the
+ * container ID.
+ * @param isBack true if this is popping the back stack or false if this is a
+ * forward operation.
+ */
+ private void updateTransitionEndState(TransitionState state, SparseArray<Fragment> outFragments,
+ SparseArray<Fragment> inFragments, boolean isBack) {
+ ArrayMap<String, View> tempViews1 = new ArrayMap<String, View>();
+ ArrayMap<String, View> tempViews2 = new ArrayMap<String, View>();
+ ArrayList<String> tempNames = new ArrayList<String>();
+ ArrayList<View> tempViews = new ArrayList<View>();
+
+ int numInFragments = inFragments.size();
+ for (int i = 0; i < numInFragments; i++) {
+ Fragment inFragment = inFragments.valueAt(i);
+ tempViews1.clear();
+ ArrayMap<String, View> namedViews = mapEnteringSharedElements(inFragment, tempViews1,
+ tempViews2, isBack);
+ // remap shared elements and set the name mapping used in the shared element transition.
+ if (isBack) {
+ inFragment.mExitTransitionListener.remapSharedElements(
+ mSharedElementTargetNames, namedViews);
+ setBackNameOverrides(state, namedViews, true);
+ } else {
+ inFragment.mEnterTransitionListener.remapSharedElements(
+ mSharedElementTargetNames, namedViews);
+ setNameOverrides(state, namedViews, true);
+ }
+
+ if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
+ // now we know the epicenter of the entering transition.
+ View epicenter = namedViews.get(mSharedElementTargetNames.get(0));
+ if (epicenter != null) {
+ state.enteringEpicenterView = epicenter;
+ }
+ }
+
+ int containerId = inFragments.keyAt(i);
+ SharedElementListener sharedElementListener = isBack ?
+ outFragments.get(containerId).mEnterTransitionListener :
+ inFragment.mEnterTransitionListener;
+ tempNames.clear();
+ tempNames.addAll(namedViews.keySet());
+ tempViews.clear();
+ tempViews.addAll(namedViews.values());
+ sharedElementListener.setSharedElementEnd(tempNames, tempViews, null);
+ }
+
+ // Don't include any newly-hidden fragments in the transition.
+ excludeHiddenFragments(state);
+ }
+
+ private ArrayMap<String, View> mapEnteringSharedElements(Fragment inFragment,
+ ArrayMap<String, View> namedViews, ArrayMap<String, View> tempViews2, boolean isBack) {
+ View root = inFragment.getView();
+ if (root != null) {
+ if (mSharedElementSourceNames != null) {
+ root.findNamedViews(namedViews);
+ if (isBack) {
+ namedViews = remapNames(mSharedElementSourceNames,
+ mSharedElementTargetNames, namedViews, tempViews2);
+ } else {
+ namedViews.retainAll(mSharedElementTargetNames);
+ }
+ }
+ }
+ return namedViews;
+ }
+
+ private static void cleanupHiddenFragments(Transition transition, TransitionState state) {
+ final ArrayList<View> hiddenViews = state.hiddenFragmentViews;
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ transition.removeListener(this);
+ int numViews = hiddenViews.size();
+ for (int i = 0; i < numViews; i++) {
+ transition.excludeTarget(hiddenViews.get(i), false);
+ }
+ }
+ });
+ }
+
+ private void excludeHiddenFragments(TransitionState state, int containerId,
+ Transition transition) {
+ if (mManager.mAdded != null) {
+ for (int i = 0; i < mManager.mAdded.size(); i++) {
+ Fragment fragment = mManager.mAdded.get(i);
+ if (fragment.mView != null && fragment.mContainer != null &&
+ fragment.mContainerId == containerId) {
+ if (fragment.mHidden) {
+ if (!state.hiddenFragmentViews.contains(fragment.mView)) {
+ transition.excludeTarget(fragment.mView, true);
+ state.hiddenFragmentViews.add(fragment.mView);
+ }
+ } else {
+ transition.excludeTarget(fragment.mView, false);
+ state.hiddenFragmentViews.remove(fragment.mView);
+ }
+ }
+ }
}
}
- private void includeVisibleFragments(Transition transition) {
- if (mManager.mAdded != null) {
- for (int i = 0; i < mManager.mAdded.size(); i++) {
- Fragment fragment = mManager.mAdded.get(i);
- if (fragment.mView != null && !fragment.mHidden) {
- transition.excludeTarget(fragment.mView, false);
+ private void excludeHiddenFragments(TransitionState state) {
+ int numTransitions = state.overallTransitions.size();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition transition = state.overallTransitions.valueAt(i);
+ int containerId = state.overallTransitions.keyAt(i);
+ excludeHiddenFragments(state, containerId, transition);
+ }
+ }
+
+ private static void addTransitioningViews(Transition transition, final Collection<View> views) {
+ for (View view : views) {
+ transition.addTarget(view);
+ }
+
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ transition.removeListener(this);
+ for (View view : views) {
+ transition.removeTarget(view);
}
}
- }
+ });
}
private static void setEpicenter(Transition transition, View view) {
@@ -1010,16 +1464,17 @@
@Override
public Rect onGetEpicenter(Transition transition) {
- if (mEpicenter == null && state.mEnteringEpicenterView != null) {
+ if (mEpicenter == null && state.enteringEpicenterView != null) {
mEpicenter = new Rect();
- state.mEnteringEpicenterView.getBoundsOnScreen(mEpicenter);
+ state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
}
return mEpicenter;
}
});
}
- public TransitionState popFromBackStack(boolean doStateMove, TransitionState state) {
+ public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
+ SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "popFromBackStack: " + this);
LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -1029,8 +1484,10 @@
}
if (state == null) {
- state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames);
- } else {
+ if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
+ state = beginTransition(firstOutFragments, lastInFragments, true);
+ }
+ } else if (!doStateMove) {
setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
}
@@ -1110,7 +1567,7 @@
mManager.moveToState(mManager.mCurState,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
if (state != null) {
- updateTransitionEndState(state, mSharedElementSourceNames);
+ updateTransitionEndState(state, firstOutFragments, lastInFragments, true);
state = null;
}
}
@@ -1122,15 +1579,17 @@
return state;
}
- private static void setNameOverride(Transition transition, String source, String target) {
- ArrayMap<String, String> overrides = transition.getNameOverrides();
- for (int index = 0; index < overrides.size(); index++) {
- if (source.equals(overrides.valueAt(index))) {
- overrides.setValueAt(index, target);
- return;
+ private static void setNameOverride(ArrayMap<String, String> overrides,
+ String source, String target) {
+ if (source != null && target != null && !source.equals(target)) {
+ for (int index = 0; index < overrides.size(); index++) {
+ if (source.equals(overrides.valueAt(index))) {
+ overrides.setValueAt(index, target);
+ return;
+ }
}
+ overrides.put(source, target);
}
- overrides.put(source, target);
}
private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
@@ -1139,7 +1598,36 @@
for (int i = 0; i < sourceNames.size(); i++) {
String source = sourceNames.get(i);
String target = targetNames.get(i);
- setNameOverride(state.excludingTransition, source, target);
+ setNameOverride(state.nameOverrides, source, target);
+ }
+ }
+ }
+
+ private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
+ boolean isEnd) {
+ int count = mSharedElementTargetNames.size();
+ for (int i = 0; i < count; i++) {
+ String source = mSharedElementSourceNames.get(i);
+ String originalTarget = mSharedElementTargetNames.get(i);
+ String target = namedViews.get(originalTarget).getTransitionName();
+ if (isEnd) {
+ setNameOverride(state.nameOverrides, source, target);
+ } else {
+ setNameOverride(state.nameOverrides, target, source);
+ }
+ }
+ }
+
+ private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
+ boolean isEnd) {
+ int count = namedViews.size();
+ for (int i = 0; i < count; i++) {
+ String source = namedViews.keyAt(i);
+ String target = namedViews.valueAt(i).getTransitionName();
+ if (isEnd) {
+ setNameOverride(state.nameOverrides, source, target);
+ } else {
+ setNameOverride(state.nameOverrides, target, source);
}
}
}
@@ -1161,14 +1649,11 @@
}
public class TransitionState {
- public ArrayList<View> hiddenViews = new ArrayList<View>();
- public ArrayList<View> transitioningViews = new ArrayList<View>();
- public ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
- public Transition exitTransition;
- public Transition sharedElementTransition;
- public Transition enterTransition;
- public TransitionSet excludingTransition;
- public ViewGroup sceneRoot;
- public View mEnteringEpicenterView;
+ public SparseArray<Transition> overallTransitions = new SparseArray<Transition>();
+ public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
+ public ArrayList<View> hiddenFragmentViews = new ArrayList<View>();
+
+ public View enteringEpicenterView;
+ public View nonExistentView;
}
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 2ff3d57..dbee81e 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -23,10 +23,14 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionSet;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.AttributeSet;
@@ -58,11 +62,11 @@
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
-
+
Bundle mSavedFragmentState;
-
+
Fragment mInstance;
-
+
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
@@ -74,7 +78,7 @@
mDetached = frag.mDetached;
mArguments = frag.mArguments;
}
-
+
public FragmentState(Parcel in) {
mClassName = in.readString();
mIndex = in.readInt();
@@ -87,18 +91,18 @@
mArguments = in.readBundle();
mSavedFragmentState = in.readBundle();
}
-
+
public Fragment instantiate(Activity activity, Fragment parent) {
if (mInstance != null) {
return mInstance;
}
-
+
if (mArguments != null) {
mArguments.setClassLoader(activity.getClassLoader());
}
-
+
mInstance = Fragment.instantiate(activity, mClassName, mArguments);
-
+
if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(activity.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
@@ -117,7 +121,7 @@
return mInstance;
}
-
+
public int describeContents() {
return 0;
}
@@ -134,13 +138,13 @@
dest.writeBundle(mArguments);
dest.writeBundle(mSavedFragmentState);
}
-
+
public static final Parcelable.Creator<FragmentState> CREATOR
= new Parcelable.Creator<FragmentState>() {
public FragmentState createFromParcel(Parcel in) {
return new FragmentState(in);
}
-
+
public FragmentState[] newArray(int size) {
return new FragmentState[size];
}
@@ -299,17 +303,17 @@
* how you can determine if a fragment placed in a container is no longer
* running in a layout with that container and avoid creating its view hierarchy
* in that case.)
- *
+ *
* <p>The attributes of the <fragment> tag are used to control the
* LayoutParams provided when attaching the fragment's view to the parent
* container. They can also be parsed by the fragment in {@link #onInflate}
* as parameters.
- *
+ *
* <p>The fragment being instantiated must have some kind of unique identifier
* so that it can be re-associated with a previous instance if the parent
* activity needs to be destroyed and recreated. This can be provided these
* ways:
- *
+ *
* <ul>
* <li>If nothing is explicitly supplied, the view ID of the container will
* be used.
@@ -318,7 +322,7 @@
* <li><code>android:id</code> can be used in <fragment> to provide
* a specific identifier for the fragment.
* </ul>
- *
+ *
* <a name="BackStack"></a>
* <h3>Back Stack</h3>
*
@@ -347,7 +351,7 @@
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
private static final ArrayMap<String, Class<?>> sClassMap =
new ArrayMap<String, Class<?>>();
-
+
static final int INVALID_STATE = -1; // Invalid state used as a null value.
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
@@ -355,9 +359,11 @@
static final int STOPPED = 3; // Fully created, not started.
static final int STARTED = 4; // Created and started, not resumed.
static final int RESUMED = 5; // Created started and resumed.
-
+
+ private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
+
int mState = INITIALIZING;
-
+
// Non-null if the fragment's view hierarchy is currently animating away,
// meaning we need to wait a bit on completely destroying it. This is the
// animation that is running.
@@ -370,13 +376,13 @@
// When instantiated from saved state, this is the saved state.
Bundle mSavedFragmentState;
SparseArray<Parcelable> mSavedViewState;
-
+
// Index into active fragment array.
int mIndex = -1;
-
+
// Internal unique name for this fragment;
String mWho;
-
+
// Construction arguments;
Bundle mArguments;
@@ -391,25 +397,25 @@
// True if the fragment is in the list of added fragments.
boolean mAdded;
-
+
// If set this fragment is being removed from its activity.
boolean mRemoving;
// True if the fragment is in the resumed state.
boolean mResumed;
-
+
// Set to true if this fragment was instantiated from a layout file.
boolean mFromLayout;
-
+
// Set to true when the view has actually been inflated in its layout.
boolean mInLayout;
// True if this fragment has been restored from previously saved state.
boolean mRestored;
-
+
// Number of active back stack entries this fragment is in.
int mBackStackNesting;
-
+
// The fragment manager we are associated with. Set as soon as the
// fragment is used in a transaction; cleared after it has been removed
// from all transactions.
@@ -428,29 +434,29 @@
// was dynamically added to the view hierarchy, or the ID supplied in
// layout.
int mFragmentId;
-
+
// When a fragment is being dynamically added to the view hierarchy, this
// is the identifier of the parent container it is being added to.
int mContainerId;
-
+
// The optional named tag for this fragment -- usually used to find
// fragments that are not part of the layout.
String mTag;
-
+
// Set to true when the app has requested that this fragment be hidden
// from the user.
boolean mHidden;
-
+
// Set to true when the app has requested that this fragment be detached.
boolean mDetached;
// If set this fragment would like its instance retained across
// configuration changes.
boolean mRetainInstance;
-
+
// If set this fragment is being retained across the current config change.
boolean mRetaining;
-
+
// If set this fragment has menu items to contribute.
boolean mHasMenu;
@@ -459,16 +465,16 @@
// Used to verify that subclasses call through to super class.
boolean mCalled;
-
+
// If app has requested a specific animation, this is the one to use.
int mNextAnim;
-
+
// The parent container of the fragment after dynamically added to UI.
ViewGroup mContainer;
-
+
// The View generated for this fragment.
View mView;
-
+
// Whether this fragment should defer starting until after other fragments
// have been started and their loaders are finished.
boolean mDeferStart;
@@ -479,7 +485,19 @@
LoaderManagerImpl mLoaderManager;
boolean mLoadersStarted;
boolean mCheckedForLoaderManager;
-
+
+ private Transition mEnterTransition = null;
+ private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mExitTransition = null;
+ private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementEnterTransition = null;
+ private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+ private Boolean mAllowReturnTransitionOverlap;
+ private Boolean mAllowEnterTransitionOverlap;
+
+ SharedElementListener mEnterTransitionListener = SharedElementListener.NULL_LISTENER;
+ SharedElementListener mExitTransitionListener = SharedElementListener.NULL_LISTENER;
+
/**
* State information that has been retrieved from a fragment instance
* through {@link FragmentManager#saveFragmentInstanceState(Fragment)
@@ -543,7 +561,7 @@
* will not be called when the fragment is re-instantiated; instead,
* arguments can be supplied by the caller with {@link #setArguments}
* and later retrieved by the Fragment with {@link #getArguments}.
- *
+ *
* <p>Applications should generally not implement a constructor. The
* first place application code an run where the fragment is ready to
* be used is in {@link #onAttach(Activity)}, the point where the fragment
@@ -609,7 +627,7 @@
+ " empty constructor that is public", e);
}
}
-
+
final void restoreViewState(Bundle savedInstanceState) {
if (mSavedViewState != null) {
mView.restoreHierarchyState(mSavedViewState);
@@ -649,7 +667,7 @@
@Override final public int hashCode() {
return super.hashCode();
}
-
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
@@ -669,7 +687,7 @@
sb.append('}');
return sb.toString();
}
-
+
/**
* Return the identifier this fragment is known by. This is either
* the android:id value supplied in a layout or the container view ID
@@ -678,14 +696,14 @@
final public int getId() {
return mFragmentId;
}
-
+
/**
* Get the tag name of the fragment, if specified.
*/
final public String getTag() {
return mTag;
}
-
+
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
@@ -760,7 +778,7 @@
final public Activity getActivity() {
return mActivity;
}
-
+
/**
* Return <code>getActivity().getResources()</code>.
*/
@@ -770,7 +788,7 @@
}
return mActivity.getResources();
}
-
+
/**
* Return a localized, styled CharSequence from the application's package's
* default string table.
@@ -870,7 +888,7 @@
final public boolean isRemoving() {
return mRemoving;
}
-
+
/**
* Return true if the layout is included as part of an activity view
* hierarchy via the <fragment> tag. This will always be true when
@@ -889,7 +907,7 @@
final public boolean isResumed() {
return mResumed;
}
-
+
/**
* Return true if the fragment is currently visible to the user. This means
* it: (1) has been added, (2) has its view attached to the window, and
@@ -899,7 +917,7 @@
return isAdded() && !isHidden() && mView != null
&& mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
}
-
+
/**
* Return true if the fragment has been hidden. By default fragments
* are shown. You can find out about changes to this state with
@@ -910,7 +928,7 @@
final public boolean isHidden() {
return mHidden;
}
-
+
/**
* Called when the hidden state (as returned by {@link #isHidden()} of
* the fragment has changed. Fragments start out not hidden; this will
@@ -920,7 +938,7 @@
*/
public void onHiddenChanged(boolean hidden) {
}
-
+
/**
* Control whether a fragment instance is retained across Activity
* re-creation (such as from a configuration change). This can only
@@ -942,16 +960,16 @@
}
mRetainInstance = retain;
}
-
+
final public boolean getRetainInstance() {
return mRetainInstance;
}
-
+
/**
* Report that this fragment would like to participate in populating
* the options menu by receiving a call to {@link #onCreateOptionsMenu}
* and related methods.
- *
+ *
* @param hasMenu If true, the fragment has menu items to contribute.
*/
public void setHasOptionsMenu(boolean hasMenu) {
@@ -1034,7 +1052,7 @@
public void startActivity(Intent intent) {
startActivity(intent, null);
}
-
+
/**
* Call {@link Activity#startActivity(Intent, Bundle)} from the fragment's
* containing Activity.
@@ -1081,13 +1099,13 @@
mActivity.startActivityFromFragment(this, intent, requestCode, options);
}
}
-
+
/**
* Receive the result from a previous call to
* {@link #startActivityForResult(Intent, int)}. This follows the
* related Activity API as described there in
* {@link Activity#onActivityResult(int, int, Intent)}.
- *
+ *
* @param requestCode The integer request code originally supplied to
* startActivityForResult(), allowing you to identify who this
* result came from.
@@ -1098,7 +1116,7 @@
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
-
+
/**
* @hide Hack so that DialogFragment can make its Dialog before creating
* its views, and the view construction can use the dialog's context for
@@ -1115,7 +1133,7 @@
return mActivity.getLayoutInflater();
}
}
-
+
/**
* @deprecated Use {@link #onInflate(Activity, AttributeSet, Bundle)} instead.
*/
@@ -1131,7 +1149,7 @@
* tag in a layout file. Note this is <em>before</em> the fragment's
* {@link #onAttach(Activity)} has been called; all you should do here is
* parse the attributes and save them away.
- *
+ *
* <p>This is called every time the fragment is inflated, even if it is
* being inflated into a new instance with saved state. It typically makes
* sense to re-parse the parameters each time, to allow them to change with
@@ -1169,8 +1187,33 @@
public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
onInflate(attrs, savedInstanceState);
mCalled = true;
+
+ TypedArray a = activity.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Fragment);
+ mEnterTransition = loadTransition(activity, a, mEnterTransition, null,
+ com.android.internal.R.styleable.Fragment_fragmentEnterTransition);
+ mReturnTransition = loadTransition(activity, a, mReturnTransition, USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentReturnTransition);
+ mExitTransition = loadTransition(activity, a, mExitTransition, null,
+ com.android.internal.R.styleable.Fragment_fragmentExitTransition);
+ mReenterTransition = loadTransition(activity, a, mReenterTransition, USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentReenterTransition);
+ mSharedElementEnterTransition = loadTransition(activity, a, mSharedElementEnterTransition,
+ null, com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition);
+ mSharedElementReturnTransition = loadTransition(activity, a, mSharedElementReturnTransition,
+ USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition);
+ if (mAllowEnterTransitionOverlap == null) {
+ mAllowEnterTransitionOverlap = a.getBoolean(
+ com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap, true);
+ }
+ if (mAllowReturnTransitionOverlap == null) {
+ mAllowReturnTransitionOverlap = a.getBoolean(
+ com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap, true);
+ }
+ a.recycle();
}
-
+
/**
* Called when a fragment is first attached to its activity.
* {@link #onCreate(Bundle)} will be called after this.
@@ -1178,25 +1221,25 @@
public void onAttach(Activity activity) {
mCalled = true;
}
-
+
/**
* Called when a fragment loads an animation.
*/
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
return null;
}
-
+
/**
* Called to do initial creation of a fragment. This is called after
* {@link #onAttach(Activity)} and before
* {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
- *
+ *
* <p>Note that this can be called while the fragment's activity is
* still in the process of being created. As such, you can not rely
* on things like the activity's content view hierarchy being initialized
* at this point. If you want to do work once the activity itself is
* created, see {@link #onActivityCreated(Bundle)}.
- *
+ *
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
@@ -1209,10 +1252,10 @@
* This is optional, and non-graphical fragments can return null (which
* is the default implementation). This will be called between
* {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}.
- *
+ *
* <p>If you return a View from here, you will later be called in
* {@link #onDestroyView} when the view is being released.
- *
+ *
* @param inflater The LayoutInflater object that can be used to inflate
* any views in the fragment,
* @param container If non-null, this is the parent view that the fragment's
@@ -1220,7 +1263,7 @@
* but this can be used to generate the LayoutParams of the view.
* @param savedInstanceState If non-null, this fragment is being re-constructed
* from a previous saved state as given here.
- *
+ *
* @return Return the View for the fragment's UI, or null.
*/
@Nullable
@@ -1241,18 +1284,18 @@
*/
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
}
-
+
/**
* Get the root view for the fragment's layout (the one returned by {@link #onCreateView}),
* if provided.
- *
+ *
* @return The fragment's root view, or null if it has no layout.
*/
@Nullable
public View getView() {
return mView;
}
-
+
/**
* Called when the fragment's activity has been created and this
* fragment's view hierarchy instantiated. It can be used to do final
@@ -1292,7 +1335,7 @@
*/
public void onStart() {
mCalled = true;
-
+
if (!mLoadersStarted) {
mLoadersStarted = true;
if (!mCheckedForLoaderManager) {
@@ -1304,7 +1347,7 @@
}
}
}
-
+
/**
* Called when the fragment is visible to the user and actively running.
* This is generally
@@ -1314,7 +1357,7 @@
public void onResume() {
mCalled = true;
}
-
+
/**
* Called to ask the fragment to save its current dynamic state, so it
* can later be reconstructed in a new instance of its process is
@@ -1336,11 +1379,11 @@
*/
public void onSaveInstanceState(Bundle outState) {
}
-
+
public void onConfigurationChanged(Configuration newConfig) {
mCalled = true;
}
-
+
/**
* Called when the Fragment is no longer resumed. This is generally
* tied to {@link Activity#onPause() Activity.onPause} of the containing
@@ -1349,7 +1392,7 @@
public void onPause() {
mCalled = true;
}
-
+
/**
* Called when the Fragment is no longer started. This is generally
* tied to {@link Activity#onStop() Activity.onStop} of the containing
@@ -1358,11 +1401,11 @@
public void onStop() {
mCalled = true;
}
-
+
public void onLowMemory() {
mCalled = true;
}
-
+
public void onTrimMemory(int level) {
mCalled = true;
}
@@ -1379,7 +1422,7 @@
public void onDestroyView() {
mCalled = true;
}
-
+
/**
* Called when the fragment is no longer in use. This is called
* after {@link #onStop()} and before {@link #onDetach()}.
@@ -1434,16 +1477,16 @@
public void onDetach() {
mCalled = true;
}
-
+
/**
* Initialize the contents of the Activity's standard options menu. You
* should place your menu items in to <var>menu</var>. For this method
* to be called, you must have first called {@link #setHasOptionsMenu}. See
* {@link Activity#onCreateOptionsMenu(Menu) Activity.onCreateOptionsMenu}
* for more information.
- *
+ *
* @param menu The options menu in which you place your items.
- *
+ *
* @see #setHasOptionsMenu
* @see #onPrepareOptionsMenu
* @see #onOptionsItemSelected
@@ -1458,10 +1501,10 @@
* dynamically modify the contents. See
* {@link Activity#onPrepareOptionsMenu(Menu) Activity.onPrepareOptionsMenu}
* for more information.
- *
+ *
* @param menu The options menu as last shown or first initialized by
* onCreateOptionsMenu().
- *
+ *
* @see #setHasOptionsMenu
* @see #onCreateOptionsMenu
*/
@@ -1477,7 +1520,7 @@
*/
public void onDestroyOptionsMenu() {
}
-
+
/**
* This hook is called whenever an item in your options menu is selected.
* The default implementation simply returns false to have the normal
@@ -1485,15 +1528,15 @@
* its Handler as appropriate). You can use this method for any items
* for which you would like to do processing without those other
* facilities.
- *
+ *
* <p>Derived classes should call through to the base class for it to
* perform the default menu handling.
- *
+ *
* @param item The menu item that was selected.
- *
+ *
* @return boolean Return false to allow normal menu processing to
* proceed, true to consume it here.
- *
+ *
* @see #onCreateOptionsMenu
*/
public boolean onOptionsItemSelected(MenuItem item) {
@@ -1503,13 +1546,13 @@
/**
* This hook is called whenever the options menu is being closed (either by the user canceling
* the menu with the back/menu button, or when an item is selected).
- *
+ *
* @param menu The options menu as last shown or first initialized by
* onCreateOptionsMenu().
*/
public void onOptionsMenuClosed(Menu menu) {
}
-
+
/**
* Called when a context menu for the {@code view} is about to be shown.
* Unlike {@link #onCreateOptionsMenu}, this will be called every
@@ -1537,25 +1580,25 @@
* {@link OnCreateContextMenuListener} on the view to this fragment, so
* {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
* called when it is time to show the context menu.
- *
+ *
* @see #unregisterForContextMenu(View)
* @param view The view that should show a context menu.
*/
public void registerForContextMenu(View view) {
view.setOnCreateContextMenuListener(this);
}
-
+
/**
* Prevents a context menu to be shown for the given view. This method will
* remove the {@link OnCreateContextMenuListener} on the view.
- *
+ *
* @see #registerForContextMenu(View)
* @param view The view that should stop showing a context menu.
*/
public void unregisterForContextMenu(View view) {
view.setOnCreateContextMenuListener(null);
}
-
+
/**
* This hook is called whenever an item in a context menu is selected. The
* default implementation simply returns false to have the normal processing
@@ -1568,7 +1611,7 @@
* <p>
* Derived classes should call through to the base class for it to perform
* the default menu handling.
- *
+ *
* @param item The context menu item that was selected.
* @return boolean Return false to allow normal context menu processing to
* proceed, true to consume it here.
@@ -1576,7 +1619,284 @@
public boolean onContextItemSelected(MenuItem item) {
return false;
}
-
+
+ /**
+ * When custom transitions are used with Fragments, the enter transition listener
+ * is called when this Fragment is attached or detached when not popping the back stack.
+ *
+ * @param listener Used to manipulate the shared element transitions on this Fragment
+ * when added not as a pop from the back stack.
+ */
+ public void setEnterSharedElementTransitionListener(SharedElementListener listener) {
+ if (listener == null) {
+ listener = SharedElementListener.NULL_LISTENER;
+ }
+ mEnterTransitionListener = listener;
+ }
+
+ /**
+ * When custom transitions are used with Fragments, the exit transition listener
+ * is called when this Fragment is attached or detached when popping the back stack.
+ *
+ * @param listener Used to manipulate the shared element transitions on this Fragment
+ * when added as a pop from the back stack.
+ */
+ public void setExitSharedElementTransitionListener(SharedElementListener listener) {
+ if (listener == null) {
+ listener = SharedElementListener.NULL_LISTENER;
+ }
+ mExitTransitionListener = listener;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected.
+ *
+ * @param transition The Transition to use to move Views into the initial Scene.
+ * @attr ref android.R.styleable#Fragment_fragmentEnterTransition
+ */
+ public void setEnterTransition(Transition transition) {
+ mEnterTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}.
+ *
+ * @return the Transition to use to move Views into the initial Scene.
+ * @attr ref android.R.styleable#Fragment_fragmentEnterTransition
+ */
+ public Transition getEnterTransition() {
+ return mEnterTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when the Fragment is
+ * preparing to be removed, hidden, or detached because of popping the back stack. The exiting
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#VISIBLE} to {@link View#INVISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected. If nothing is set, the default will be to
+ * use the same value as set in {@link #setEnterTransition(android.transition.Transition)}.
+ *
+ * @param transition The Transition to use to move Views out of the Scene when the Fragment
+ * is preparing to close.
+ * @attr ref android.R.styleable#Fragment_fragmentExitTransition
+ */
+ public void setReturnTransition(Transition transition) {
+ mReturnTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when the Fragment is
+ * preparing to be removed, hidden, or detached because of popping the back stack. The exiting
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#VISIBLE} to {@link View#INVISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected.
+ *
+ * @return the Transition to use to move Views out of the Scene when the Fragment
+ * is preparing to close.
+ * @attr ref android.R.styleable#Fragment_fragmentExitTransition
+ */
+ public Transition getReturnTransition() {
+ return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+ : mReturnTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when the
+ * fragment is removed, hidden, or detached when not popping the back stack.
+ * The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected.
+ *
+ * @param transition The Transition to use to move Views out of the Scene when the Fragment
+ * is being closed not due to popping the back stack.
+ * @attr ref android.R.styleable#Fragment_fragmentExitTransition
+ */
+ public void setExitTransition(Transition transition) {
+ mExitTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when the
+ * fragment is removed, hidden, or detached when not popping the back stack.
+ * The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected.
+ *
+ * @return the Transition to use to move Views out of the Scene when the Fragment
+ * is being closed not due to popping the back stack.
+ * @attr ref android.R.styleable#Fragment_fragmentExitTransition
+ */
+ public Transition getExitTransition() {
+ return mExitTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views in to the scene when returning due
+ * to popping a back stack. The entering Views will be those that are regular Views
+ * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+ * will extend {@link android.transition.Visibility} as exiting is governed by changing
+ * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null,
+ * the views will remain unaffected. If nothing is set, the default will be to use the same
+ * transition as {@link #setExitTransition(android.transition.Transition)}.
+ *
+ * @param transition The Transition to use to move Views into the scene when reentering from a
+ * previously-started Activity.
+ * @attr ref android.R.styleable#Fragment_fragmentReenterTransition
+ */
+ public void setReenterTransition(Transition transition) {
+ mReenterTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views in to the scene when returning due
+ * to popping a back stack. The entering Views will be those that are regular Views
+ * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+ * will extend {@link android.transition.Visibility} as exiting is governed by changing
+ * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null,
+ * the views will remain unaffected. If nothing is set, the default will be to use the same
+ * transition as {@link #setExitTransition(android.transition.Transition)}.
+ *
+ * @return the Transition to use to move Views into the scene when reentering from a
+ * previously-started Activity.
+ * @attr ref android.R.styleable#Fragment_fragmentReenterTransition
+ */
+ public Transition getReenterTransition() {
+ return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+ : mReenterTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ *
+ * @param transition The Transition to use for shared elements transferred into the content
+ * Scene.
+ * @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
+ */
+ public void setSharedElementEnterTransition(Transition transition) {
+ mSharedElementEnterTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ *
+ * @return The Transition to use for shared elements transferred into the content
+ * Scene.
+ * @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
+ */
+ public Transition getSharedElementEnterTransition() {
+ return mSharedElementEnterTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred back during a
+ * pop of the back stack. This Transition acts in the leaving Fragment.
+ * Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * If no value is set, the default will be to use the same value as
+ * {@link #setSharedElementEnterTransition(android.transition.Transition)}.
+ *
+ * @param transition The Transition to use for shared elements transferred out of the content
+ * Scene.
+ * @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
+ */
+ public void setSharedElementReturnTransition(Transition transition) {
+ mSharedElementReturnTransition = transition;
+ }
+
+ /**
+ * Return the Transition that will be used for shared elements transferred back during a
+ * pop of the back stack. This Transition acts in the leaving Fragment.
+ * Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * If no value is set, the default will be to use the same value as
+ * {@link #setSharedElementEnterTransition(android.transition.Transition)}.
+ *
+ * @return The Transition to use for shared elements transferred out of the content
+ * Scene.
+ * @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
+ */
+ public Transition getSharedElementReturnTransition() {
+ return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION ?
+ getSharedElementEnterTransition() : mSharedElementReturnTransition;
+ }
+
+ /**
+ * Sets whether the the exit transition and enter transition overlap or not.
+ * When true, the enter transition will start as soon as possible. When false, the
+ * enter transition will wait until the exit transition completes before starting.
+ *
+ * @param allow true to start the enter transition when possible or false to
+ * wait until the exiting transition completes.
+ * @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
+ */
+ public void setAllowEnterTransitionOverlap(boolean allow) {
+ mAllowEnterTransitionOverlap = allow;
+ }
+
+ /**
+ * Returns whether the the exit transition and enter transition overlap or not.
+ * When true, the enter transition will start as soon as possible. When false, the
+ * enter transition will wait until the exit transition completes before starting.
+ *
+ * @return true when the enter transition should start as soon as possible or false to
+ * when it should wait until the exiting transition completes.
+ * @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
+ */
+ public boolean getAllowEnterTransitionOverlap() {
+ return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+ }
+
+ /**
+ * Sets whether the the return transition and reenter transition overlap or not.
+ * When true, the reenter transition will start as soon as possible. When false, the
+ * reenter transition will wait until the return transition completes before starting.
+ *
+ * @param allow true to start the reenter transition when possible or false to wait until the
+ * return transition completes.
+ * @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
+ */
+ public void setAllowReturnTransitionOverlap(boolean allow) {
+ mAllowReturnTransitionOverlap = allow;
+ }
+
+ /**
+ * Returns whether the the return transition and reenter transition overlap or not.
+ * When true, the reenter transition will start as soon as possible. When false, the
+ * reenter transition will wait until the return transition completes before starting.
+ *
+ * @return true to start the reenter transition when possible or false to wait until the
+ * return transition completes.
+ * @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
+ */
+ public boolean getAllowReturnTransitionOverlap() {
+ return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
+ }
+
/**
* Print the Fragments's state into the given stream.
*
@@ -1588,53 +1908,53 @@
*/
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
writer.print(prefix); writer.print("mFragmentId=#");
- writer.print(Integer.toHexString(mFragmentId));
- writer.print(" mContainerId=#");
- writer.print(Integer.toHexString(mContainerId));
- writer.print(" mTag="); writer.println(mTag);
+ writer.print(Integer.toHexString(mFragmentId));
+ writer.print(" mContainerId=#");
+ writer.print(Integer.toHexString(mContainerId));
+ writer.print(" mTag="); writer.println(mTag);
writer.print(prefix); writer.print("mState="); writer.print(mState);
- writer.print(" mIndex="); writer.print(mIndex);
- writer.print(" mWho="); writer.print(mWho);
- writer.print(" mBackStackNesting="); writer.println(mBackStackNesting);
+ writer.print(" mIndex="); writer.print(mIndex);
+ writer.print(" mWho="); writer.print(mWho);
+ writer.print(" mBackStackNesting="); writer.println(mBackStackNesting);
writer.print(prefix); writer.print("mAdded="); writer.print(mAdded);
- writer.print(" mRemoving="); writer.print(mRemoving);
- writer.print(" mResumed="); writer.print(mResumed);
- writer.print(" mFromLayout="); writer.print(mFromLayout);
- writer.print(" mInLayout="); writer.println(mInLayout);
+ writer.print(" mRemoving="); writer.print(mRemoving);
+ writer.print(" mResumed="); writer.print(mResumed);
+ writer.print(" mFromLayout="); writer.print(mFromLayout);
+ writer.print(" mInLayout="); writer.println(mInLayout);
writer.print(prefix); writer.print("mHidden="); writer.print(mHidden);
- writer.print(" mDetached="); writer.print(mDetached);
- writer.print(" mMenuVisible="); writer.print(mMenuVisible);
- writer.print(" mHasMenu="); writer.println(mHasMenu);
+ writer.print(" mDetached="); writer.print(mDetached);
+ writer.print(" mMenuVisible="); writer.print(mMenuVisible);
+ writer.print(" mHasMenu="); writer.println(mHasMenu);
writer.print(prefix); writer.print("mRetainInstance="); writer.print(mRetainInstance);
- writer.print(" mRetaining="); writer.print(mRetaining);
- writer.print(" mUserVisibleHint="); writer.println(mUserVisibleHint);
+ writer.print(" mRetaining="); writer.print(mRetaining);
+ writer.print(" mUserVisibleHint="); writer.println(mUserVisibleHint);
if (mFragmentManager != null) {
writer.print(prefix); writer.print("mFragmentManager=");
- writer.println(mFragmentManager);
+ writer.println(mFragmentManager);
}
if (mActivity != null) {
writer.print(prefix); writer.print("mActivity=");
- writer.println(mActivity);
+ writer.println(mActivity);
}
if (mParentFragment != null) {
writer.print(prefix); writer.print("mParentFragment=");
- writer.println(mParentFragment);
+ writer.println(mParentFragment);
}
if (mArguments != null) {
writer.print(prefix); writer.print("mArguments="); writer.println(mArguments);
}
if (mSavedFragmentState != null) {
writer.print(prefix); writer.print("mSavedFragmentState=");
- writer.println(mSavedFragmentState);
+ writer.println(mSavedFragmentState);
}
if (mSavedViewState != null) {
writer.print(prefix); writer.print("mSavedViewState=");
- writer.println(mSavedViewState);
+ writer.println(mSavedViewState);
}
if (mTarget != null) {
writer.print(prefix); writer.print("mTarget="); writer.print(mTarget);
- writer.print(" mTargetRequestCode=");
- writer.println(mTargetRequestCode);
+ writer.print(" mTargetRequestCode=");
+ writer.println(mTargetRequestCode);
}
if (mNextAnim != 0) {
writer.print(prefix); writer.print("mNextAnim="); writer.println(mNextAnim);
@@ -1648,7 +1968,7 @@
if (mAnimatingAway != null) {
writer.print(prefix); writer.print("mAnimatingAway="); writer.println(mAnimatingAway);
writer.print(prefix); writer.print("mStateAfterAnimating=");
- writer.println(mStateAfterAnimating);
+ writer.println(mStateAfterAnimating);
}
if (mLoaderManager != null) {
writer.print(prefix); writer.println("Loader Manager:");
@@ -1886,7 +2206,7 @@
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onStop()");
}
-
+
if (mLoadersStarted) {
mLoadersStarted = false;
if (!mCheckedForLoaderManager) {
@@ -1929,4 +2249,23 @@
+ " did not call through to super.onDestroy()");
}
}
+
+ private static Transition loadTransition(Context context, TypedArray typedArray,
+ Transition currentValue, Transition defaultValue, int id) {
+ if (currentValue != defaultValue) {
+ return currentValue;
+ }
+ int transitionId = typedArray.getResourceId(id, 0);
+ Transition transition = defaultValue;
+ if (transitionId != 0 && transitionId != com.android.internal.R.transition.no_transition) {
+ TransitionInflater inflater = TransitionInflater.from(context);
+ transition = inflater.inflateTransition(transitionId);
+ if (transition instanceof TransitionSet &&
+ ((TransitionSet)transition).getTransitionCount() == 0) {
+ transition = null;
+ }
+ }
+ return transition;
+ }
+
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 1df1d42..ef69fdd 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1497,7 +1497,10 @@
return false;
}
final BackStackRecord bss = mBackStack.remove(last);
- bss.popFromBackStack(true, null);
+ SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
+ SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
+ bss.calculateBackFragments(firstOutFragments, lastInFragments);
+ bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
reportBackStackChanged();
} else {
int index = -1;
@@ -1541,10 +1544,16 @@
states.add(mBackStack.remove(i));
}
final int LAST = states.size()-1;
+ SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
+ SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
+ for (int i=0; i<=LAST; i++) {
+ states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
+ }
BackStackRecord.TransitionState state = null;
for (int i=0; i<=LAST; i++) {
if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
- state = states.get(i).popFromBackStack(i == LAST, state);
+ state = states.get(i).popFromBackStack(i == LAST, state,
+ firstOutFragments, lastInFragments);
}
reportBackStackChanged();
}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 1077bac..25cd3cc 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -172,19 +172,16 @@
public abstract FragmentTransaction setTransition(int transit);
/**
- * Set a {@link android.transition.Transition} resource id to use with this transaction.
- * <var>transitionId</var> will be played for fragments when going forward and when popping
- * the back stack.
- * @param sceneRootId The ID of the element acting as the scene root for the transition.
- * This should be a ViewGroup containing all Fragments in the transaction.
- * @param transitionId The resource ID for the Transition used during the Fragment transaction.
+ * TODO: remove from API
+ * @hide
*/
- public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId);
+ public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) {
+ return this;
+ }
/**
- * Used with {@link #setCustomTransition(int, int)} to map a View from a removed or hidden
- * Fragment to a View from a shown or added Fragment.
- * <var>sharedElement</var> must have a unique transitionName in the View hierarchy.
+ * Used with to map a View from a removed or hidden Fragment to a View from a shown
+ * or added Fragment.
* @param sharedElement A View in a disappearing Fragment to match with a View in an
* appearing Fragment.
* @param name The transitionName for a View in an appearing Fragment to match to the shared
diff --git a/core/java/android/transition/TransitionUtils.java b/core/java/android/transition/TransitionUtils.java
index b0c9e9a..a84ecd1 100644
--- a/core/java/android/transition/TransitionUtils.java
+++ b/core/java/android/transition/TransitionUtils.java
@@ -40,6 +40,33 @@
}
}
+ public static Transition mergeTransitions(Transition... transitions) {
+ int count = 0;
+ int nonNullIndex = -1;
+ for (int i = 0; i < transitions.length; i++) {
+ if (transitions[i] != null) {
+ count++;
+ nonNullIndex = i;
+ }
+ }
+
+ if (count == 0) {
+ return null;
+ }
+
+ if (count == 1) {
+ return transitions[nonNullIndex];
+ }
+
+ TransitionSet transitionSet = new TransitionSet();
+ for (int i = 0; i < transitions.length; i++) {
+ if (transitions[i] != null) {
+ transitionSet.addTransition(transitions[i]);
+ }
+ }
+ return transitionSet;
+ }
+
public static class MatrixEvaluator implements TypeEvaluator<Matrix> {
float[] mTempStartValues = new float[9];
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 9b6f200..ebc683a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1430,7 +1430,9 @@
* {@link android.transition.Visibility} as entering is governed by changing visibility from
* {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
* entering Views will remain unaffected.
+ *
* @param transition The Transition to use to move Views into the initial Scene.
+ * @attr ref android.R.styleable#Window_windowEnterTransition
*/
public void setEnterTransition(Transition transition) {}
@@ -1444,8 +1446,10 @@
* {@link View#VISIBLE} to {@link View#INVISIBLE}. If <code>transition</code> is null,
* entering Views will remain unaffected. If nothing is set, the default will be to
* use the same value as set in {@link #setEnterTransition(android.transition.Transition)}.
+ *
* @param transition The Transition to use to move Views out of the Scene when the Window
* is preparing to close.
+ * @attr ref android.R.styleable#Window_windowReturnTransition
*/
public void setReturnTransition(Transition transition) {}
@@ -1456,8 +1460,10 @@
* {@link android.transition.Visibility} as exiting is governed by changing visibility
* from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
* remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @param transition The Transition to use to move Views out of the scene when calling a
* new Activity.
+ * @attr ref android.R.styleable#Window_windowExitTransition
*/
public void setExitTransition(Transition transition) {}
@@ -1470,8 +1476,10 @@
* the views will remain unaffected. If nothing is set, the default will be to use the same
* transition as {@link #setExitTransition(android.transition.Transition)}.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @param transition The Transition to use to move Views into the scene when reentering from a
* previously-started Activity.
+ * @attr ref android.R.styleable#Window_windowReenterTransition
*/
public void setReenterTransition(Transition transition) {}
@@ -1484,6 +1492,7 @@
* entering Views will remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
*
* @return the Transition to use to move Views into the initial Scene.
+ * @attr ref android.R.styleable#Window_windowEnterTransition
*/
public Transition getEnterTransition() { return null; }
@@ -1495,8 +1504,10 @@
* {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
* {@link android.transition.Visibility} as entering is governed by changing visibility from
* {@link View#VISIBLE} to {@link View#INVISIBLE}.
+ *
* @return The Transition to use to move Views out of the Scene when the Window
* is preparing to close.
+ * @attr ref android.R.styleable#Window_windowReturnTransition
*/
public Transition getReturnTransition() { return null; }
@@ -1507,8 +1518,10 @@
* {@link android.transition.Visibility} as exiting is governed by changing visibility
* from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
* remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @return the Transition to use to move Views out of the scene when calling a
* new Activity.
+ * @attr ref android.R.styleable#Window_windowExitTransition
*/
public Transition getExitTransition() { return null; }
@@ -1519,8 +1532,10 @@
* will extend {@link android.transition.Visibility} as exiting is governed by changing
* visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @return The Transition to use to move Views into the scene when reentering from a
* previously-started Activity.
+ * @attr ref android.R.styleable#Window_windowReenterTransition
*/
public Transition getReenterTransition() { return null; }
@@ -1530,8 +1545,10 @@
* {@link android.transition.ChangeBounds}. A null
* value will cause transferred shared elements to blink to the final position.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @param transition The Transition to use for shared elements transferred into the content
* Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementEnterTransition
*/
public void setSharedElementEnterTransition(Transition transition) {}
@@ -1543,22 +1560,28 @@
* If no value is set, the default will be to use the same value as
* {@link #setSharedElementEnterTransition(android.transition.Transition)}.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @param transition The Transition to use for shared elements transferred out of the content
* Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementReturnTransition
*/
public void setSharedElementReturnTransition(Transition transition) {}
/**
* Returns the Transition that will be used for shared elements transferred into the content
* Scene. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @return Transition to use for sharend elements transferred into the content Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementEnterTransition
*/
public Transition getSharedElementEnterTransition() { return null; }
/**
* Returns the Transition that will be used for shared elements transferred back to a
* calling Activity. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @return Transition to use for sharend elements transferred into the content Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementReturnTransition
*/
public Transition getSharedElementReturnTransition() { return null; }
@@ -1568,8 +1591,10 @@
* must animate during the exit transition, this Transition should be used. Upon completion,
* the shared elements may be transferred to the started Activity.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @param transition The Transition to use for shared elements in the launching Window
* prior to transferring to the launched Activity's Window.
+ * @attr ref android.R.styleable#Window_windowSharedElementExitTransition
*/
public void setSharedElementExitTransition(Transition transition) {}
@@ -1579,8 +1604,10 @@
* is set, this will default to
* {@link #setSharedElementExitTransition(android.transition.Transition)}.
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
* @param transition The Transition to use for shared elements in the launching Window
* after the shared element has returned to the Window.
+ * @attr ref android.R.styleable#Window_windowSharedElementReenterTransition
*/
public void setSharedElementReenterTransition(Transition transition) {}
@@ -1591,6 +1618,7 @@
*
* @return the Transition to use for shared elements in the launching Window prior
* to transferring to the launched Activity's Window.
+ * @attr ref android.R.styleable#Window_windowSharedElementExitTransition
*/
public Transition getSharedElementExitTransition() { return null; }
@@ -1601,6 +1629,7 @@
*
* @return the Transition that will be used for shared elements reentering from a started
* Activity after it has returned the shared element to it start location.
+ * @attr ref android.R.styleable#Window_windowSharedElementReenterTransition
*/
public Transition getSharedElementReenterTransition() { return null; }
@@ -1610,8 +1639,10 @@
* transition of the calling Activity. When true, the transition will start as soon as possible.
* When false, the transition will wait until the remote exiting transition completes before
* starting.
+ *
* @param allow true to start the enter transition when possible or false to
* wait until the exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowEnterTransitionOverlap
*/
public void setAllowEnterTransitionOverlap(boolean allow) {}
@@ -1621,8 +1652,10 @@
* transition of the calling Activity. When true, the transition will start as soon as possible.
* When false, the transition will wait until the remote exiting transition completes before
* starting.
+ *
* @return true when the enter transition should start as soon as possible or false to
* when it should wait until the exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowEnterTransitionOverlap
*/
public boolean getAllowEnterTransitionOverlap() { return true; }
@@ -1632,10 +1665,20 @@
* transition of the called Activity when reentering after if finishes. When true,
* the transition will start as soon as possible. When false, the transition will wait
* until the called Activity's exiting transition completes before starting.
+ *
* @param allow true to start the transition when possible or false to wait until the
* called Activity's exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowReturnTransitionOverlap
*/
- public void setAllowExitTransitionOverlap(boolean allow) {}
+ public void setAllowReturnTransitionOverlap(boolean allow) {}
+
+ /**
+ * TODO: remove this.
+ * @hide
+ */
+ public void setAllowExitTransitionOverlap(boolean allow) {
+ setAllowReturnTransitionOverlap(allow);
+ }
/**
* Returns how the transition set in
@@ -1643,10 +1686,18 @@
* transition of the called Activity when reentering after if finishes. When true,
* the transition will start as soon as possible. When false, the transition will wait
* until the called Activity's exiting transition completes before starting.
+ *
* @return true when the transition should start when possible or false when it should wait
* until the called Activity's exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowReturnTransitionOverlap
*/
- public boolean getAllowExitTransitionOverlap() { return true; }
+ public boolean getAllowReturnTransitionOverlap() { return true; }
+
+ /**
+ * TODO: remove this.
+ * @hide
+ */
+ public boolean getAllowExitTransitionOverlap() { return getAllowReturnTransitionOverlap(); }
/**
* Returns the duration, in milliseconds, of the window background fade
@@ -1654,8 +1705,10 @@
* <p>When executing the enter transition, the background starts transparent
* and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is
* 300 milliseconds.</p>
+ *
* @return The duration of the window background fade to opaque during enter transition.
* @see #getEnterTransition()
+ * @attr ref android.R.styleable#Window_windowTransitionBackgroundFadeDuration
*/
public long getTransitionBackgroundFadeDuration() { return 0; }
@@ -1665,9 +1718,11 @@
* <p>When executing the enter transition, the background starts transparent
* and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is
* 300 milliseconds.</p>
+ *
* @param fadeDurationMillis The duration of the window background fade to or from opaque
* during enter transition.
* @see #setEnterTransition(android.transition.Transition)
+ * @attr ref android.R.styleable#Window_windowTransitionBackgroundFadeDuration
*/
public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { }
@@ -1679,6 +1734,7 @@
* @return <code>true</code> when shared elements should use an Overlay during
* shared element transitions or <code>false</code> when they should animate as
* part of the normal View hierarchy.
+ * @attr ref android.R.styleable#Window_windowSharedElementsUseOverlay
*/
public boolean getSharedElementsUseOverlay() { return true; }
@@ -1689,6 +1745,7 @@
* @param sharedElementsUseOverlay <code>true</code> indicates that shared elements should
* be transitioned with an Overlay or <code>false</code>
* to transition within the normal View hierarchy.
+ * @attr ref android.R.styleable#Window_windowSharedElementsUseOverlay
*/
public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) { }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a798d2e..8905329 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -514,8 +514,8 @@
<!-- Flag indicating whether this Window's transition should overlap with
the exiting transition of the called Activity when the called Activity
finishes. Corresponds to
- {@link android.view.Window#setAllowExitTransitionOverlap(boolean)}. -->
- <attr name="windowAllowExitTransitionOverlap" format="boolean"/>
+ {@link android.view.Window#setAllowReturnTransitionOverlap(boolean)}. -->
+ <attr name="windowAllowReturnTransitionOverlap" format="boolean"/>
<!-- Indicates whether or not shared elements should use an overlay
during transitions. The default value is true. -->
@@ -1853,8 +1853,8 @@
<!-- Flag indicating whether this Window's transition should overlap with
the exiting transition of the called Activity when the called Activity
finishes. Corresponds to
- {@link android.view.Window#setAllowExitTransitionOverlap(boolean)}. -->
- <attr name="windowAllowExitTransitionOverlap"/>
+ {@link android.view.Window#setAllowReturnTransitionOverlap(boolean)}. -->
+ <attr name="windowAllowReturnTransitionOverlap"/>
<!-- Indicates whether or not shared elements should use an overlay
during transitions. The default value is true. -->
@@ -6627,6 +6627,53 @@
IDs (through the android:id attribute) instead of tags because
they are faster and allow for compile-time type checking. -->
<attr name="tag" />
+
+ <!-- The Transition that will be used to move Views out of the scene when the
+ fragment is removed, hidden, or detached when not popping the back stack.
+ Corresponds to {@link android.app.Fragment#setExitTransition(
+ android.transition.Transition)} -->
+ <attr name="fragmentExitTransition" format="reference"/>
+
+ <!-- The Transition that will be used to move Views into the initial scene.
+ Corresponds to {@link android.app.Fragment#setEnterTransition(
+ android.transition.Transition)} -->
+ <attr name="fragmentEnterTransition" format="reference"/>
+
+ <!-- The Transition that will be used for shared elements transferred into the content
+ Scene.
+ Corresponds to {@link android.app.Fragment#setSharedElementEnterTransition(
+ android.transition.Transition)} -->
+ <attr name="fragmentSharedElementEnterTransition" format="reference"/>
+
+ <!-- The Transition that will be used to move Views out of the scene when the Fragment is
+ preparing to be removed, hidden, or detached because of popping the back stack.
+ Corresponds to {@link android.app.Fragment#setReturnTransition(
+ android.transition.Transition)} -->
+ <attr name="fragmentReturnTransition" format="reference"/>
+
+ <!-- The Transition that will be used for shared elements transferred back during a
+ pop of the back stack. This Transition acts in the leaving Fragment.
+ Corresponds to {@link android.app.Fragment#setSharedElementReturnTransition(
+ android.transition.Transition)} -->
+ <attr name="fragmentSharedElementReturnTransition" format="reference"/>
+
+ <!-- The Transition that will be used to move Views in to the scene when returning due
+ to popping a back stack.
+ Corresponds to {@link android.app.Fragment#setReenterTransition(
+ android.transition.Transition)} -->
+ <attr name="fragmentReenterTransition" format="reference"/>
+
+ <!-- Sets whether the enter and exit transitions should overlap when transitioning
+ forward.
+ Corresponds to {@link android.app.Fragment#setAllowEnterTransitionOverlap(
+ boolean)} -->
+ <attr name="fragmentAllowEnterTransitionOverlap" format="reference"/>
+
+ <!-- Sets whether the enter and exit transitions should overlap when transitioning
+ because of popping the back stack.
+ Corresponds to {@link android.app.Fragment#setAllowReturnTransitionOverlap(
+ boolean)} -->
+ <attr name="fragmentAllowReturnTransitionOverlap" format="reference"/>
</declare-styleable>
<!-- Use <code>device-admin</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5e76a87..75c0e2c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2146,7 +2146,7 @@
<public type="attr" name="windowExitTransition" />
<public type="attr" name="windowSharedElementEnterTransition" />
<public type="attr" name="windowSharedElementExitTransition" />
- <public type="attr" name="windowAllowExitTransitionOverlap" />
+ <public type="attr" name="windowAllowReturnTransitionOverlap" />
<public type="attr" name="windowAllowEnterTransitionOverlap" />
<public type="attr" name="sessionService" />
<public type="attr" name="stackViewStyle" />
@@ -2282,6 +2282,14 @@
<public type="attr" name="spotShadowAlpha" />
<public type="attr" name="navigationIcon" />
<public type="attr" name="navigationContentDescription" />
+ <public type="attr" name="fragmentExitTransition" />
+ <public type="attr" name="fragmentEnterTransition" />
+ <public type="attr" name="fragmentSharedElementEnterTransition" />
+ <public type="attr" name="fragmentReturnTransition" />
+ <public type="attr" name="fragmentSharedElementReturnTransition" />
+ <public type="attr" name="fragmentReenterTransition" />
+ <public type="attr" name="fragmentAllowEnterTransitionOverlap" />
+ <public type="attr" name="fragmentAllowReturnTransitionOverlap" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />