开发中经常遇到很多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都判断一下。