Fragment是android3.0以后添加的。主要是为了解决 Android 设备尺寸多样化之后的显示问题。我们都知道 Android 设备有小屏的手机,大屏的手机,平板甚至电视。在 fragment 出现之前我们很多情况下都是先开发一个适配手机的 APP,再拷贝一份代码适配平板的 APP。Fragment 的出现就解决了这一个问题。Fragment 必须依附与 Activity 存在,Fragment 比 Activity 较轻量级,可以把 Fragment 看成是 Activity 的界面的一部分,但是Fragment可以提供与用户交互的界面并且有自己的生命周期,这样就不用把一堆逻辑都放在 Activity 中了。另外,还可以动态的添加、移除和替换某一个 Fragment。
首先单独看下 Fragment 的生命周期,以下是官网给出的生命周期图。
Fragment 必须依附于 Activity 而存在,所以Activity 的生命周期会直接影响到 Fragment 的生命周期。
我们可以看到Fragment 的生命周期函数比起 Activity 要多一些,但是对照着 Activity 的生命周期来记忆应该也不难记忆。
这里需要注意一点,Fragment 的生命周期不是从 new Fragment()开始的,而是从加到 Activity 开始的。
onAttach(Context context) 这是当Fragment 与 Activity 发生关联时进行调用的。
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 创建该 Fragment 视图。
onActivityCreated(@Nullable Bundle savedInstanceState) 当 Activity 的 onCreate方法返回时调用。
onDestroyView() 与onCreateView对应,当视图被移除时调用。
onDetach() 与 onAttach 对应,当视图与 Activity 解除关联时调用。
注意:除了onCreateView之外,其它生命周期函数在复写的,必须调用父类的对应生命周期函数。
Fragment 需要依附在Activity中使用,Activity 通过FragmentManager对其所拥有的 Fragment 进行管理。Fragment 有两种,一种是3.0系统以后自带的 Fragment;一种是 support.v4包下的Fragment;这里建议用 support.v4包下面的 Fragment,因为 support.v4包是可以更新的,官方经常会对这些包里的控件等进行更新和优化,如果你用系统自带的 Fragment 就没法应用这些优化了。
不同的管理方式,会走不同的 Fragment 的生命周期。下面就来介绍几种 Activity通过 FragmentManager 管理 Fragment 的方式。
1、replace 方式
所谓的 replace 方式就是通过**FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment)或者FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment, @Nullable String tag)**方法根据需求显示不同的 Fragment。
简单的说就是用一个 Fragment 去替换当前容器中已经存在的 Fragment,相当与先 remove 掉已经存在的 Fragment,然后在调用 add 将需要显示的Fragment 加到容器中去。下面让我们看下 replace 下 Fragment 的生命周期是怎样的。
我们有两个 Fragment,一个是 FirstFragment,一个是 SecondFragment。通过两个按钮来进行 replace 操作。
private void changeFragment(int index) {
if (index == 1) {
if (firstFragment == null) {
firstFragment = new FirstFragment();
}
Log.e(getClass().getSimpleName(),"<-------------------Replace FirstFragment to SecondFragment------------------------>");
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.fl_container, firstFragment).commit();
} else if (index == 2) {
if (secondFragment == null) {
secondFragment = new SecondFragment();
}
Log.e(getClass().getSimpleName(),"<-------------------Replace SecondFragment to FirstFragment------------------------>");
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.fl_container, secondFragment).commit();
}
}
FirstFragment 和 SecondFragment 很简单,就是给每个生命周期打印一个 log,代码就不上了。
当我们打开当前的 Activity 时,我们将第一个 Fragment 加到 Activity 中,生命周期如下:
此时我们在点击切换到 SecondFragment,生命周期如下
从上面的生命周期我们可以看到,当我们用 SecondFragment replace FirstFragment的时候,FirstFragment实例会销毁。
2、show hide 方式
private void changeShowHidden(int index) {
if (firstFragment == null) {
firstFragment = new FirstFragment();
}
if (secondFragment == null) {
secondFragment = new SecondFragment();
}
if (index == 1) {
if (firstFragment.isAdded()) {
if (firstFragment.isHidden()) {
Log.e(getClass().getSimpleName(),"<-------------------show FirstFragment hide SecondFragment------------------------>");
getSupportFragmentManager().beginTransaction().hide(secondFragment).show(firstFragment).commit();
}
} else {
if (secondFragment.isAdded()) {
Log.e(getClass().getSimpleName(),"<-------------------add firstFragment------------------------>");
getSupportFragmentManager().beginTransaction().hide(secondFragment).add(R.id.fl_container,firstFragment).commit();
} else {
Log.e(getClass().getSimpleName(),"<-------------------add firstFragment------------------------>");
getSupportFragmentManager().beginTransaction().add(R.id.fl_container, firstFragment).commit();
}
}
} else if (index == 2) {
if (secondFragment.isAdded()) {
if (secondFragment.isHidden()) {
Log.e(getClass().getSimpleName(),"<-------------------show SecondFragment hide FirstFragment------------------------>");
getSupportFragmentManager().beginTransaction().hide(firstFragment).show(secondFragment).commit();
}
} else {
if (firstFragment.isAdded()) {
Log.e(getClass().getSimpleName(),"<-------------------add secondFragment------------------------>");
getSupportFragmentManager().beginTransaction().hide(firstFragment).add(R.id.fl_container, secondFragment).commit();
} else {
Log.e(getClass().getSimpleName(),"<-------------------add firstFragment------------------------>");
getSupportFragmentManager().beginTransaction().add(R.id.fl_container, secondFragment).commit();
}
}
}
}
刚进入Activity 的时候:
这时候点击Fragment2的按钮
这时候我们再次点击 Fragment2的按钮
同时我在 FirstFragment 和 SecondFragment 中分别放了一个 EditText,在行切换 Fragment 的时候,两个 EditText 中的内容都没有丢失。
从上面的打印的生命周期函数和页面显示的内容我们可以看到,在进行 show 和 hide 的时候,FirstFragment 和 SecondFragment 并没有并销毁,试图也没有进行重建。此种方式可以很好的复用已经存在的Fragment。那么,我们在有些需要保存页面状态的时候可以使用 show 和 hide 来进行操作。
3、attach 和 detach 方式
detach 将View 从 UI 上移除,这和 replace 和 remove(replace 相当于 remove+add,所以这里不单独介绍 remove 了)类似,不同的是 detach 之后,Fragment 的状态还会由 FragmentTransaction 保持。仅仅是通过调用 onDestroyView 来销毁试图层的结构,并不销毁实例,当再次需要该 Fragment 的时候,也不会重新创建该 Fragment,而是从 onCreateView重新进行视图层的结构构建。
我们首先看代码,这里我放了三个按钮放在界面上一个 attch,一个 detach, 一个判断Fragment 是否还是 add 状态
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_attach_detach);
findViewById(R.id.btn_attach).setOnClickListener(view->{
changeFragment(1);
});
findViewById(R.id.btn_detach).setOnClickListener(view->{
changeFragment(2);
});
findViewById(R.id.btn_is_add).setOnClickListener(view-> {
Log.e(getClass().getSimpleName(),"<------------------- after attach isAdd " + firstFragment.isAdded() + "------------------------>");
});
if (firstFragment == null) {
firstFragment = new FirstFragment();
}
getSupportFragmentManager().beginTransaction().add(R.id.fl_container, firstFragment).commit();
}
private void changeFragment(int type) {
if (type == 1) {
Log.e(getClass().getSimpleName(),"<-------------------attach FirstFragment------------------------>");
getSupportFragmentManager().beginTransaction().attach(firstFragment).commit();
} else {
Log.e(getClass().getSimpleName(),"<-------------------detach FirstFragment------------------------>");
getSupportFragmentManager().beginTransaction().detach(firstFragment).commit();
}
}
我们第一次进入页面的时候先将 Fragment add 到 Activity 中去。
然后点击 Detach,接着点击 IS Fragment Add 按钮
这里可以看到 Fragment 只走到了 onDestroyView 回调函数,并没有走 onDestroy 和 onDetach 方法。也就是说仅仅是将视图给销毁了,并没有销毁 Fragment 实例。
我们再点击 Attach 按钮,然后再点击IS Fragment Add 按钮
发现只是从 onCreateView 开始重建视图。那么我们怎么能确认是否是重建了视图呢。其实很简单,我们在 Fragment 中的 EditText 中输入一些文字,当我们 detach 后再 Attach 会发现 EditText 中的内容消失了。
注意:以上讨论都是不考虑回退栈的情况。
4、Fragment 回退栈
上面我们讲到了有关 Fragment 的 replace(add、remove)、show(hide)、attach(detach)。那么如果我想讲上次的 commit 返回怎么办呢?这里就要用到FragmentTransaction的回退栈的一些知识了。
在说回退栈之前我们首先来简单说下 add、remove 和 replace 的简单流程。
add(int containerViewId, Fragment fragment, String tag); 这个方法是将Fragment(里面保存了该 fragment 属于哪个 containerId 所表示的容器) 放到 Activity 的 Fragment 的队列的最上层,同样 remove 就是删除队列中指定的 fragment。而
replace(int containerViewId, Fragment fragment); 则是替换整个containerViewId中的Fragment 全部清掉,然后再将当前的Fragment Add 进队列中。
下面我们来看下回退栈的有关知识。
1)、transaction.addToBackStack(String tag);
在 commit 之前将本次事务加到回退栈中去。
2)、manager.popBackStack();
在需要回退的时候调用此方法将栈顶的事务弹出回退栈。popBackStack默认弹出的是最上层的操作内容。
当栈中有多层的操作,而我们想弹出指定的操作的话就要用到下面的两个方法了
void popBackStack(int id, int flags);
void popBackStack(String name, int flags);
注意:这里调用popBackStack来弹出回退栈中的事务时,其实是将该操作加入到 FragmentManager 的操作队列中等待循环执行的,也就是说操作有可能不会立即执行,如果需要立即执行的话可以调用下面几个方法
popBackStackImmediate()
popBackStackImmediate(String tag)
popBackStackImmediate(String tag, int flag)
popBackStackImmediate(int id, int flag)
这里简单介绍下各个参数的意义。
id:这个参数就是当我们commit 时候的返回值;
name: 这个是 addToBackStack 时候的 tag 值;
flag: 0或FragmentManager.POP_BACK_STACK_INCLUSIVE。当为0时表示除了第一个参数指定的这层之外,其上面的层全部弹出,弹出后参数一的层为栈顶;当为POP_BACK_STACK_INCLUSIVE时,表示,连同参数一指定的层及其上面的层一起弹出。
下面我们来看下具体情况。
1)、
private int stackId1, stackId2, stackId3, stackId4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transaction);
findViewById(R.id.btn_add_fragment_1).setOnClickListener(view->{
stackId1 = addFragment(new AddToBackFragment1(), "fragment_1");
});
findViewById(R.id.btn_add_fragment_2).setOnClickListener(view->{
stackId2 = addFragment(new AddToBackFragment2(), "fragment_2");
});
findViewById(R.id.btn_add_fragment_3).setOnClickListener(view->{
stackId3 = addFragment(new AddToBackFragment3(), "fragment_3");
});
findViewById(R.id.btn_add_fragment_4).setOnClickListener(view->{
stackId4 = addFragment(new AddToBackFragment4(), "fragment_4");
});
findViewById(R.id.btn_pop_back).setOnClickListener(view->{
popBackStack();
});
}
private int addFragment(Fragment fragment, String stackName) {
Log.e(getClass().getSimpleName(),"<-------------------"+ stackName +" add------------------------>");
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fl_container, fragment);
fragmentTransaction.addToBackStack(stackName);
return fragmentTransaction.commit();
}
private void popBackStack() {
Log.e(getClass().getSimpleName(),"<-------------------pop------------------------>");
getSupportFragmentManager().popBackStack();
}
当我们将第一个 Add 第一个 Fragment 时,并在页面的 EditText 中输入一串字符串,可以看到生命周期为
当将第二个 Fragment 加入之后
我们发现在加入 Fragment2时 Fragment 不进实例没有被销毁,视图结构也没有销毁(至少从打印的生命周期来看是这样的)。
为了进一步验证我们刚才所说的,我们来点击 pop_back_stack 按钮,是事务回滚来看下。
从上面的图可以看到之前我们之前输入的字符串依然在视图上可以看到。也就进一步验证了上面我们说的。
2)、接下来我们来看看之前说们说到popBackStack有个 flags的参数。
先看代码
findViewById(R.id.btn_pop_fragment_3_0).setOnClickListener(view->{
popBackStack(stackId3, 0);
});
findViewById(R.id.btn_pop_fragment_3_inclusive).setOnClickListener(view->{
popBackStack(stackId3, FragmentManager.POP_BACK_STACK_INCLUSIVE);
});
private void popBackStack(int id, int flag) {
if (flag == 0) {
Log.e(getClass().getSimpleName(),"<-------------------popBackStack 0------------------------>");
} else {
Log.e(getClass().getSimpleName(),"<-------------------popBackStack POP_BACK_STACK_INCLUSIVE------------------------>");
}
getSupportFragmentManager().popBackStack(id, flag);
}
首先我们将 fragment1~4全部加到Activity 中。
现在栈中最上层的是Fragment4。
我们现在点击 POP_FRAGMENT_3_0看下效果,
先看界面
我们发现这里是只把 Fragment4给清掉了。
从生命周期回调我们也可以看出仅仅是把 Fragment4给清除掉了。
接着我们看下POP_BACK_STACK_INCLUSIVE的情况,我们回到之前 Fragment4还在的情形,然后点击POP_FRAGMENT_3_INCLUSIVE看下效果:
我们看到现在显示的是 fragment_2,也就是说这里将 Fragment3一起都清掉了。
所有我们可以验证上面我们说的当 flag 参数为0时仅弹出 id(name)所指定的那个 Fragment 上面的那些的 Fragment,如果 flag 为POP_BACK_STACK_INCLUSIVE那么会连同 id 所指定的那个 Fragment 一起弹出。
5、FragmentTransaction 回滚的事务性
private void addAllFragment() {
Log.e(getClass().getSimpleName(),"<-------------------add all fragment------------------------>");
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fl_container, new AddToBackFragment1());
fragmentTransaction.add(R.id.fl_container, new AddToBackFragment2());
fragmentTransaction.add(R.id.fl_container, new AddToBackFragment3());
fragmentTransaction.add(R.id.fl_container, new AddToBackFragment4());
fragmentTransaction.addToBackStack("all Fragment");
fragmentTransaction.commit();
}
这里我们先把所有的 Fragment 通过一次 commit 加到Activity 中去。
现在我们点击POP_BACK_STACK 按钮,看看有什么效果
我们可以看到,我们只点击了一次 POP_BACK_STACK 按钮,所有的 Fragment 都被清掉了。
所以我们可以得出来这样一个结论:回滚是以一次提交的事务为单位进行的
好了这篇文章先介绍到这,下篇将介绍 Fragment 和 Activity 以及 Fragment 与 Fragment 之间的通信。