开发中经常遇到很多fragment 嵌套在一起,我们不仅需要在actvity中管理fragment, 在fragment也要管理所属的fragment,一个两个还好说,如果特别多的话,我们的项目嵌套很难管理,我们自己都觉得乱。
实际开发中我就遇到这种问题,之前都是每个模块一个activity,现在全是fragment,不光要处理跳转,还有可能支持fragment回退。我开始想怎么解决这个问题。
首先,先来看在MainActivity 中的 fragment 管理:
private void replaceFragment(int id) {
FragmentManager fgManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fgManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.anim_fragment_in,R.anim.anim_fragment_out);
hideAll(fragmentTransaction);
if (id == R.id.rb_home) {
if (homeMainFragment == null) {
homeMainFragment = new HomeMainFragment();
fragmentTransaction.add(R.id.li_content, homeMainFragment);
}
fragmentTransaction.show(homeMainFragment);
} else if (id == R.id.rb_entity) {
if (entityMainFragment == null) {
entityMainFragment = new EntityMainFragment();
fragmentTransaction.add(R.id.li_content, entityMainFragment);
}
fragmentTransaction.show(entityMainFragment);
} else if (id == R.id.rb_space) {
if (spaceMainFragment == null) {
spaceMainFragment = new MapMainFragment2();
fragmentTransaction.add(R.id.li_content, spaceMainFragment);
}
fragmentTransaction.show(spaceMainFragment);
} else if (id == R.id.rb_network) {
if (networkMainFragment == null) {
networkMainFragment = new NetworkMainFragment();
fragmentTransaction.add(R.id.li_content, networkMainFragment);
}
fragmentTransaction.show(networkMainFragment);
} else if (id == R.id.rb_mine) {
if (mineFragment == null) {
mineFragment = new MineFragment();
fragmentTransaction.add(R.id.li_content, mineFragment);
}
fragmentTransaction.show(mineFragment);
}
fragmentTransaction.commit();
}
private void hideAll(FragmentTransaction fragmentTransaction) {
if (homeMainFragment != null) fragmentTransaction.hide(homeMainFragment);
if (entityMainFragment != null) fragmentTransaction.hide(entityMainFragment);
if (spaceMainFragment != null) fragmentTransaction.hide(spaceMainFragment);
if (networkMainFragment != null) fragmentTransaction.hide(networkMainFragment);
if (mineFragment != null) fragmentTransaction.hide(mineFragment);
}
之所以没用replace的方式,因为要缓存fragment,这样可以提升MainActivity中的切换fragment速度。
再看fragment中切换 fragment的方式:
/**
* 替换布局
*
* @param fragment
* @param isAddToBackStack 是否加入后退栈
*/
public void replaceFragment(Fragment fragment, boolean isAddToBackStack) {
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.anim_fragment_in, R.anim.anim_fragment_out);
fragmentTransaction.replace(R.id.container, fragment);
if (isAddToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
}
是否加入回退栈中取决于这个fragment是否支持在点击back的时候返回上一个fragment。还有就是注意在fragment切换的时候使用 getChildFragmentManager() ,不能像activity那样使用getFragmentManager().
实际中最头疼的是这个我这么多的fragment,每一个都要在自己的里面写一个replaceFragment()方法,为什么呢,能不能写一个基类,把replaceFargment() 封装在里面呢?
答案当然是可以,不过有几个问题。 第一,每一个fragment的布局id不同,即使你封装了,还是要在每次替换的时候查看自己的布局id,很不方便; 第二,fragment是不能监听回退事件的,需要在activity中解决。下面来看我的一个思路。
我们都知道android 布局有一个最外层布局,叫做DecorView,它包装了一个 title 标题栏 和 content, 我们自己setContent()的时候其实是设置DecorView的content; 基于同样的思路,我设计了一个FragmentWrapper基类:
public abstract class FragmentWrapper extends Fragment {
private View view;
private View inc_title;
private ImageView img_title;
private TextView tv_title;
private RelativeLayout rl_container;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = LayoutInflater.from(getActivity()).inflate(R.layout.phoneapp_base_fragmentwrapper, container, false);
initWrapperView(view);
View customView = onCreateCustomerView(inflater, rl_container, savedInstanceState);
setContent(customView);
initView(customView);
initData();
return view;
}
private void initWrapperView(View view) {
rl_container = view.findViewById(R.id.rl_view_container);
inc_title = view.findViewById(R.id.inc_title);
img_title = view.findViewById(R.id.img_title);
tv_title = view.findViewById(R.id.tv_title);
}
private void setContent(View customerView) {
if (customerView == null) {
Log.e("FragmentWrapper error", "customerView can't be null");
return;
}
rl_container.removeAllViews();
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
customerView.setLayoutParams(params);
rl_container.addView(customerView);
}
public void replaceFragment(Fragment fragment) {
replaceFragment(fragment, true);
}
/**
* 替换自身布局
*
* @param fragment
* @param isAddToBackStack 是否加入后退栈
*/
public void replaceFragment(Fragment fragment, boolean isAddToBackStack) {
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.anim_fragment_in, R.anim.anim_fragment_out);
fragmentTransaction.replace(R.id.rl_wrapper_container, fragment);
if (isAddToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
}
public void replaceInnerFragment(int layoutId, Fragment fragment) {
replaceInnerFragment(layoutId, fragment, false);
}
/**
* 替换内部布局
*
* @param layoutId 内部布局id,注意此id必须在布局中存在
* @param fragment
* @param isAddToBackStack 是否加入后退栈中
*/
public void replaceInnerFragment(int layoutId, Fragment fragment, boolean isAddToBackStack) {
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.anim_fragment_in, R.anim.anim_fragment_out);
fragmentTransaction.replace(layoutId, fragment);
if (isAddToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
}
/**
* 设置标题是否可见 默认是不可见的
*
* @param b
*/
private void setTitleEnabled(Boolean b) {
if (b) {
inc_title.setVisibility(View.VISIBLE);
} else {
inc_title.setVisibility(View.GONE);
}
}
/**
* 设置标题文字
*
* @param text
*/
public void setTitleText(String text) {
if (inc_title.getVisibility() != View.VISIBLE)
inc_title.setVisibility(View.VISIBLE);
tv_title.setText(text);
}
/**
* 设置标题图标
*
* @param iconId
*/
public void setTitleIcon(int iconId) {
if (inc_title.getVisibility() != View.VISIBLE)
inc_title.setVisibility(View.VISIBLE);
img_title.setImageResource(iconId);
}
/**
* find and inflater your own layout
*
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
public abstract View onCreateCustomerView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState);
/**
* init your view here
*
* @param customView
*/
protected abstract void initView(View customView);
/**
* init some data here
*/
protected abstract void initData();
}
看下它的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_wrapper_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/inc_title"
layout="@layout/phoneapp_title_main"
android:visibility="gone"/>
<RelativeLayout
android:id="@+id/rl_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/inc_title"></RelativeLayout>
</RelativeLayout>
这样不论我们自己的fragment布局是什么,我们都只调用父类的replaceFrament()就可以了, 只有当我们替换子布局的时候,需要传入布局id。 是不是很ok?
然后是回退问题,如果我想点击回退的时候退回我上一个fragment, 首先达到这样的效果需要两个fragment在一个回退栈中,所以我们替换的时候,addToBackStack设置为true.
然后在activity中进行操作:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
LogUtil.debug("按下返回键");
ToastUtils.showToast(this, "按下返回键");
int checkId = rg_bottom.getCheckedRadioButtonId();
boolean isPopBack = true;
if (checkId == R.id.rb_home) {
isPopBack = popBackFragment(homeMainFragment);
} else if (checkId == R.id.rb_entity) {
isPopBack = popBackFragment(entityMainFragment);
} else if (checkId == R.id.rb_space) {
isPopBack = popBackFragment(spaceMainFragment);
} else if (checkId == R.id.rb_network) {
isPopBack = popBackFragment(networkMainFragment);
} else if (checkId == R.id.rb_mine) {
isPopBack = popBackFragment(mineFragment);
}
return isPopBack;
}
return super.onKeyDown(keyCode, event);
}
private boolean popBackFragment(Fragment fg) {
if (fg != null) {
if (fg.getChildFragmentManager().getBackStackEntryCount() > 0) {
fg.getChildFragmentManager().popBackStack();
return true;
}
}
return false;
}
上面activity中有五个主fragment, 意思就是我们现在有五个回退栈,所以要根据当前是处于哪一个fragment回退栈分别判断。网上很多使用接口来在fragment实现回退,但是我感觉没有必要,因为我们只需要一个地方统一判断就可以了,并不需要每一个fragment都判断一下。