BottomNavigationView下Fragment的两种切换方式

这个文章比较“肤浅”,但是其实网上对于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]);
            //加上判断fragment是否为空,为空要new一个
            fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
            fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
            fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
        }

通过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]);
             //加上判断fragment是否为空,为空要new一个
            fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
            fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
            fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
        }

        //设置默认
        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;
    }
}

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android有一个回收机制,当内存不足时,会自动回收相关内存。 我们使用FragmentActivity放入Fr...
    简单Liml阅读 1,596评论 0 0
  • Fragment,俗称碎片,自 Android 3.0 开始被引进并大量使用。然而就是这样耳熟能详的一个东西,在开...
    亦枫阅读 23,884评论 9 84
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 保持着执拗 有些执着 我并非是好人 也未必是坏人 现在的我不缺什么突然害怕了闭眼之后的什么 现在的你不要什么经历的...
    克塞尔阅读 155评论 0 2
  • 田蛙哭夏逝,夜夜泣无休。 陌上呱呱咒,孰怜万物忧?
    童姥阅读 241评论 0 2