美文网首页AndroidAndroid知识Android技术知识
BottomNavigationView下Fragment的两种

BottomNavigationView下Fragment的两种

作者: 聪葱忙忘 | 来源:发表于2017-08-01 16:34 被阅读202次

    这个文章比较“肤浅”,但是其实网上对于Fragment切换这么肤浅的事情也甚少有文章说的清楚,所以稍微介绍下。

    BottomNavigationView

    网上有好多关于BottomNavigationView的教程,讲的挺详细的,本文这里没有细讲这个的意向,但是下面用到BottomNavigationView的监听事件:

    bottomNavi.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    switch (item.getItemId()){
    case R.id.home:
    switchFragment(0);
    return true;
    case R.id.found:
    switchFragment(1);
    return true;
    case R.id.account:
    switchFragment(2);
    return true;
    }

                return false;
            }
        });
    

    注意在case里面要return true,要不切换的时候会没有动画效果的。
    switchFragment 是我切换显示fragment的方法。

    初始化fragment的列表

    private static final String TAG_HOME = "home";
    private static final String TAG_FOUND = "found";
    private static final String TAG_ACCOUNT = "account"
    private static final String[] TAGS = {"home","found","account"};
    private void buildFragmentList() {
    BdHomeFragment homeFragment = new BdHomeFragment();
    BdFoundFragment foundragment = new BdFoundFragment();
    BdAccountFragment accountFragment = new BdAccountFragment();
    fragments.add(homeFragment);
    fragments.add(foundragment);
    fragments.add(accountFragment);
    }

    fragment的切换方式一:replace

    private void switchFragment(int pos, String tag) {
    getSupportFragmentManager()
    .beginTransaction()
    .replace(R.id.fragmentholder, fragments.get(pos), tag)
    .commit();
    }

    R.id.fragmentholder是fragment的容器,上面有提及过,在这里使用,为显示的fragment指定容器。
    这种方式比较简单,直接初始化fragment的list和写好对应的tag后,切换一次直接replace就好了。

    fragment的切换方式二,hide,show,重点说这个。

    因为hide,show的使用方式不当的话,会导致很多bug。
    比如说重叠问题,重叠问题这个在android 23版本上被修复了。
    但是在使用23版本上有时候还是会遇到回收内存后界面重叠的情况,那就是你的打开方式不对了。
    看下面:
    在onCreate里面调用的设置默认界面,比如说三个fragment,我让第二个为默认,就设置1,这很简单,没有问题。
    //设置默认
    prePos = 0
    setDefaultFragment(prePos );

    private void setDefaultFragment(int pos){
        Fragment now = fragments.get(pos);
        if(!now.isAdded()){
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.fragmentholder,fragments.get(prePos),TAGS[pos])
                    .commit();
        }else{
            getSupportFragmentManager()
                    .beginTransaction()
                    .show(now)
                    .commit();
        }
    }
    

    buildFragmentList 跟上面切换的一样。

    switchFragment: 在判断to是否add进去过了来判断是add还是show,这个也很简单。
    prePos 是记录了当前显示的fragment在list中的位置。
    为了
    private void switchFragment(int pos) {
    //Toast.makeText(this,prePos+" -> "+pos,Toast.LENGTH_LONG).show();
    FragmentTransaction transaction = getSupportFragmentManager()
    .beginTransaction();
    Fragment from = fragments.get(prePos);
    Fragment to = fragments.get(pos);
    if(!to.isAdded()){
    transaction.hide(from)
    .add(R.id.fragmentholder,fragments.get(pos),TAGS[pos])
    .commit();
    }else{
    transaction.hide(from)
    .show(to)
    .commit();
    }
    prePos = pos;
    }

    好了,代码都这么简单而且没有问题,然后发生重叠了。这不是打脸吗,而且翻过好多文章都说23以上修复了bug...Android不是在耍我们吧。
    思考下重叠原因,肯定是内存回收机制的原因。
    我们可以在android studio上通过一系列的骚操作来复现内存回收的情况:

    Paste_Image.png

    打开Android Device Monitor
    你可以看到你的应用在你的手机(真机也是可以的)上运行的线程,以包名显示,比如说是com.test.fragment 。
    你要模拟内存回收,运行应用后按home键回到桌面,然后在Android Device Monitor把com.test.fragment给stop了。
    然后再按进应用,内存回收又重启进入应用的一波骚操作你就完成了。在开发还是挺有用的。

    好了,继续分析,从生命周期说起。
    应用内存回收后会执行onSaveInstanceState这个方法,而且全局变量的会被清空掉,都被回收了,全局变量算什么,application都照样null了。
    所以我们还是要在onSaveInstanceState保存下我们珍贵的prePos,位置信息。
    因为BottomNavigationView比较灵活,比如说你滑到第二个界面,内存被回收了重启进去,切换状态还是在第二个的状态,只是我们这里上面的fragment显示重叠了。
    这样保存
    @Override
    public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //保存上一个位置
    outState.putInt(PRE,prePos);
    }

    然后在onCreate 中当savedInstanceState!=null时重新赋值。
    这样应该没问题了吧,位置信息对了,hide,show应该就不会有毛病了吧。
    然而并不是。
    还是重叠,仔细观察下。
    该show的Fragment是显示了,但是该消失的没有消失。。。
    看下切换代码,消失的是如何实现的
    FragmentTransaction transaction = getSupportFragmentManager()
    .beginTransaction();
    Fragment from = fragments.get(prePos);
    Fragment to = fragments.get(pos);
    if(!to.isAdded()){
    transaction.hide(from)
    .add(R.id.fragmentholder,fragments.get(pos),TAGS[pos])
    .commit();
    }else{
    transaction.hide(from)
    .show(to)
    .commit();
    }
    prePos = pos;

    Fragment from = fragments.get(prePos);
    transaction.hide(from)
    恍然大悟,内存回收后,此from和彼from看上去一样,实际上,内存上已经不一样了。
    你hide错fragment了,hide了个新的fragment,旧的还是show出来了。

    解决

    所以应该这么做。
    在onCreate 中

        if(savedInstanceState==null){
            //默认为0
            prePos = 0;
            fragments = new ArrayList<>(3);
            buildFragmentList();
        }else{
            //内存被回收了,fragments的list也被回收了,重新add进去
            prePos = savedInstanceState.getInt(PRE);
            fragments = new ArrayList<>(3);
            BdHomeFragment homeFragment = (BdHomeFragment) getSupportFragmentManager().findFragmentByTag(TAGS[0]);
            BdFoundFragment foundragment = (BdFoundFragment) getSupportFragmentManager().findFragmentByTag(TAGS[1]);
            BdAccountFragment accountFragment = (BdAccountFragment) getSupportFragmentManager().findFragmentByTag(TAGS[2]);
            fragments.add(homeFragment);
            fragments.add(foundragment);
            fragments.add(accountFragment);
        }
    

    通过findFragmentByTag来保证内存回收前后的fragment是一样的就ok了。

    上面懒得看,直接看hide show代码的

    public class BdMainActivity extends BaseActivity {

    @BindView(R.id.bottom_navi)
    BottomNavigationView bottomNavi;
    
    private ArrayList<Fragment> fragments ;
    private static final String TAG_HOME = "home";
    private static final String TAG_FOUND = "found";
    private static final String TAG_ACCOUNT = "account";
    private static final String[] TAGS = {"home","found","account"};
    private int prePos;
    private String PRE = "PREPOS";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bd_main);
        ButterKnife.bind(this); //初始化所有fragment
    
        //切换的点击事件
        bottomNavi.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.home:
                        switchFragment(0);
                        return true;
                    case R.id.found:
                        switchFragment(1);
                        return true;
                    case R.id.account:
                        switchFragment(2);
                        return true;
                }
    
                return false;
            }
        });
    
        if(savedInstanceState==null){
            //默认为0
            prePos = 0;
            fragments = new ArrayList<>(3);
            buildFragmentList();
        }else{
            //内存被回收了,fragments的list也被回收了,重新add进去
            prePos = savedInstanceState.getInt(PRE);
            fragments = new ArrayList<>(3);
            BdHomeFragment homeFragment = (BdHomeFragment) getSupportFragmentManager().findFragmentByTag(TAGS[0]);
            BdFoundFragment foundragment = (BdFoundFragment) getSupportFragmentManager().findFragmentByTag(TAGS[1]);
            BdAccountFragment accountFragment = (BdAccountFragment) getSupportFragmentManager().findFragmentByTag(TAGS[2]);
            fragments.add(homeFragment);
            fragments.add(foundragment);
            fragments.add(accountFragment);
        }
    
        //设置默认
        setDefaultFragment(prePos);
    }
    //设置默认
    private void setDefaultFragment(int pos){
        Fragment now = fragments.get(pos);
        if(!now.isAdded()){
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.fragmentholder,fragments.get(prePos),TAGS[pos])
                    .commit();
        }else{
            getSupportFragmentManager()
                    .beginTransaction()
                    .show(now)
                    .commit();
        }
    }
    
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //保存上一个位置
        outState.putInt(PRE,prePos);
    }
    
    private void buildFragmentList() {
        BdHomeFragment homeFragment = new BdHomeFragment();
        BdFoundFragment foundragment = new BdFoundFragment();
        BdAccountFragment accountFragment = new BdAccountFragment();
        fragments.add(homeFragment);
        fragments.add(foundragment);
        fragments.add(accountFragment);
    }
    
    private void switchFragment(int pos) {
        //Toast.makeText(this,prePos+" -> "+pos,Toast.LENGTH_LONG).show();
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();
        Fragment from = fragments.get(prePos);
        Fragment to = fragments.get(pos);
        if(!to.isAdded()){
            transaction.hide(from)
                    .add(R.id.fragmentholder,fragments.get(pos),TAGS[pos])
                    .commit();
        }else{
            transaction.hide(from)
                    .show(to)
                    .commit();
        }
        prePos = pos;
    }
    

    }

    虽然很简单,又说的很啰嗦,但是感觉还是挺实用的。

    相关文章

      网友评论

      • 做梦枯岛醒:这一会md一会不md的……害怕
      • 我一定会学会:老哥我发现一个问题:就是当你这个app,没切换过tab,也就是说只有DefaultFragment设置了tag,然后把app放在后台,一段时间后,资源被回收了,重新回到app,savedInstanceState!=null,因为DefaultFragment设置了tag,其他BdFoundFragment,BdAccountFragment没有设置tag,所以获取到的这两个是null,这就可能导致崩溃了!
        我一定会学会:@聪葱忙忘 哈哈,互帮互助!
        聪葱忙忘:兄dei,谢谢你,帮我找到了个bug,这个问题确实存在,我之前发现过这个问题,以为是fragment的bug,现在水落石出了
      • 我一定会学会:老哥,你的文章代码格式好像乱了
      • 我一定会学会:讲的很好,但是我发现用hide和show出现一个问题,就是当从后台返回app首页,所有被hide的界面都会走onresume的方法。如果我们再里面去请求网络,会所有的hide的fragment都回去请求网络。请问如何让当前显示的fragment,去请求,而不是所以的去请求。
        聪葱忙忘:在onResume中判断当前fragment是否show,isShow就请求网络

      本文标题:BottomNavigationView下Fragment的两种

      本文链接:https://www.haomeiwen.com/subject/ahbjlxtx.html