diff options
author | George Mount <mount@google.com> | 2014-08-22 17:04:02 -0700 |
---|---|---|
committer | George Mount <mount@google.com> | 2014-09-02 15:15:12 -0700 |
commit | c03da0e7a9ef721709d51cf8a2d539a5bd8a320e (patch) | |
tree | 41ffad1cec7f1f4b19615a2bb5262183048439f6 /core/java/android | |
parent | 8fd8124911958ae454c7306698e2f423b0b11e39 (diff) | |
download | frameworks_base-c03da0e7a9ef721709d51cf8a2d539a5bd8a320e.zip frameworks_base-c03da0e7a9ef721709d51cf8a2d539a5bd8a320e.tar.gz frameworks_base-c03da0e7a9ef721709d51cf8a2d539a5bd8a320e.tar.bz2 |
Make Fragment Transitions match Acitivty Transitions API
Bug 17188255
Change-Id: I506a097be4010d7156caf465c95295c58612c16e
Diffstat (limited to 'core/java/android')
-rw-r--r-- | core/java/android/app/BackStackRecord.java | 763 | ||||
-rw-r--r-- | core/java/android/app/Fragment.java | 583 | ||||
-rw-r--r-- | core/java/android/app/FragmentManager.java | 13 | ||||
-rw-r--r-- | core/java/android/app/FragmentTransaction.java | 17 | ||||
-rw-r--r-- | core/java/android/transition/TransitionUtils.java | 27 | ||||
-rw-r--r-- | core/java/android/view/Window.java | 61 |
6 files changed, 1189 insertions, 275 deletions
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.Parcel; 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 class BackStackState implements Parcelable { 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 @@ final class BackStackState implements Parcelable { 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 @@ final class BackStackState implements Parcelable { 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 @@ final class BackStackState implements Parcelable { 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 @@ final class BackStackState implements Parcelable { 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 @@ final class BackStackRecord extends FragmentTransaction implements int mBreadCrumbShortTitleRes; CharSequence mBreadCrumbShortTitleText; - int mCustomTransition; - int mSceneRoot; ArrayList<String> mSharedElementSourceNames; ArrayList<String> mSharedElementTargetNames; @@ -573,13 +563,6 @@ final class BackStackRecord extends FragmentTransaction implements } @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 @@ final class BackStackRecord extends FragmentTransaction implements 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,144 +844,608 @@ final class BackStackRecord extends FragmentTransaction implements } 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); + } + } + } + + 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; + } } - View rootView = mManager.mContainer.findViewById(mSceneRoot); - if (!(rootView instanceof ViewGroup)) { - throw new IllegalArgumentException("SceneRoot is not a ViewGroup"); + 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(); - // 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); + // 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); - - setSharedElementEpicenter(state.enterTransition, state); - - state.excludingTransition = new TransitionSet() - .addTransition(state.exitTransition) - .addTransition(state.enterTransition); + state.nonExistentView = new View(mManager.mActivity); + + 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>(); + + // 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); + } - 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); + // 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); } - state.transitioningViews.removeAll(state.namedViews.values()); - state.excludingTransition.addTransition(state.sharedElementTransition); - addTransitioningViews(state.sharedElementTransition, state.namedViews.values()); } - // Adds the (maybe) exiting views, not including the shared element. - // If some stay, that's ok. - addTransitioningViews(state.exitTransition, state.transitioningViews); + if (state.overallTransitions.size() == 0) { + state = null; + } + return state; + } - // 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); + private static Transition getEnterTransition(Fragment inFragment, boolean isBack) { + if (inFragment == null) { + return null; + } + return isBack ? inFragment.getReenterTransition() : inFragment.getEnterTransition(); + } - // 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); + private static Transition getExitTransition(Fragment outFragment, boolean isBack) { + if (outFragment == null) { + return null; + } + return isBack ? outFragment.getReturnTransition() : outFragment.getExitTransition(); + } - TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition); - return state; + private static Transition getSharedElementTransition(Fragment inFragment, Fragment outFragment, + boolean isBack) { + if (inFragment == null || outFragment == null) { + return null; + } + return isBack ? outFragment.getSharedElementReturnTransition() : + inFragment.getSharedElementEnterTransition(); } - 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); - - 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 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); + if (isBack) { + outFragment.mEnterTransitionListener.remapSharedElements( + mSharedElementTargetNames, namedViews); + setBackNameOverrides(state, namedViews, false); + } else { + outFragment.mExitTransitionListener.remapSharedElements( + mSharedElementTargetNames, namedViews); + setNameOverrides(state, namedViews, false); + } - // Don't allow capturing state for the newly-hidden fragments. - excludeHiddenFragments(state, false); + return namedViews; + } - // Allow capturing state for the newly-shown fragments - includeVisibleFragments(state.excludingTransition); + /** + * 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 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)); + 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 { - for (View view : views) { - transition.addTarget(view); + 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 void includeVisibleFragments(Transition transition) { + 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.mHidden) { - transition.excludeTarget(fragment.mView, false); + 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 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) { final Rect epicenter = new Rect(); view.getBoundsOnScreen(epicenter); @@ -1010,16 +1464,17 @@ final class BackStackRecord extends FragmentTransaction implements @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 @@ final class BackStackRecord extends FragmentTransaction implements } 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 @@ final class BackStackRecord extends FragmentTransaction implements 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 @@ final class BackStackRecord extends FragmentTransaction implements 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 @@ final class BackStackRecord extends FragmentTransaction implements 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 @@ final class BackStackRecord extends FragmentTransaction implements } 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.Context; 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 class FragmentState implements Parcelable { 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 @@ final class FragmentState implements Parcelable { mDetached = frag.mDetached; mArguments = frag.mArguments; } - + public FragmentState(Parcel in) { mClassName = in.readString(); mIndex = in.readInt(); @@ -87,18 +91,18 @@ final class FragmentState implements Parcelable { 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 @@ final class FragmentState implements Parcelable { return mInstance; } - + public int describeContents() { return 0; } @@ -134,13 +138,13 @@ final class FragmentState implements Parcelable { 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 @@ final class FragmentState implements Parcelable { * 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 @@ final class FragmentState implements Parcelable { * <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 @@ final class FragmentState implements Parcelable { 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene + " empty constructor that is public", e); } } - + final void restoreViewState(Bundle savedInstanceState) { if (mSavedViewState != null) { mView.restoreHierarchyState(mSavedViewState); @@ -649,7 +667,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene @Override final public int hashCode() { return super.hashCode(); } - + @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -669,7 +687,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene final public Activity getActivity() { return mActivity; } - + /** * Return <code>getActivity().getResources()</code>. */ @@ -770,7 +788,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } return mActivity.getResources(); } - + /** * Return a localized, styled CharSequence from the application's package's * default string table. @@ -870,7 +888,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene public void startActivity(Intent intent) { startActivity(intent, null); } - + /** * Call {@link Activity#startActivity(Intent, Bundle)} from the fragment's * containing Activity. @@ -1081,13 +1099,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene return mActivity.getLayoutInflater(); } } - + /** * @deprecated Use {@link #onInflate(Activity, AttributeSet, Bundle)} instead. */ @@ -1131,7 +1149,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ public void onStart() { mCalled = true; - + if (!mLoadersStarted) { mLoadersStarted = true; if (!mCheckedForLoaderManager) { @@ -1304,7 +1347,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } } } - + /** * Called when the fragment is visible to the user and actively running. * This is generally @@ -1314,7 +1357,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene public void onStop() { mCalled = true; } - + public void onLowMemory() { mCalled = true; } - + public void onTrimMemory(int level) { mCalled = true; } @@ -1379,7 +1422,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene /** * 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * {@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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * <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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene */ 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene 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 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene throw new SuperNotCalledException("Fragment " + this + " did not call through to super.onStop()"); } - + if (mLoadersStarted) { mLoadersStarted = false; if (!mCheckedForLoaderManager) { @@ -1929,4 +2249,23 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene + " 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 class FragmentTransaction { 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 class TransitionUtils { } } + 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 @@ public abstract class Window { * {@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 @@ public abstract class Window { * {@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 @@ public abstract class Window { * {@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 @@ public abstract class Window { * 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 @@ public abstract class Window { * 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 @@ public abstract class Window { * {@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 @@ public abstract class Window { * {@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 @@ public abstract class Window { * 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 @@ public abstract class Window { * {@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 @@ public abstract class Window { * 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 @@ public abstract class Window { * 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 @@ public abstract class Window { * 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 @@ public abstract class Window { * * @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 @@ public abstract class Window { * * @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 @@ public abstract class Window { * 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 @@ public abstract class Window { * 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 @@ public abstract class Window { * 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 @@ public abstract class Window { * 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 @@ public abstract class Window { * <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 @@ public abstract class Window { * <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 @@ public abstract class Window { * @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 @@ public abstract class Window { * @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) { } |