Fragment
既可以在布局文件中进行静态配置创建,也可以使用FragmentManager
进行动态创建.在动态创建时, 可以通过调用FragmentTransaction
的addToBackStack
接口, 指定该FragmentTransaction对象添加到FragmentManager中的back stack
中,这将对用户的操作(按back键)产生影响.本文档通过一个demo程序说明其中的一些细节.
Demo程序说明
包括一个Activity和四个Fragment, Activity中动态创建Fragment和销毁Fragment的操作.四个Fragment共用一个布局文件, 通过一个文本区域显示当前的Fragment名称.Activity类代码实现如下:
public class MainActivity extends Activity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private int mAddedNum;
private CheckBox mAddToBackStack;
private CheckBox mPopFlag;
private EditText mBackStackName;
private FragmentManager mFragmentManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_demo);
mAddToBackStack = findViewById(R.id.cb_add_to_back_stack);
mPopFlag = findViewById(R.id.cb_include_flag);
mBackStackName = findViewById(R.id.et_back_stack_name);
findViewById(R.id.bt_add_fragment).setOnClickListener(this);
findViewById(R.id.bt_pop_fragment).setOnClickListener(this);
findViewById(R.id.bt_pop_fragment_with_name).setOnClickListener(this);
mFragmentManager = getFragmentManager();
}
@Override
public void onClick(View v) {
final int id = v.getId();
switch (id) {
case R.id.bt_add_fragment:
addFragment();
break;
case R.id.bt_pop_fragment:
popBackStack();
break;
case R.id.bt_pop_fragment_with_name:
popBackStackByNameOrId();
break;
}
}
private boolean needAddToBackStack() {
return mAddToBackStack.isChecked();
}
private int getPopFlag() {
if (mPopFlag.isChecked()) return FragmentManager.POP_BACK_STACK_INCLUSIVE;
return 0;
}
private void addFragment() {
Fragment fragment;
String tag;
switch (mAddedNum) {
case 0:
fragment = new Fragment1();
tag = "Fragment1";
break;
case 1:
fragment = new Fragment2();
tag = "Fragment2";
break;
case 2:
fragment = new Fragment3();
tag = "Fragment3";
break;
case 3:
fragment = new Fragment4();
tag = "Fragment4";
break;
default:
mAddedNum = 0;
fragment = new Fragment1();
tag = "Fragment1";
break;
}
mAddedNum++;
addFragment(R.id.fragment_container, fragment, tag);
}
private void addFragment(@IdRes int resId, @NonNull Fragment fragment, @NonNull String tag) {
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.add(resId, fragment, tag);
if (needAddToBackStack()) fragmentTransaction.addToBackStack(tag);
final int commitId = fragmentTransaction.commit();
Log.d(TAG, "Commit id:" + commitId);
}
private void popBackStack() {
mFragmentManager.popBackStack();
}
private void popBackStackByNameOrId() {
final int popFlag = getPopFlag();
final String text = mBackStackName.getText().toString();
if (text.startsWith("id:")) {
final int id = Integer.valueOf(text.substring(3)).intValue();
mFragmentManager.popBackStack(id, popFlag);
} else if (text.startsWith("name:")) {
final String name = text.substring(5);
mFragmentManager.popBackStack(name, popFlag);
}
}
}
四个Fragment的代码实现如下:
public class Fragment1 extends BaseFragment {
private static final String TAG = Fragment1.class.getSimpleName();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return createView(inflater, container, TAG);
}
}
public class Fragment2 extends BaseFragment {
private static final String TAG = Fragment2.class.getSimpleName();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return createView(inflater, container, TAG);
}
}
public class Fragment3 extends BaseFragment {
private static final String TAG = Fragment3.class.getSimpleName();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return createView(inflater, container, TAG);
}
}
public class Fragment4 extends BaseFragment {
private static final String TAG = Fragment4.class.getSimpleName();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return createView(inflater, container, TAG);
}
}
public class BaseFragment extends Fragment {
protected View createView(LayoutInflater inflater, ViewGroup container, String text) {
final View view = inflater.inflate(R.layout.fragment_demo, container, false);
((TextView) view.findViewById(R.id.fragment_id)).setText(text);
return view;
}
}
Activity的布局文件为:
Fragment共用的布局文件为:
Fragment Manager Back Stack对用户back按键的响应
当用户按下back
键时,Activity
的默认执行为(Activity.java):
/**
* Called when the activity has detected the user's press of the back
* key. The default implementation simply finishes the current activity,
* but you can override this to do whatever you want.
*/
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
FragmentManager fragmentManager = mFragments.getFragmentManager();
if (fragmentManager.isStateSaved() || !fragmentManager.popBackStackImmediate()) {
finishAfterTransition();
}
}
也就是说, 如果用户执行back按键操作, FragmentManager
对back stack
中的记录进行pop
处理,如果栈中存在记录,函数popBackStackImmediate
将返回true
, 那么通常情况下, Activity将不会调用finishAfterTransition
进行销毁.
场景一
在不选中复选框Add to back stack
的前提下,点击两次add
按钮, 根据代码逻辑, 将动态创建Fragment1
和Fragment2
. 如果用户点击一次back
键, 那么MainActivity
将销毁,显示桌面.
场景二
在选中复选框Add to back stack
的前提下,点击两次add
按钮, 根据代码逻辑, 将动态创建Fragment1
和Fragment2
. 显示结果为:
如果用户点击一次back
键,那么FragmentManager将对back stack进行pop处理, Fragment2出栈,只有Fragment1在栈中, 因此结果显示为:
如果这时候用户再次点击一次back按键,因为Fragment1还在back stack中,所以Fragment1出栈,函数popBackStackImmediate
返回值仍热为true
, MainActivity仍然不调用函数finishAfterTransition
进行销毁, 显示结果为MainActivity的初始界面:
如果用户再次点击一次back按键,因为此时FragmentManager中的back stack中已经没有记录,函数popBackStackImmediate
将返回false
, 导致调用finishAfterTransition
销毁MainActivity,显示桌面.
通过这两个场景, 基本说明了FragmentManager的back stack对用户的back按键的响应情况.点击back按键时, 会对back stack中的记录进行pop操作, 但是,新的栈顶的Fragment是否显示,取决于当前显示的Fragment与新的栈顶Fragment的添加顺序.
场景三
在选中复选框Add to back stack
的前提下,点击两次add
按钮, 然后取消复选框Add to back stack
, 再点击一次add
按钮.FragmentManager中依次添加了Fragment1
, Fragment2
和Fragment3
, 前两个Fragment同时添加到了back stack, Fragment3没有添加到back stack, 最终显示的是Fragment3的界面.
如果用户点击一次back
按键,Fragment2将从栈中退出,当前显示界面不变,仍然为Fragment3.再次点击一次back按键,Fragment1将从栈中退出,当前显示界面不变,仍然为Fragment3, 再次点击一次back按键,因为栈中已经没有记录,MainActivity将销毁, 返回桌面.
Commit索引以及pop指定的记录
FragmentManager动态创建Fragment时, 如果该Fragment同时添加到back stack, 则commit
函数的返回值为该BackStackRecord
(back stack中的每个记录用该类描述)的索引, 如果该Fragment没有添加到back stack, commit函数返回值为-1
.BackStackRecord的索引的一个作用是, 我们可以从back stack中pop出该索引对应的记录, 需要注意的是,根据栈的属性, 栈中该记录以上的记录也将被pop操作.BackStackRecord的索引是从0开始分配的.
FragmentManager
提供了接口popBackStack(int id, int flags)
来pop指定索引的记录.FragmentManager还提供了接口popBackStackImmediate(String name, int flags)
根据记录的名称进行pop操作, 二者内在逻辑几乎完全相同.当调用这两个接口时, 其第二个输入参数flags
的含义是,如果flags为0
, 该索引对应的记录不会从back stack中pop出去,只是将该记录上面的记录pop出去.如果flags
为POP_BACK_STACK_INCLUSIVE
,则该索引对应的记录也将被pop出去.
场景四
在选中复选框Add to back stack
的前提下,点击四次add
按钮,即将Fragment1
, Fragment2
,Fragment3
和Fragment4
添加到了back stack中, 并且当前显示为Fragment4
.FragmentManager的back stack中的情况为:
如果通过FragmentManager调用mFragmentManager.popBackStack(1, 0)
,那么栈中的操作结果将是:
如果通过FragmentManager调用mFragmentManager.popBackStack(1, FragmentManager.POP_BACK_STACK_INCLUSIVE),
那么栈中的操作结果将是:
FragmentManager的接口popBackStack()
将把栈顶的记录pop出去, 而接口popBackStack(String name, int flags)
和popBackStack(int id, int flags)
将有可能从栈中pop出多个记录.对于判断哪些记录需要pop操作的函数为(FragmentManager.java):
@SuppressWarnings("unused")
boolean popBackStackState(ArrayList records, ArrayList isRecordPop,
String name, int id, int flags) {
if (mBackStack == null) {
return false;
}
if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) {
int last = mBackStack.size() - 1;
if (last < 0) {
return false;
}
records.add(mBackStack.remove(last));
isRecordPop.add(true);
} else {
int index = -1;
if (name != null || id >= 0) {
// If a name or ID is specified, look for that place in
// the stack.
index = mBackStack.size()-1;
while (index >= 0) {
BackStackRecord bss = mBackStack.get(index);
if (name != null && name.equals(bss.getName())) {
break;
}
if (id >= 0 && id == bss.mIndex) {
break;
}
index--;
}
if (index < 0) {
return false;
}
if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
index--;
// Consume all following entries that match.
while (index >= 0) {
BackStackRecord bss = mBackStack.get(index);
if ((name != null && name.equals(bss.getName()))
|| (id >= 0 && id == bss.mIndex)) {
index--;
continue;
}
break;
}
}
}
if (index == mBackStack.size()-1) {
return false;
}
for (int i = mBackStack.size() - 1; i > index; i--) {
records.add(mBackStack.remove(i));
isRecordPop.add(true);
}
}
return true;
}
该函数执行完毕后,输出参数records
中将保存需要pop出的BackStackRecord
对象. 如果没有指定name
或者id
时, 从back stack栈顶pop出一个记录, 否则根据name
或者id
,并结合flags
找出需要pop的记录.
BackStackRecord的索引分配
每个添加到back stack的BackStackRecord
对象都要分配索引.如果一直是向back stack添加BackStackRecord对象,其所以将从0开始连续增加进行分配. 如果back stack发生了pop操作,则pop出的记录的索引将会被回收,用作下一次的分配.FragmentManager
类中的如下两个成员变量控制BackStackRecord的索引分配(FragmentManager.java):
// Must be accessed while locked.
ArrayList mBackStackIndices;
ArrayList mAvailBackStackIndices;
分配索引时(FragmentManager.java):
public int allocBackStackIndex(BackStackRecord bse) {
synchronized (this) {
if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
if (mBackStackIndices == null) {
mBackStackIndices = new ArrayList();
}
int index = mBackStackIndices.size();
if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
mBackStackIndices.add(bse);
return index;
} else {
int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
mBackStackIndices.set(index, bse);
return index;
}
}
}
即如果mAvailBackStackIndices
有空闲的索引(说明之前发生过pop操作), 则从其中分配索引, 否则,从索引结构mBackStackIndices
中分配索引.
当back stack发生pop操作时, 释放BackStackRecord的索引(FragmentManager.java):
public void freeBackStackIndex(int index) {
synchronized (this) {
mBackStackIndices.set(index, null);
if (mAvailBackStackIndices == null) {
mAvailBackStackIndices = new ArrayList();
}
if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
mAvailBackStackIndices.add(index);
}
}
索引结构mBackStackIndices
不再引用BackStackRecord, 但是该结构的size
不会减小,而是把释放的索引保存在mAvailBackStackIndices
中.
尾声
FragmentManager的接口popBackStack是异步调用, 其同步调用接口为popBackStackImmediate. fragmentTransaction的commit接口也是异步调用, 其同步调用接口为commitNow.