Merge changes Id7ae3838,I8e297443

* changes:
  Fix NPE when shared elements are empty.
  Allow antroid.transition Transitions in fragments.
This commit is contained in:
George Mount
2014-06-17 20:46:33 +00:00
committed by Android (Google) Code Review
9 changed files with 653 additions and 104 deletions

View File

@@ -4203,6 +4203,9 @@ package android.app {
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int);
method public abstract android.app.FragmentTransaction setCustomTransition(int, int);
method public abstract android.app.FragmentTransaction setSharedElement(android.view.View, java.lang.String);
method public abstract android.app.FragmentTransaction setSharedElements(android.util.Pair<android.view.View, java.lang.String>...);
method public abstract android.app.FragmentTransaction setTransition(int);
method public abstract android.app.FragmentTransaction setTransitionStyle(int);
method public abstract android.app.FragmentTransaction show(android.app.Fragment);

View File

@@ -293,13 +293,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
if (transition == null || views == null || views.isEmpty()) {
return null;
}
// Add the targets to a set containing transition so that transition
// remains unaffected. We don't want to modify the targets of transition itself.
TransitionSet set = new TransitionSet();
set.addTransition(transition);
if (views != null) {
for (View view: views) {
for (View view : views) {
set.addTarget(view);
}
}
// By adding the transition after addTarget, we prevent addTarget from
// affecting transition.
set.addTransition(transition);
return set;
}

View File

@@ -16,36 +16,53 @@
package android.app;
import com.android.internal.util.FastPrintWriter;
import android.graphics.Rect;
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.util.ArrayMap;
import android.util.Log;
import android.util.LogWriter;
import com.android.internal.util.FastPrintWriter;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
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;
final CharSequence mBreadCrumbTitleText;
final int mBreadCrumbShortTitleRes;
final CharSequence mBreadCrumbShortTitleText;
final ArrayList<String> mSharedElementSourceNames;
final ArrayList<String> mSharedElementTargetNames;
public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
int numRemoved = 0;
BackStackRecord.Op op = bse.mHead;
while (op != null) {
if (op.removed != null) numRemoved += op.removed.size();
if (op.removed != null) {
numRemoved += op.removed.size();
}
op = op.next;
}
mOps = new int[bse.mNumOp*7 + numRemoved];
mOps = new int[bse.mNumOp * 7 + numRemoved];
if (!bse.mAddToBackStack) {
throw new IllegalStateException("Not on back stack");
@@ -63,7 +80,7 @@ final class BackStackState implements Parcelable {
if (op.removed != null) {
final int N = op.removed.size();
mOps[pos++] = N;
for (int i=0; i<N; i++) {
for (int i = 0; i < N; i++) {
mOps[pos++] = op.removed.get(i).mIndex;
}
} else {
@@ -79,6 +96,10 @@ 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;
}
public BackStackState(Parcel in) {
@@ -91,6 +112,10 @@ 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();
}
public BackStackRecord instantiate(FragmentManagerImpl fm) {
@@ -100,8 +125,10 @@ final class BackStackState implements Parcelable {
while (pos < mOps.length) {
BackStackRecord.Op op = new BackStackRecord.Op();
op.cmd = mOps[pos++];
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
if (FragmentManagerImpl.DEBUG) {
Log.v(FragmentManagerImpl.TAG,
"Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
}
int findex = mOps[pos++];
if (findex >= 0) {
Fragment f = fm.mActive.get(findex);
@@ -116,9 +143,11 @@ final class BackStackState implements Parcelable {
final int N = mOps[pos++];
if (N > 0) {
op.removed = new ArrayList<Fragment>(N);
for (int i=0; i<N; i++) {
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiate " + bse + " set remove fragment #" + mOps[pos]);
for (int i = 0; i < N; i++) {
if (FragmentManagerImpl.DEBUG) {
Log.v(FragmentManagerImpl.TAG,
"Instantiate " + bse + " set remove fragment #" + mOps[pos]);
}
Fragment r = fm.mActive.get(mOps[pos++]);
op.removed.add(r);
}
@@ -135,6 +164,10 @@ 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);
return bse;
}
@@ -153,6 +186,10 @@ 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);
}
public static final Parcelable.Creator<BackStackState> CREATOR
@@ -217,6 +254,11 @@ final class BackStackRecord extends FragmentTransaction implements
int mBreadCrumbShortTitleRes;
CharSequence mBreadCrumbShortTitleText;
int mCustomTransition;
int mSceneRoot;
ArrayList<String> mSharedElementSourceNames;
ArrayList<String> mSharedElementTargetNames;
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
@@ -240,78 +282,112 @@ final class BackStackRecord extends FragmentTransaction implements
void dump(String prefix, PrintWriter writer, boolean full) {
if (full) {
writer.print(prefix); writer.print("mName="); writer.print(mName);
writer.print(" mIndex="); writer.print(mIndex);
writer.print(" mCommitted="); writer.println(mCommitted);
writer.print(prefix);
writer.print("mName=");
writer.print(mName);
writer.print(" mIndex=");
writer.print(mIndex);
writer.print(" mCommitted=");
writer.println(mCommitted);
if (mTransition != FragmentTransaction.TRANSIT_NONE) {
writer.print(prefix); writer.print("mTransition=#");
writer.print(Integer.toHexString(mTransition));
writer.print(" mTransitionStyle=#");
writer.println(Integer.toHexString(mTransitionStyle));
writer.print(prefix);
writer.print("mTransition=#");
writer.print(Integer.toHexString(mTransition));
writer.print(" mTransitionStyle=#");
writer.println(Integer.toHexString(mTransitionStyle));
}
if (mEnterAnim != 0 || mExitAnim !=0) {
writer.print(prefix); writer.print("mEnterAnim=#");
writer.print(Integer.toHexString(mEnterAnim));
writer.print(" mExitAnim=#");
writer.println(Integer.toHexString(mExitAnim));
if (mEnterAnim != 0 || mExitAnim != 0) {
writer.print(prefix);
writer.print("mEnterAnim=#");
writer.print(Integer.toHexString(mEnterAnim));
writer.print(" mExitAnim=#");
writer.println(Integer.toHexString(mExitAnim));
}
if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
writer.print(prefix); writer.print("mPopEnterAnim=#");
writer.print(Integer.toHexString(mPopEnterAnim));
writer.print(" mPopExitAnim=#");
writer.println(Integer.toHexString(mPopExitAnim));
if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
writer.print(prefix);
writer.print("mPopEnterAnim=#");
writer.print(Integer.toHexString(mPopEnterAnim));
writer.print(" mPopExitAnim=#");
writer.println(Integer.toHexString(mPopExitAnim));
}
if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
writer.print(Integer.toHexString(mBreadCrumbTitleRes));
writer.print(" mBreadCrumbTitleText=");
writer.println(mBreadCrumbTitleText);
writer.print(prefix);
writer.print("mBreadCrumbTitleRes=#");
writer.print(Integer.toHexString(mBreadCrumbTitleRes));
writer.print(" mBreadCrumbTitleText=");
writer.println(mBreadCrumbTitleText);
}
if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
writer.print(" mBreadCrumbShortTitleText=");
writer.println(mBreadCrumbShortTitleText);
writer.print(prefix);
writer.print("mBreadCrumbShortTitleRes=#");
writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
writer.print(" mBreadCrumbShortTitleText=");
writer.println(mBreadCrumbShortTitleText);
}
}
if (mHead != null) {
writer.print(prefix); writer.println("Operations:");
writer.print(prefix);
writer.println("Operations:");
String innerPrefix = prefix + " ";
Op op = mHead;
int num = 0;
while (op != null) {
String cmdStr;
switch (op.cmd) {
case OP_NULL: cmdStr="NULL"; break;
case OP_ADD: cmdStr="ADD"; break;
case OP_REPLACE: cmdStr="REPLACE"; break;
case OP_REMOVE: cmdStr="REMOVE"; break;
case OP_HIDE: cmdStr="HIDE"; break;
case OP_SHOW: cmdStr="SHOW"; break;
case OP_DETACH: cmdStr="DETACH"; break;
case OP_ATTACH: cmdStr="ATTACH"; break;
default: cmdStr="cmd=" + op.cmd; break;
case OP_NULL:
cmdStr = "NULL";
break;
case OP_ADD:
cmdStr = "ADD";
break;
case OP_REPLACE:
cmdStr = "REPLACE";
break;
case OP_REMOVE:
cmdStr = "REMOVE";
break;
case OP_HIDE:
cmdStr = "HIDE";
break;
case OP_SHOW:
cmdStr = "SHOW";
break;
case OP_DETACH:
cmdStr = "DETACH";
break;
case OP_ATTACH:
cmdStr = "ATTACH";
break;
default:
cmdStr = "cmd=" + op.cmd;
break;
}
writer.print(prefix); writer.print(" Op #"); writer.print(num);
writer.print(": "); writer.print(cmdStr);
writer.print(" "); writer.println(op.fragment);
writer.print(prefix);
writer.print(" Op #");
writer.print(num);
writer.print(": ");
writer.print(cmdStr);
writer.print(" ");
writer.println(op.fragment);
if (full) {
if (op.enterAnim != 0 || op.exitAnim != 0) {
writer.print(innerPrefix); writer.print("enterAnim=#");
writer.print(Integer.toHexString(op.enterAnim));
writer.print(" exitAnim=#");
writer.println(Integer.toHexString(op.exitAnim));
writer.print(innerPrefix);
writer.print("enterAnim=#");
writer.print(Integer.toHexString(op.enterAnim));
writer.print(" exitAnim=#");
writer.println(Integer.toHexString(op.exitAnim));
}
if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
writer.print(innerPrefix); writer.print("popEnterAnim=#");
writer.print(Integer.toHexString(op.popEnterAnim));
writer.print(" popExitAnim=#");
writer.println(Integer.toHexString(op.popExitAnim));
writer.print(innerPrefix);
writer.print("popEnterAnim=#");
writer.print(Integer.toHexString(op.popEnterAnim));
writer.print(" popExitAnim=#");
writer.println(Integer.toHexString(op.popExitAnim));
}
}
if (op.removed != null && op.removed.size() > 0) {
for (int i=0; i<op.removed.size(); i++) {
for (int i = 0; i < op.removed.size(); i++) {
writer.print(innerPrefix);
if (op.removed.size() == 1) {
writer.print("Removed: ");
@@ -319,8 +395,10 @@ final class BackStackRecord extends FragmentTransaction implements
if (i == 0) {
writer.println("Removed:");
}
writer.print(innerPrefix); writer.print(" #"); writer.print(i);
writer.print(": ");
writer.print(innerPrefix);
writer.print(" #");
writer.print(i);
writer.print(": ");
}
writer.println(op.removed.get(i));
}
@@ -494,6 +572,51 @@ final class BackStackRecord extends FragmentTransaction implements
return this;
}
@Override
public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) {
mSceneRoot = sceneRootId;
mCustomTransition = transitionId;
return this;
}
@Override
public FragmentTransaction setSharedElement(View sharedElement, String name) {
String viewName = sharedElement.getViewName();
if (viewName == null) {
throw new IllegalArgumentException("Unique viewNames are required for all" +
" sharedElements");
}
mSharedElementSourceNames = new ArrayList<String>(1);
mSharedElementSourceNames.add(viewName);
mSharedElementTargetNames = new ArrayList<String>(1);
mSharedElementTargetNames.add(name);
return this;
}
@Override
public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) {
if (sharedElements == null || sharedElements.length == 0) {
mSharedElementSourceNames = null;
mSharedElementTargetNames = null;
} else {
ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length);
ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length);
for (int i = 0; i < sharedElements.length; i++) {
String viewName = sharedElements[i].first.getViewName();
if (viewName == null) {
throw new IllegalArgumentException("Unique viewNames are required for all" +
" sharedElements");
}
sourceNames.add(viewName);
targetNames.add(sharedElements[i].second);
}
mSharedElementSourceNames = sourceNames;
mSharedElementTargetNames = targetNames;
}
return this;
}
public FragmentTransaction setTransitionStyle(int styleRes) {
mTransitionStyle = styleRes;
return this;
@@ -550,21 +673,27 @@ final class BackStackRecord extends FragmentTransaction implements
if (!mAddToBackStack) {
return;
}
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
+ " by " + amt);
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Bump nesting in " + this
+ " by " + amt);
}
Op op = mHead;
while (op != null) {
if (op.fragment != null) {
op.fragment.mBackStackNesting += amt;
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ op.fragment + " to " + op.fragment.mBackStackNesting);
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Bump nesting of "
+ op.fragment + " to " + op.fragment.mBackStackNesting);
}
}
if (op.removed != null) {
for (int i=op.removed.size()-1; i>=0; i--) {
for (int i = op.removed.size() - 1; i >= 0; i--) {
Fragment r = op.removed.get(i);
r.mBackStackNesting += amt;
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ r + " to " + r.mBackStackNesting);
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Bump nesting of "
+ r + " to " + r.mBackStackNesting);
}
}
}
op = op.next;
@@ -578,9 +707,11 @@ final class BackStackRecord extends FragmentTransaction implements
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (mCommitted) {
throw new IllegalStateException("commit already called");
}
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -597,9 +728,11 @@ final class BackStackRecord extends FragmentTransaction implements
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
public void run() {
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this);
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Run: " + this);
}
if (mAddToBackStack) {
if (mIndex < 0) {
@@ -609,6 +742,9 @@ final class BackStackRecord extends FragmentTransaction implements
bumpBackStackNesting(1);
TransitionState state = beginTransition(mSharedElementSourceNames,
mSharedElementTargetNames);
Op op = mHead;
while (op != null) {
switch (op.cmd) {
@@ -616,14 +752,17 @@ final class BackStackRecord extends FragmentTransaction implements
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
} break;
}
break;
case OP_REPLACE: {
Fragment f = op.fragment;
if (mManager.mAdded != null) {
for (int i=0; i<mManager.mAdded.size(); i++) {
for (int i = 0; i < mManager.mAdded.size(); i++) {
Fragment old = mManager.mAdded.get(i);
if (FragmentManagerImpl.DEBUG) Log.v(TAG,
"OP_REPLACE: adding=" + f + " old=" + old);
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG,
"OP_REPLACE: adding=" + f + " old=" + old);
}
if (f == null || old.mContainerId == f.mContainerId) {
if (old == f) {
op.fragment = f = null;
@@ -635,8 +774,10 @@ final class BackStackRecord extends FragmentTransaction implements
old.mNextAnim = op.exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ old + " to " + old.mBackStackNesting);
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Bump nesting of "
+ old + " to " + old.mBackStackNesting);
}
}
mManager.removeFragment(old, mTransition, mTransitionStyle);
}
@@ -647,32 +788,38 @@ final class BackStackRecord extends FragmentTransaction implements
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
} break;
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.removeFragment(f, mTransition, mTransitionStyle);
} break;
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.hideFragment(f, mTransition, mTransitionStyle);
} break;
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.showFragment(f, mTransition, mTransitionStyle);
} break;
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.detachFragment(f, mTransition, mTransitionStyle);
} break;
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.attachFragment(f, mTransition, mTransitionStyle);
} break;
}
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
@@ -687,9 +834,174 @@ final class BackStackRecord extends FragmentTransaction implements
if (mAddToBackStack) {
mManager.addBackStackState(this);
}
if (state != null) {
updateTransitionEndState(state, mSharedElementTargetNames);
}
}
public void popFromBackStack(boolean doStateMove) {
private TransitionState beginTransition(ArrayList<String> sourceNames,
ArrayList<String> targetNames) {
if (mCustomTransition <= 0 || mSceneRoot <= 0) {
return null;
}
View rootView = mManager.mContainer.findViewById(mSceneRoot);
if (!(rootView instanceof ViewGroup)) {
throw new IllegalArgumentException("SceneRoot is not a ViewGroup");
}
TransitionState state = new TransitionState();
// get Transition scene root and create Transitions
state.sceneRoot = (ViewGroup) rootView;
state.sceneRoot.captureTransitioningViews(state.transitioningViews);
state.exitTransition = TransitionInflater.from(mManager.mActivity)
.inflateTransition(mCustomTransition);
state.sharedElementTransition = TransitionInflater.from(mManager.mActivity)
.inflateTransition(mCustomTransition);
state.enterTransition = TransitionInflater.from(mManager.mActivity)
.inflateTransition(mCustomTransition);
// 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);
if (sourceNames != null) {
// Map shared elements.
state.sceneRoot.findNamedViews(state.namedViews);
state.namedViews.retainAll(sourceNames);
View epicenterView = state.namedViews.get(sourceNames.get(0));
if (epicenterView != null) {
// The epicenter is only the first shared element.
setEpicenter(state.exitTransition, epicenterView);
setEpicenter(state.sharedElementTransition, epicenterView);
}
state.transitioningViews.removeAll(state.namedViews.values());
state.excludingTransition.addTransition(state.sharedElementTransition);
addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
}
// Adds the (maybe) exiting views, not including the shared element.
// If some stay, that's ok.
addTransitioningViews(state.exitTransition, state.transitioningViews);
// Prepare for shared element name mapping. This could be chained in the case
// of popping several back stack states.
state.excludingTransition.setNameOverrides(new ArrayMap<String, String>());
setNameOverrides(state, sourceNames, targetNames);
// Don't include any subtree in the views that are hidden when capturing the
// view hierarchy transitions. They should be as if not there.
excludeHiddenFragments(state, true);
TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition);
return state;
}
private void updateTransitionEndState(TransitionState state, ArrayList<String> names) {
// Find all views that are entering.
ArrayList<View> enteringViews = new ArrayList<View>();
state.sceneRoot.captureTransitioningViews(enteringViews);
enteringViews.removeAll(state.transitioningViews);
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));
}
}
// Add all entering views to the enter transition.
addTransitioningViews(state.enterTransition, enteringViews);
// Don't allow capturing state for the newly-hidden fragments.
excludeHiddenFragments(state, false);
// Allow capturing state for the newly-shown fragments
includeVisibleFragments(state.excludingTransition);
}
private void addTransitioningViews(Transition transition, Collection<View> views) {
if (views.isEmpty()) {
// Add a view so that we can modify the valid views at the end of the
// fragment transaction.
transition.addTarget(new View(mManager.mActivity));
} else {
for (View view : views) {
transition.addTarget(view);
}
}
}
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);
}
}
}
if (forceExclude && state.hiddenViews.isEmpty()) {
state.excludingTransition.excludeTarget(new View(mManager.mActivity), true);
}
}
private void includeVisibleFragments(Transition transition) {
if (mManager.mAdded != null) {
for (int i = 0; i < mManager.mAdded.size(); i++) {
Fragment fragment = mManager.mAdded.get(i);
if (fragment.mView != null && !fragment.mHidden) {
transition.excludeTarget(fragment.mView, false);
}
}
}
}
private static void setEpicenter(Transition transition, View view) {
final Rect epicenter = new Rect();
view.getBoundsOnScreen(epicenter);
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return epicenter;
}
});
}
private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
private Rect mEpicenter;
@Override
public Rect onGetEpicenter(Transition transition) {
if (mEpicenter == null && state.mEnteringEpicenterView != null) {
mEpicenter = new Rect();
state.mEnteringEpicenterView.getBoundsOnScreen(mEpicenter);
}
return mEpicenter;
}
});
}
public TransitionState popFromBackStack(boolean doStateMove, TransitionState state) {
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "popFromBackStack: " + this);
LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -698,6 +1010,12 @@ final class BackStackRecord extends FragmentTransaction implements
pw.flush();
}
if (state == null) {
state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames);
} else {
setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
}
bumpBackStackNesting(-1);
Op op = mTail;
@@ -709,7 +1027,8 @@ final class BackStackRecord extends FragmentTransaction implements
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
} break;
}
break;
case OP_REPLACE: {
Fragment f = op.fragment;
if (f != null) {
@@ -719,42 +1038,48 @@ final class BackStackRecord extends FragmentTransaction implements
mTransitionStyle);
}
if (op.removed != null) {
for (int i=0; i<op.removed.size(); i++) {
for (int i = 0; i < op.removed.size(); i++) {
Fragment old = op.removed.get(i);
old.mNextAnim = op.popEnterAnim;
mManager.addFragment(old, false);
}
}
} break;
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.addFragment(f, false);
} break;
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.showFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
} break;
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.hideFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
} break;
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.attachFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
} break;
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.detachFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
} break;
}
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
@@ -766,12 +1091,39 @@ final class BackStackRecord extends FragmentTransaction implements
if (doStateMove) {
mManager.moveToState(mManager.mCurState,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
if (state != null) {
updateTransitionEndState(state, mSharedElementSourceNames);
state = null;
}
}
if (mIndex >= 0) {
mManager.freeBackStackIndex(mIndex);
mIndex = -1;
}
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;
}
}
overrides.put(source, target);
}
private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
ArrayList<String> targetNames) {
if (sourceNames != null) {
for (int i = 0; i < sourceNames.size(); i++) {
String source = sourceNames.get(i);
String target = targetNames.get(i);
setNameOverride(state.excludingTransition, source, target);
}
}
}
public String getName() {
@@ -789,4 +1141,16 @@ final class BackStackRecord extends FragmentTransaction implements
public boolean isEmpty() {
return mNumOp == 0;
}
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;
}
}

View File

@@ -133,8 +133,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
public void onTransitionEnd(Transition transition) {
setViewVisibility(mSharedElements, View.INVISIBLE);
ViewGroupOverlay overlay = getDecor().getOverlay();
for (int i = 0; i < mSharedElementSnapshots.size(); i++) {
overlay.add(mSharedElementSnapshots.get(i));
if (mSharedElementSnapshots != null) {
for (int i = 0; i < mSharedElementSnapshots.size(); i++) {
overlay.add(mSharedElementSnapshots.get(i));
}
}
}
});

View File

@@ -1494,7 +1494,7 @@ final class FragmentManagerImpl extends FragmentManager {
return false;
}
final BackStackRecord bss = mBackStack.remove(last);
bss.popFromBackStack(true);
bss.popFromBackStack(true, null);
reportBackStackChanged();
} else {
int index = -1;
@@ -1538,9 +1538,10 @@ final class FragmentManagerImpl extends FragmentManager {
states.add(mBackStack.remove(i));
}
final int LAST = states.size()-1;
BackStackRecord.TransitionState state = null;
for (int i=0; i<=LAST; i++) {
if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
states.get(i).popFromBackStack(i == LAST);
state = states.get(i).popFromBackStack(i == LAST, state);
}
reportBackStackChanged();
}

View File

@@ -1,5 +1,8 @@
package android.app;
import android.util.Pair;
import android.view.View;
/**
* API for performing a set of Fragment operations.
*
@@ -168,6 +171,36 @@ 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.
*/
public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId);
/**
* 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 viewName in the View hierarchy.
* @param sharedElement A View in a disappearing Fragment to match with a View in an
* appearing Fragment.
* @param name The viewName for a View in an appearing Fragment to match to the shared
* element.
*/
public abstract FragmentTransaction setSharedElement(View sharedElement, String name);
/**
* Used with {@link #setCustomTransition(int, int)} to map multiple Views from removed or hidden
* Fragments to a Views from a shown or added Fragments. Views in
* <var>sharedElements</var> must have unique viewNames in the View hierarchy.
* @param sharedElements Pairs of Views in disappearing Fragments to viewNames in
* appearing Fragments.
*/
public abstract FragmentTransaction setSharedElements(Pair<View, String>... sharedElements);
/**
* Set a custom style resource that will be used for resolving transit
* animations.

View File

@@ -206,6 +206,10 @@ public abstract class Transition implements Cloneable {
// like CircularPropagation
EpicenterCallback mEpicenterCallback;
// For Fragment shared element transitions, linking views explicitly by mismatching
// viewNames.
ArrayMap<String, String> mNameOverrides;
/**
* Constructs a Transition object with no target objects. A transition with
* no targets defaults to running on all target objects in the scene hierarchy
@@ -1347,6 +1351,21 @@ public abstract class Transition implements Cloneable {
} else {
captureHierarchy(sceneRoot, start);
}
if (!start && mNameOverrides != null) {
int numOverrides = mNameOverrides.size();
ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides);
for (int i = 0; i < numOverrides; i++) {
String fromName = mNameOverrides.keyAt(i);
overriddenViews.add(mStartValues.nameValues.remove(fromName));
}
for (int i = 0; i < numOverrides; i++) {
View view = overriddenViews.get(i);
if (view != null) {
String toName = mNameOverrides.valueAt(i);
mStartValues.nameValues.put(toName, view);
}
}
}
}
static void addViewValues(TransitionValuesMaps transitionValuesMaps,
@@ -1400,10 +1419,12 @@ public abstract class Transition implements Cloneable {
mStartValues.viewValues.clear();
mStartValues.idValues.clear();
mStartValues.itemIdValues.clear();
mStartValues.nameValues.clear();
} else {
mEndValues.viewValues.clear();
mEndValues.idValues.clear();
mEndValues.itemIdValues.clear();
mEndValues.nameValues.clear();
}
}
@@ -1866,6 +1887,20 @@ public abstract class Transition implements Cloneable {
return mCanRemoveViews;
}
/**
* Sets the shared element names -- a mapping from a name at the start state to
* a different name at the end state.
* @hide
*/
public void setNameOverrides(ArrayMap<String, String> overrides) {
mNameOverrides = overrides;
}
/** @hide */
public ArrayMap<String, String> getNameOverrides() {
return mNameOverrides;
}
@Override
public String toString() {
return toString("");

View File

@@ -168,14 +168,36 @@ public class TransitionSet extends Transition {
@Override
public TransitionSet addTarget(View target) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).addTarget(target);
}
return (TransitionSet) super.addTarget(target);
}
@Override
public TransitionSet addTarget(int targetId) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).addTarget(targetId);
}
return (TransitionSet) super.addTarget(targetId);
}
@Override
public TransitionSet addTarget(String targetName) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).addTarget(targetName);
}
return (TransitionSet) super.addTarget(targetName);
}
@Override
public TransitionSet addTarget(Class targetType) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).addTarget(targetType);
}
return (TransitionSet) super.addTarget(targetType);
}
@Override
public TransitionSet addListener(TransitionListener listener) {
return (TransitionSet) super.addListener(listener);
@@ -183,14 +205,68 @@ public class TransitionSet extends Transition {
@Override
public TransitionSet removeTarget(int targetId) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).removeTarget(targetId);
}
return (TransitionSet) super.removeTarget(targetId);
}
@Override
public TransitionSet removeTarget(View target) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).removeTarget(target);
}
return (TransitionSet) super.removeTarget(target);
}
@Override
public TransitionSet removeTarget(Class target) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).removeTarget(target);
}
return (TransitionSet) super.removeTarget(target);
}
@Override
public TransitionSet removeTarget(String target) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).removeTarget(target);
}
return (TransitionSet) super.removeTarget(target);
}
@Override
public Transition excludeTarget(View target, boolean exclude) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).excludeTarget(target, exclude);
}
return super.excludeTarget(target, exclude);
}
@Override
public Transition excludeTarget(String targetViewName, boolean exclude) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).excludeTarget(targetViewName, exclude);
}
return super.excludeTarget(targetViewName, exclude);
}
@Override
public Transition excludeTarget(int targetId, boolean exclude) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).excludeTarget(targetId, exclude);
}
return super.excludeTarget(targetId, exclude);
}
@Override
public Transition excludeTarget(Class type, boolean exclude) {
for (int i = 0; i < mTransitions.size(); i++) {
mTransitions.get(i).excludeTarget(type, exclude);
}
return super.excludeTarget(type, exclude);
}
@Override
public TransitionSet removeListener(TransitionListener listener) {
return (TransitionSet) super.removeListener(listener);

View File

@@ -18,6 +18,9 @@ package android.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.view.View;
import android.view.ViewGroup;
@@ -272,15 +275,23 @@ public abstract class Visibility extends Transition {
if (startView.getParent() == null) {
// no parent - safe to use
overlayView = startView;
} else if (startView.getParent() instanceof View &&
startView.getParent().getParent() == null) {
} else if (startView.getParent() instanceof View) {
View startParent = (View) startView.getParent();
int id = startParent.getId();
if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
// no parent, but its parent is unparented but the parent
// hierarchy has been replaced by a new hierarchy with the same id
// and it is safe to un-parent startView
overlayView = startView;
if (!isValidTarget(startParent)) {
if (startView.isAttachedToWindow()) {
overlayView = copyViewImage(startView);
} else {
overlayView = startView;
}
} else if (startParent.getParent() == null) {
int id = startParent.getId();
if (id != View.NO_ID && sceneRoot.findViewById(id) != null
&& mCanRemoveViews) {
// no parent, but its parent is unparented but the parent
// hierarchy has been replaced by a new hierarchy with the same id
// and it is safe to un-parent startView
overlayView = startView;
}
}
}
}
@@ -378,6 +389,26 @@ public abstract class Visibility extends Transition {
return null;
}
private View copyViewImage(View view) {
int width = view.getWidth();
int height = view.getHeight();
if (width <= 0 || height <= 0) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
final BitmapDrawable drawable = new BitmapDrawable(bitmap);
View overlayView = new View(view.getContext());
overlayView.setBackground(drawable);
int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
overlayView.measure(widthSpec, heightSpec);
overlayView.layout(0, 0, width, height);
return overlayView;
}
/**
* The default implementation of this method returns a null Animator. Subclasses should
* override this method to make targets disappear with the desired transition. The