blob: 13030ca285b55cad84d493ca1dfb140c0de30e7d [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.TransitionManager;
import android.transition.TransitionSet;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LogWriter;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
final class BackStackState implements Parcelable {
final int[] mOps;
final int mTransition;
final int mTransitionStyle;
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();
}
op = op.next;
}
mOps = new int[bse.mNumOp * 7 + numRemoved];
if (!bse.mAddToBackStack) {
throw new IllegalStateException("Not on back stack");
}
op = bse.mHead;
int pos = 0;
while (op != null) {
mOps[pos++] = op.cmd;
mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
mOps[pos++] = op.enterAnim;
mOps[pos++] = op.exitAnim;
mOps[pos++] = op.popEnterAnim;
mOps[pos++] = op.popExitAnim;
if (op.removed != null) {
final int N = op.removed.size();
mOps[pos++] = N;
for (int i = 0; i < N; i++) {
mOps[pos++] = op.removed.get(i).mIndex;
}
} else {
mOps[pos++] = 0;
}
op = op.next;
}
mTransition = bse.mTransition;
mTransitionStyle = bse.mTransitionStyle;
mName = bse.mName;
mIndex = bse.mIndex;
mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
mSharedElementSourceNames = bse.mSharedElementSourceNames;
mSharedElementTargetNames = bse.mSharedElementTargetNames;
}
public BackStackState(Parcel in) {
mOps = in.createIntArray();
mTransition = in.readInt();
mTransitionStyle = in.readInt();
mName = in.readString();
mIndex = in.readInt();
mBreadCrumbTitleRes = in.readInt();
mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mBreadCrumbShortTitleRes = in.readInt();
mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mSharedElementSourceNames = in.createStringArrayList();
mSharedElementTargetNames = in.createStringArrayList();
}
public BackStackRecord instantiate(FragmentManagerImpl fm) {
BackStackRecord bse = new BackStackRecord(fm);
int pos = 0;
int num = 0;
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]);
}
int findex = mOps[pos++];
if (findex >= 0) {
Fragment f = fm.mActive.get(findex);
op.fragment = f;
} else {
op.fragment = null;
}
op.enterAnim = mOps[pos++];
op.exitAnim = mOps[pos++];
op.popEnterAnim = mOps[pos++];
op.popExitAnim = mOps[pos++];
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]);
}
Fragment r = fm.mActive.get(mOps[pos++]);
op.removed.add(r);
}
}
bse.addOp(op);
num++;
}
bse.mTransition = mTransition;
bse.mTransitionStyle = mTransitionStyle;
bse.mName = mName;
bse.mIndex = mIndex;
bse.mAddToBackStack = true;
bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
bse.mSharedElementSourceNames = mSharedElementSourceNames;
bse.mSharedElementTargetNames = mSharedElementTargetNames;
bse.bumpBackStackNesting(1);
return bse;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeIntArray(mOps);
dest.writeInt(mTransition);
dest.writeInt(mTransitionStyle);
dest.writeString(mName);
dest.writeInt(mIndex);
dest.writeInt(mBreadCrumbTitleRes);
TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
dest.writeInt(mBreadCrumbShortTitleRes);
TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
dest.writeStringList(mSharedElementSourceNames);
dest.writeStringList(mSharedElementTargetNames);
}
public static final Parcelable.Creator<BackStackState> CREATOR
= new Parcelable.Creator<BackStackState>() {
public BackStackState createFromParcel(Parcel in) {
return new BackStackState(in);
}
public BackStackState[] newArray(int size) {
return new BackStackState[size];
}
};
}
/**
* @hide Entry of an operation on the fragment back stack.
*/
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
static final String TAG = FragmentManagerImpl.TAG;
final FragmentManagerImpl mManager;
static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;
static final class Op {
Op next;
Op prev;
int cmd;
Fragment fragment;
int enterAnim;
int exitAnim;
int popEnterAnim;
int popExitAnim;
ArrayList<Fragment> removed;
}
Op mHead;
Op mTail;
int mNumOp;
int mEnterAnim;
int mExitAnim;
int mPopEnterAnim;
int mPopExitAnim;
int mTransition;
int mTransitionStyle;
boolean mAddToBackStack;
boolean mAllowAddToBackStack = true;
String mName;
boolean mCommitted;
int mIndex = -1;
int mBreadCrumbTitleRes;
CharSequence mBreadCrumbTitleText;
int mBreadCrumbShortTitleRes;
CharSequence mBreadCrumbShortTitleText;
ArrayList<String> mSharedElementSourceNames;
ArrayList<String> mSharedElementTargetNames;
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("BackStackEntry{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
if (mIndex >= 0) {
sb.append(" #");
sb.append(mIndex);
}
if (mName != null) {
sb.append(" ");
sb.append(mName);
}
sb.append("}");
return sb.toString();
}
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
dump(prefix, writer, true);
}
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);
if (mTransition != FragmentTransaction.TRANSIT_NONE) {
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 (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);
}
if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
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:");
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;
}
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));
}
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));
}
}
if (op.removed != null && op.removed.size() > 0) {
for (int i = 0; i < op.removed.size(); i++) {
writer.print(innerPrefix);
if (op.removed.size() == 1) {
writer.print("Removed: ");
} else {
if (i == 0) {
writer.println("Removed:");
}
writer.print(innerPrefix);
writer.print(" #");
writer.print(i);
writer.print(": ");
}
writer.println(op.removed.get(i));
}
}
op = op.next;
num++;
}
}
}
public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
}
public int getId() {
return mIndex;
}
public int getBreadCrumbTitleRes() {
return mBreadCrumbTitleRes;
}
public int getBreadCrumbShortTitleRes() {
return mBreadCrumbShortTitleRes;
}
public CharSequence getBreadCrumbTitle() {
if (mBreadCrumbTitleRes != 0) {
return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
}
return mBreadCrumbTitleText;
}
public CharSequence getBreadCrumbShortTitle() {
if (mBreadCrumbShortTitleRes != 0) {
return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
}
return mBreadCrumbShortTitleText;
}
void addOp(Op op) {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
mNumOp++;
}
public FragmentTransaction add(Fragment fragment, String tag) {
doAddOp(0, fragment, tag, OP_ADD);
return this;
}
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager;
if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
throw new IllegalStateException("Can't change tag of fragment "
+ fragment + ": was " + fragment.mTag
+ " now " + tag);
}
fragment.mTag = tag;
}
if (containerViewId != 0) {
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
return replace(containerViewId, fragment, null);
}
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
if (containerViewId == 0) {
throw new IllegalArgumentException("Must use non-zero containerViewId");
}
doAddOp(containerViewId, fragment, tag, OP_REPLACE);
return this;
}
public FragmentTransaction remove(Fragment fragment) {
Op op = new Op();
op.cmd = OP_REMOVE;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction show(Fragment fragment) {
Op op = new Op();
op.cmd = OP_SHOW;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction detach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_DETACH;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction attach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_ATTACH;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction setCustomAnimations(int enter, int exit) {
return setCustomAnimations(enter, exit, 0, 0);
}
public FragmentTransaction setCustomAnimations(int enter, int exit,
int popEnter, int popExit) {
mEnterAnim = enter;
mExitAnim = exit;
mPopEnterAnim = popEnter;
mPopExitAnim = popExit;
return this;
}
public FragmentTransaction setTransition(int transition) {
mTransition = transition;
return this;
}
@Override
public FragmentTransaction addSharedElement(View sharedElement, String name) {
String transitionName = sharedElement.getTransitionName();
if (transitionName == null) {
throw new IllegalArgumentException("Unique transitionNames are required for all" +
" sharedElements");
}
if (mSharedElementSourceNames == null) {
mSharedElementSourceNames = new ArrayList<String>();
mSharedElementTargetNames = new ArrayList<String>();
}
mSharedElementSourceNames.add(transitionName);
mSharedElementTargetNames.add(name);
return this;
}
public FragmentTransaction setTransitionStyle(int styleRes) {
mTransitionStyle = styleRes;
return this;
}
public FragmentTransaction addToBackStack(String name) {
if (!mAllowAddToBackStack) {
throw new IllegalStateException(
"This FragmentTransaction is not allowed to be added to the back stack.");
}
mAddToBackStack = true;
mName = name;
return this;
}
public boolean isAddToBackStackAllowed() {
return mAllowAddToBackStack;
}
public FragmentTransaction disallowAddToBackStack() {
if (mAddToBackStack) {
throw new IllegalStateException(
"This transaction is already being added to the back stack");
}
mAllowAddToBackStack = false;
return this;
}
public FragmentTransaction setBreadCrumbTitle(int res) {
mBreadCrumbTitleRes = res;
mBreadCrumbTitleText = null;
return this;
}
public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
mBreadCrumbTitleRes = 0;
mBreadCrumbTitleText = text;
return this;
}
public FragmentTransaction setBreadCrumbShortTitle(int res) {
mBreadCrumbShortTitleRes = res;
mBreadCrumbShortTitleText = null;
return this;
}
public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
mBreadCrumbShortTitleRes = 0;
mBreadCrumbShortTitleText = text;
return this;
}
void bumpBackStackNesting(int amt) {
if (!mAddToBackStack) {
return;
}
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 (op.removed != null) {
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);
}
}
}
op = op.next;
}
}
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");
}
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
PrintWriter pw = new FastPrintWriter(logw, false, 1024);
dump(" ", null, pw, null);
pw.flush();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
public void run() {
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Run: " + this);
}
if (mAddToBackStack) {
if (mIndex < 0) {
throw new IllegalStateException("addToBackStack() called after commit()");
}
}
bumpBackStackNesting(1);
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
calculateFragments(firstOutFragments, lastInFragments);
beginTransition(firstOutFragments, lastInFragments, false);
Op op = mHead;
while (op != null) {
switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
break;
case OP_REPLACE: {
Fragment f = op.fragment;
int containerId = f.mContainerId;
if (mManager.mAdded != null) {
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 (old.mContainerId == containerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = op.exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Bump nesting of "
+ old + " to " + old.mBackStackNesting);
}
}
mManager.removeFragment(old, mTransition, mTransitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.removeFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.hideFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.showFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.detachFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.attachFragment(f, mTransition, mTransitionStyle);
}
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
}
op = op.next;
}
mManager.moveToState(mManager.mCurState, mTransition,
mTransitionStyle, true);
if (mAddToBackStack) {
mManager.addBackStackState(this);
}
}
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) {
if (!mManager.mContainer.onHasView()) {
return; // nothing to see, so no transitions
}
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;
}
}
/**
* 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) {
if (!mManager.mContainer.onHasView()) {
return; // nothing to see, so no transitions
}
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;
}
}
/**
* 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 #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
* java.util.ArrayList)}.
*/
private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
SparseArray<Fragment> lastInFragments, boolean isBack) {
TransitionState state = new TransitionState();
// Adding a non-existent target view makes sure that the transitions don't target
// any views by default. They'll only target the views we tell add. If we don't
// add any, then no views will be targeted.
state.nonExistentView = new View(mManager.mHost.getContext());
// Go over all leaving fragments.
for (int i = 0; i < firstOutFragments.size(); i++) {
int containerId = firstOutFragments.keyAt(i);
configureTransitions(containerId, state, isBack, firstOutFragments,
lastInFragments);
}
// 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);
}
}
return state;
}
private static Transition cloneTransition(Transition transition) {
if (transition != null) {
transition = transition.clone();
}
return transition;
}
private static Transition getEnterTransition(Fragment inFragment, boolean isBack) {
if (inFragment == null) {
return null;
}
return cloneTransition(isBack ? inFragment.getReenterTransition() :
inFragment.getEnterTransition());
}
private static Transition getExitTransition(Fragment outFragment, boolean isBack) {
if (outFragment == null) {
return null;
}
return cloneTransition(isBack ? outFragment.getReturnTransition() :
outFragment.getExitTransition());
}
private static TransitionSet getSharedElementTransition(Fragment inFragment,
Fragment outFragment, boolean isBack) {
if (inFragment == null || outFragment == null) {
return null;
}
Transition transition = cloneTransition(isBack
? outFragment.getSharedElementReturnTransition()
: inFragment.getSharedElementEnterTransition());
if (transition == null) {
return null;
}
TransitionSet transitionSet = new TransitionSet();
transitionSet.addTransition(transition);
return transitionSet;
}
private static ArrayList<View> captureExitingViews(Transition exitTransition,
Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
ArrayList<View> viewList = null;
if (exitTransition != null) {
viewList = new ArrayList<View>();
View root = outFragment.getView();
root.captureTransitioningViews(viewList);
if (namedViews != null) {
viewList.removeAll(namedViews.values());
}
if (!viewList.isEmpty()) {
viewList.add(nonExistentView);
addTargets(exitTransition, viewList);
}
}
return viewList;
}
private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
boolean isBack) {
ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
if (mSharedElementSourceNames != null) {
outFragment.getView().findNamedViews(namedViews);
if (isBack) {
namedViews.retainAll(mSharedElementTargetNames);
} else {
namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
namedViews);
}
}
if (isBack) {
outFragment.mEnterTransitionCallback.onMapSharedElements(
mSharedElementTargetNames, namedViews);
setBackNameOverrides(state, namedViews, false);
} else {
outFragment.mExitTransitionCallback.onMapSharedElements(
mSharedElementTargetNames, namedViews);
setNameOverrides(state, namedViews, false);
}
return namedViews;
}
/**
* Prepares the enter transition by adding a non-existent view to the transition's target list
* and setting it epicenter callback. By adding a non-existent view to the target list,
* we can prevent any view from being targeted at the beginning of the transition.
* We will add to the views before the end state of the transition is captured so that the
* views will appear. At the start of the transition, we clear the list of targets so that
* we can restore the state of the transition and use it again.
*
* <p>The shared element transition maps its shared elements immediately prior to
* capturing the final state of the Transition.</p>
*/
private ArrayList<View> addTransitionTargets(final TransitionState state,
final Transition enterTransition, final TransitionSet sharedElementTransition,
final Transition overallTransition, final View container,
final Fragment inFragment, final Fragment outFragment,
final ArrayList<View> hiddenFragmentViews, final boolean isBack,
final ArrayList<View> sharedElementTargets) {
if (enterTransition == null && sharedElementTransition == null &&
overallTransition == null) {
return null;
}
final ArrayList<View> enteringViews = new ArrayList<View>();
container.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
container.getViewTreeObserver().removeOnPreDrawListener(this);
// Don't include any newly-hidden fragments in the transition.
excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
overallTransition);
ArrayMap<String, View> namedViews = null;
if (sharedElementTransition != null) {
namedViews = mapSharedElementsIn(state, isBack, inFragment);
removeTargets(sharedElementTransition, sharedElementTargets);
setSharedElementTargets(sharedElementTransition,
state.nonExistentView, namedViews, sharedElementTargets);
setEpicenterIn(namedViews, state);
callSharedElementEnd(state, inFragment, outFragment, isBack,
namedViews);
}
if (enterTransition != null) {
enterTransition.removeTarget(state.nonExistentView);
View view = inFragment.getView();
if (view != null) {
view.captureTransitioningViews(enteringViews);
if (namedViews != null) {
enteringViews.removeAll(namedViews.values());
}
enteringViews.add(state.nonExistentView);
// We added this earlier to prevent any views being targeted.
addTargets(enterTransition, enteringViews);
}
setSharedElementEpicenter(enterTransition, state);
}
return true;
}
});
return enteringViews;
}
private void callSharedElementEnd(TransitionState state, Fragment inFragment,
Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) {
SharedElementCallback sharedElementCallback = isBack ?
outFragment.mEnterTransitionCallback :
inFragment.mEnterTransitionCallback;
ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
ArrayList<View> views = new ArrayList<View>(namedViews.values());
sharedElementCallback.onSharedElementEnd(names, views, null);
}
private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
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;
}
}
}
private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
boolean isBack, Fragment inFragment) {
// Now map the shared elements in the incoming fragment
ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack);
// remap shared elements and set the name mapping used
// in the shared element transition.
if (isBack) {
inFragment.mExitTransitionCallback.onMapSharedElements(
mSharedElementTargetNames, namedViews);
setBackNameOverrides(state, namedViews, true);
} else {
inFragment.mEnterTransitionCallback.onMapSharedElements(
mSharedElementTargetNames, namedViews);
setNameOverrides(state, namedViews, true);
}
return namedViews;
}
private static Transition mergeTransitions(Transition enterTransition,
Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
boolean isBack) {
boolean overlap = true;
if (enterTransition != null && exitTransition != null && inFragment != null) {
overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
inFragment.getAllowEnterTransitionOverlap();
}
// Wrap the transitions. Explicit targets like in enter and exit will cause the
// views to be targeted regardless of excluded views. If that happens, then the
// excluded fragments views (hidden fragments) will still be in the transition.
Transition transition;
if (overlap) {
// Regular transition -- do it all together
TransitionSet transitionSet = new TransitionSet();
if (enterTransition != null) {
transitionSet.addTransition(enterTransition);
}
if (exitTransition != null) {
transitionSet.addTransition(exitTransition);
}
if (sharedElementTransition != null) {
transitionSet.addTransition(sharedElementTransition);
}
transition = transitionSet;
} else {
// First do exit, then enter, but allow shared element transition to happen
// during both.
Transition staggered = null;
if (exitTransition != null && enterTransition != null) {
staggered = new TransitionSet()
.addTransition(exitTransition)
.addTransition(enterTransition)
.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
} else if (exitTransition != null) {
staggered = exitTransition;
} else if (enterTransition != null) {
staggered = enterTransition;
}
if (sharedElementTransition != null) {
TransitionSet together = new TransitionSet();
if (staggered != null) {
together.addTransition(staggered);
}
together.addTransition(sharedElementTransition);
transition = together;
} else {
transition = staggered;
}
}
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 keeping track of the executing transitions.
* @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.
*/
private void configureTransitions(int containerId, TransitionState state, boolean isBack,
SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId);
if (sceneRoot != null) {
Fragment inFragment = lastInFragments.get(containerId);
Fragment outFragment = firstOutFragments.get(containerId);
Transition enterTransition = getEnterTransition(inFragment, isBack);
TransitionSet sharedElementTransition =
getSharedElementTransition(inFragment, outFragment, isBack);
Transition exitTransition = getExitTransition(outFragment, isBack);
if (enterTransition == null && sharedElementTransition == null &&
exitTransition == null) {
return; // no transitions!
}
if (enterTransition != null) {
enterTransition.addTarget(state.nonExistentView);
}
ArrayMap<String, View> namedViews = null;
ArrayList<View> sharedElementTargets = new ArrayList<View>();
if (sharedElementTransition != null) {
namedViews = remapSharedElements(state, outFragment, isBack);
setSharedElementTargets(sharedElementTransition,
state.nonExistentView, namedViews, sharedElementTargets);
// Notify the start of the transition.
SharedElementCallback callback = isBack ?
outFragment.mEnterTransitionCallback :
inFragment.mEnterTransitionCallback;
ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
ArrayList<View> views = new ArrayList<View>(namedViews.values());
callback.onSharedElementStart(names, views, null);
}
ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
namedViews, state.nonExistentView);
if (exitingViews == null || exitingViews.isEmpty()) {
exitTransition = null;
}
// Set the epicenter of the exit transition
if (mSharedElementTargetNames != null && namedViews != null) {
View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
if (epicenterView != null) {
if (exitTransition != null) {
setEpicenter(exitTransition, epicenterView);
}
if (sharedElementTransition != null) {
setEpicenter(sharedElementTransition, epicenterView);
}
}
}
Transition transition = mergeTransitions(enterTransition, exitTransition,
sharedElementTransition, inFragment, isBack);
if (transition != null) {
ArrayList<View> hiddenFragments = new ArrayList<View>();
ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
sharedElementTransition, transition, sceneRoot, inFragment,
outFragment, hiddenFragments, isBack, sharedElementTargets);
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(hiddenFragments, containerId, transition);
TransitionManager.beginDelayedTransition(sceneRoot, transition);
// Remove the view targeting after the transition starts
removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
enterTransition, enteringViews, exitTransition, exitingViews,
sharedElementTransition, sharedElementTargets, transition,
hiddenFragments);
}
}
}
/**
* Finds all children of the shared elements and sets the wrapping TransitionSet
* targets to point to those. It also limits transitions that have no targets to the
* specific shared elements. This allows developers to target child views of the
* shared elements specifically, but this doesn't happen by default.
*/
private static void setSharedElementTargets(TransitionSet transition,
View nonExistentView, ArrayMap<String, View> namedViews,
ArrayList<View> sharedElementTargets) {
sharedElementTargets.clear();
sharedElementTargets.addAll(namedViews.values());
final List<View> views = transition.getTargets();
views.clear();
final int count = sharedElementTargets.size();
for (int i = 0; i < count; i++) {
final View view = sharedElementTargets.get(i);
bfsAddViewChildren(views, view);
}
sharedElementTargets.add(nonExistentView);
addTargets(transition, sharedElementTargets);
}
/**
* Uses a breadth-first scheme to add startView and all of its children to views.
* It won't add a child if it is already in views.
*/
private static void bfsAddViewChildren(final List<View> views, final View startView) {
final int startIndex = views.size();
if (containedBeforeIndex(views, startView, startIndex)) {
return; // This child is already in the list, so all its children are also.
}
views.add(startView);
for (int index = startIndex; index < views.size(); index++) {
final View view = views.get(index);
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
final int childCount = viewGroup.getChildCount();
for (int childIndex = 0; childIndex < childCount; childIndex++) {
final View child = viewGroup.getChildAt(childIndex);
if (!containedBeforeIndex(views, child, startIndex)) {
views.add(child);
}
}
}
}
}
/**
* Does a linear search through views for view, limited to maxIndex.
*/
private static boolean containedBeforeIndex(final List<View> views, final View view,
final int maxIndex) {
for (int i = 0; i < maxIndex; i++) {
if (views.get(i) == view) {
return true;
}
}
return false;
}
/**
* After the transition has started, remove all targets that we added to the transitions
* so that the transitions are left in a clean state.
*/
private void removeTargetedViewsFromTransitions(
final ViewGroup sceneRoot, final View nonExistingView,
final Transition enterTransition, final ArrayList<View> enteringViews,
final Transition exitTransition, final ArrayList<View> exitingViews,
final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
final Transition overallTransition, final ArrayList<View> hiddenViews) {
if (overallTransition != null) {
sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
if (enterTransition != null) {
removeTargets(enterTransition, enteringViews);
}
if (exitTransition != null) {
removeTargets(exitTransition, exitingViews);
}
if (sharedElementTransition != null) {
removeTargets(sharedElementTransition, sharedElementTargets);
}
int numViews = hiddenViews.size();
for (int i = 0; i < numViews; i++) {
overallTransition.excludeTarget(hiddenViews.get(i), false);
}
overallTransition.excludeTarget(nonExistingView, false);
return true;
}
});
}
}
/**
* This method removes the views from transitions that target ONLY those views.
* The views list should match those added in addTargets and should contain
* one view that is not in the view hierarchy (state.nonExistentView).
*/
public static void removeTargets(Transition transition, ArrayList<View> views) {
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
removeTargets(child, views);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (targets != null && targets.size() == views.size() &&
targets.containsAll(views)) {
// We have an exact match. We must have added these earlier in addTargets
for (int i = views.size() - 1; i >= 0; i--) {
transition.removeTarget(views.get(i));
}
}
}
}
/**
* This method adds views as targets to the transition, but only if the transition
* doesn't already have a target. It is best for views to contain one View object
* that does not exist in the view hierarchy (state.nonExistentView) so that
* when they are removed later, a list match will suffice to remove the targets.
* Otherwise, if you happened to have targeted the exact views for the transition,
* the removeTargets call will remove them unexpectedly.
*/
public static void addTargets(Transition transition, ArrayList<View> views) {
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
addTargets(child, views);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (isNullOrEmpty(targets)) {
// We can just add the target views
int numViews = views.size();
for (int i = 0; i < numViews; i++) {
transition.addTarget(views.get(i));
}
}
}
}
private static boolean hasSimpleTarget(Transition transition) {
return !isNullOrEmpty(transition.getTargetIds()) ||
!isNullOrEmpty(transition.getTargetNames()) ||
!isNullOrEmpty(transition.getTargetTypes());
}
private static boolean isNullOrEmpty(List list) {
return list == null || list.isEmpty();
}
/**
* 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
* @return a new Map 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> remappedViews = new ArrayMap<String, View>();
if (!namedViews.isEmpty()) {
int numKeys = inMap.size();
for (int i = 0; i < numKeys; i++) {
View view = namedViews.get(inMap.get(i));
if (view != null) {
remappedViews.put(toGoInMap.get(i), view);
}
}
}
return remappedViews;
}
/**
* Maps shared elements to views in the entering fragment.
*
* @param state The transition State as returned from {@link #beginTransition(
* android.util.SparseArray, android.util.SparseArray, boolean)}.
* @param inFragment The last fragment to be added.
* @param isBack true if this is popping the back stack or false if this is a
* forward operation.
*/
private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
Fragment inFragment, boolean isBack) {
ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
View root = inFragment.getView();
if (root != null) {
if (mSharedElementSourceNames != null) {
root.findNamedViews(namedViews);
if (isBack) {
namedViews = remapNames(mSharedElementSourceNames,
mSharedElementTargetNames, namedViews);
} else {
namedViews.retainAll(mSharedElementTargetNames);
}
}
}
return namedViews;
}
private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
Transition transition) {
if (mManager.mAdded != null) {
for (int i = 0; i < mManager.mAdded.size(); i++) {
Fragment fragment = mManager.mAdded.get(i);
if (fragment.mView != null && fragment.mContainer != null &&
fragment.mContainerId == containerId) {
if (fragment.mHidden) {
if (!hiddenFragmentViews.contains(fragment.mView)) {
transition.excludeTarget(fragment.mView, true);
hiddenFragmentViews.add(fragment.mView);
}
} else {
transition.excludeTarget(fragment.mView, false);
hiddenFragmentViews.remove(fragment.mView);
}
}
}
}
}
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.enteringEpicenterView != null) {
mEpicenter = new Rect();
state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
}
return mEpicenter;
}
});
}
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);
PrintWriter pw = new FastPrintWriter(logw, false, 1024);
dump(" ", null, pw, null);
pw.flush();
}
if (state == null) {
if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
state = beginTransition(firstOutFragments, lastInFragments, true);
}
} else if (!doStateMove) {
setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
}
bumpBackStackNesting(-1);
Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
}
break;
case OP_REPLACE: {
Fragment f = op.fragment;
if (f != null) {
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
}
if (op.removed != null) {
for (int i = 0; i < op.removed.size(); i++) {
Fragment old = op.removed.get(i);
old.mNextAnim = op.popEnterAnim;
mManager.addFragment(old, false);
}
}
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.addFragment(f, false);
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.showFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.hideFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.attachFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.detachFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
}
op = op.prev;
}
if (doStateMove) {
mManager.moveToState(mManager.mCurState,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
state = null;
}
if (mIndex >= 0) {
mManager.freeBackStackIndex(mIndex);
mIndex = -1;
}
return state;
}
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);
}
}
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.nameOverrides, source, target);
}
}
}
private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
boolean isEnd) {
int count = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
for (int i = 0; i < count; i++) {
String source = mSharedElementSourceNames.get(i);
String originalTarget = mSharedElementTargetNames.get(i);
View view = namedViews.get(originalTarget);
if (view != null) {
String target = view.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 == null ? 0 : 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);
}
}
}
public String getName() {
return mName;
}
public int getTransition() {
return mTransition;
}
public int getTransitionStyle() {
return mTransitionStyle;
}
public boolean isEmpty() {
return mNumOp == 0;
}
public class TransitionState {
public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
public View enteringEpicenterView;
public View nonExistentView;
}
}