首先对项目做一个概述:
整个app只有一个MainActivity,首次进入app显示登入注册相关Fragment页面,登入后进入MainFragment页面,MainFragment布局由ViewPager和5个RadioButton构成,ViewPager承载5个fragment,RadioButton用于切换fragment;
项目中封装了BaseFragment处理一些常用的操作,所有Fragment继承于BaseFragment。
popBackStack(String name, int flags)参数的意义:
name为null,flags为0,弹出栈中最上层的fragment;
name为null,flags为1,弹出栈中所有的fragment;
name不为null,flags为0,弹出栈中该Fragment之上的Fragment;
name不为null,flags为1,弹出栈中该Fragment和之上的Fragment;
name为添加到返回栈中的name;
protected final void replaceFragment(Fragment fragment, boolean isAddToBackStack) {
if (getActivity() == null) return;
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_contains, fragment);
if (isAddToBackStack) {
transaction.addToBackStack(fragment.getClass().getName());
}
transaction.commit();
}
(1)、MainActivity会重建,onCreate方法中的savedInstanceState不为空,并且会恢复FragmentManager之前的状态及返回栈,所以onCreate中需要判断savedInstanceState是否为空,如果为空就添加Fragment,如果不为空就不处理,由系统恢复Fragment即可;
(2)、在重建过程中,先调用栈顶Fragment以及它的目标Fragment的onCreate,然后再调用Activity的onCreate,而不是先调用Activity的onCreate;
(3)、Fragment中的数据要在onSaveInstanceState方法中保存,在onCreate中将数据恢复;
(4)、Activity中的数据要在onSaveInstanceState方法中保存,在onCreate或onRestoreInstanceState中将数据恢复;如果Fragment中有访问Activity中的对象,需要考虑Fragment访问的对象是否已经初始化或者恢复数据,否则出现空指针或者bug;
(1)首先是通过FragmentManager获取当前显示的Fragment,如果属于BaseFragment,就调用它的onBackPressed,并返回boolean值,如果为ture,表示处理完毕,如果返回false,继续由onBack方法处理;
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_contains);
if (fragment instanceof BaseFragment) {
if (((BaseFragment) fragment).onBackPressed()) {
return;
} else {
onBack();
}
} else {
onBack();
}
}
getSupportFragmentManager().findFragmentById方法的是查找add列表里面最顶上的那个Fragment,因为页面切换使用的是replace方法,所以add列表里面只会有当前显示的Fagment;
(2)onBack方法中查询FragmentManager中的返回栈中Fragment数量,如果是0表示当前只有一个页面:
private long time = 0;
public void onBack() {
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() == 0) {
long current = System.currentTimeMillis();
if ((current - time) > 2000) {
time = current;
GoodToast.show(R.string.exit_app, Toast.LENGTH_SHORT);
} else {
super.onBackPressed();
}
} else {
super.onBackPressed();
}
}
FragmentStatePagerAdapter 会销毁不需要的Fragment,并从FragmentManager中移除,同时会调用onSaveInstanceState方法保存数据,下次显示时可以恢复数据;
FragmentPagerAdapter只会销毁Fragment的视图,onDestroyView会被调用,Fragment的实例还会保存在FragmentManager中,下次显示时会走onCreateView方法重新创建view;
两者相比FragmentStatePagerAdapter更节省内存,适用tab比较多的情况。
getChildFragmentManager()区别于getActivity().getSupportFragmentManager(),因为在Fragment中使用ViewPager显示多个Fragment,所以需要使用fragment中的FragmentManager;
如果使用getActivity().getSupportFragmentManager(),在ViewPager中的Fragment打开另一个Fragment时有两个问题:
(1)、两个Fragment的onCreateOptionsMenu都会被调用,从而导致被打开的Fragment的Toolbar菜单按钮点击事件失效,且会一起显示出来;
(2)、返回时,ViewPager中的Fragment的视图会为空,显示空白;
anim0:要打开的页面的动画
anim1:打开页面时被关闭的页面的动画
anim2:返回后要重新显示的页面的动画
anim3:返回时要关闭的页面的动画
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)设置转场动画为淡入淡出
(1)、通过接口回调;
(2)、给做处理工作的Fragment设置目标Fragment,处理完成后调用它目标Fragment的onActivityResult方法告知处理结果,但是这里会有一个容易出问题的地方,如果这两个Fraggment的不是由同一个FragmentManager管理,会报下面异常:
java.lang.IllegalStateException: Fragment * declared target fragment * that does not belong to this FragmentManager!
java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState;
解决方法
(1)、对于添加新的Fragment可以使用transaction.commitAllowingStateLoss()来代替transaction.commit()来避免这个异常;
(2)、对于页面返回,调用FragmentManager.popBackStack()或者getActivity().onBackPressed(),必须要判断isStateSaved()的值,为true时不能进行页面返回,为false时才能返回;
(3)、isStateSaved()的返回值在onSaveInstanceState调用时为true,在onResume时为false;
如果A跳转到B时,将A添加到返回栈,B跳转到C时,不将B添加到返回栈,然后在C中返回,虽然是直接返回到了A,但是C不会被FragmentManager移除,也不会被销毁;
正确的处理方式是将A,B都添加到返回栈中transaction.addToBackStack(fragment.getClass().getName()),C返回时使用FragmentManager的popBackStack(String name, int flags)方法将B、C出栈;
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && getView() != null) {
lazyLoading();
}
}
protected void lazyLoading() {
}
@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint()) {
lazyLoading();
}
}
setUserVisibleHint方法的调用时机是fragment被切换到时,逻辑很简单判断fragment如果可见并且view已经创建,就会调用lazyLoading();
假设ViewPager的OffscreenPageLimit为1,会预加载两个Fragment,加载过程中调用顺序是setUserVisibleHint—onCreateVeiw—onResume,因为view还没有被创建,所以两个fragment的lazyLoading()方法不会被调用,但是选中的fragment会在view被创建之后再次调用setUserVisibleHint方法,此时lazyLoading()方法就会被调用;另一个未选中的fragment在被选中时同样也会调用setUserVisibleHint方法,因为在预加载过程中view已经创建,所以lazyLoading()会被调用到;
如果当前显示第一个Fragment,点击第三个Fragment,因为第三个Fragment的视图未创建,getView为null,所以setUserVisibleHint方法里面的lazyLoading不会被执行,但是在onResume中,getUserVisibleHint此时返回true会去执行lazyloading。
但是这里还是有点问题,lazyLoading()并不是只会在fragment第一次显示时执行,当fragment再次显示时,lazyLoading()仍然会被调用,所以需要在lazyLoading中判断如果数据为空时才请求数据;