BottomNavigationView下Fragment的兩種切換方式

這個(gè)文章比較“膚淺”,但是其實(shí)網(wǎng)上對(duì)于Fragment切換這么膚淺的事情也甚少有文章說(shuō)的清楚,所以稍微介紹下。

BottomNavigationView

網(wǎng)上有好多關(guān)于BottomNavigationView的教程,講的挺詳細(xì)的,本文這里沒(méi)有細(xì)講這個(gè)的意向,但是下面用到BottomNavigationView的監(jiān)聽(tīng)事件:

 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,要不切換的時(shí)候會(huì)沒(méi)有動(dòng)畫(huà)效果的。
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的容器,上面有提及過(guò),在這里使用,為顯示的fragment指定容器。
這種方式比較簡(jiǎn)單,直接初始化fragment的list和寫(xiě)好對(duì)應(yīng)的tag后,切換一次直接replace就好了。

fragment的切換方式二,hide,show,重點(diǎn)說(shuō)這個(gè)。

因?yàn)閔ide,show的使用方式不當(dāng)?shù)脑挘瑫?huì)導(dǎo)致很多bug。
比如說(shuō)重疊問(wèn)題,重疊問(wèn)題這個(gè)在android 23版本上被修復(fù)了。
但是在使用23版本上有時(shí)候還是會(huì)遇到回收內(nèi)存后界面重疊的情況,那就是你的打開(kāi)方式不對(duì)了。
看下面:
在onCreate里面調(diào)用的設(shè)置默認(rèn)界面,比如說(shuō)三個(gè)fragment,我讓第二個(gè)為默認(rèn),就設(shè)置1,這很簡(jiǎn)單,沒(méi)有問(wèn)題。
//設(shè)置默認(rèn)

    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進(jìn)去過(guò)了來(lái)判斷是add還是show,這個(gè)也很簡(jiǎn)單。
prePos 是記錄了當(dāng)前顯示的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;
    }

好了,代碼都這么簡(jiǎn)單而且沒(méi)有問(wèn)題,然后發(fā)生重疊了。這不是打臉嗎,而且翻過(guò)好多文章都說(shuō)23以上修復(fù)了bug...Android不是在耍我們吧。
思考下重疊原因,肯定是內(nèi)存回收機(jī)制的原因。
我們可以在android studio上通過(guò)一系列的騷操作來(lái)復(fù)現(xiàn)內(nèi)存回收的情況:

Paste_Image.png

打開(kāi)Android Device Monitor
你可以看到你的應(yīng)用在你的手機(jī)(真機(jī)也是可以的)上運(yùn)行的線程,以包名顯示,比如說(shuō)是com.test.fragment 。
你要模擬內(nèi)存回收,運(yùn)行應(yīng)用后按home鍵回到桌面,然后在Android Device Monitor把com.test.fragment給stop了。
然后再按進(jìn)應(yīng)用,內(nèi)存回收又重啟進(jìn)入應(yīng)用的一波騷操作你就完成了。在開(kāi)發(fā)還是挺有用的。

好了,繼續(xù)分析,從生命周期說(shuō)起。
應(yīng)用內(nèi)存回收后會(huì)執(zhí)行onSaveInstanceState這個(gè)方法,而且全局變量的會(huì)被清空掉,都被回收了,全局變量算什么,application都照樣null了。
所以我們還是要在onSaveInstanceState保存下我們珍貴的prePos,位置信息。
因?yàn)锽ottomNavigationView比較靈活,比如說(shuō)你滑到第二個(gè)界面,內(nèi)存被回收了重啟進(jìn)去,切換狀態(tài)還是在第二個(gè)的狀態(tài),只是我們這里上面的fragment顯示重疊了。
這樣保存

@Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //保存上一個(gè)位置
        outState.putInt(PRE,prePos);
    }

然后在onCreate 中當(dāng)savedInstanceState!=null時(shí)重新賦值。
這樣應(yīng)該沒(méi)問(wèn)題了吧,位置信息對(duì)了,hide,show應(yīng)該就不會(huì)有毛病了吧。
然而并不是。
還是重疊,仔細(xì)觀察下。
該show的Fragment是顯示了,但是該消失的沒(méi)有消失。。。
看下切換代碼,消失的是如何實(shí)現(xiàn)的

 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)

恍然大悟,內(nèi)存回收后,此from和彼from看上去一樣,實(shí)際上,內(nèi)存上已經(jīng)不一樣了。
你hide錯(cuò)fragment了,hide了個(gè)新的fragment,舊的還是show出來(lái)了。

解決

所以應(yīng)該這么做。
在onCreate 中

        if(savedInstanceState==null){
            //默認(rèn)為0
            prePos = 0;
            fragments = new ArrayList<>(3);
            buildFragmentList();
        }else{
            //內(nèi)存被回收了,fragments的list也被回收了,重新add進(jìn)去
            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一個(gè)
            fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
            fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
            fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
        }

通過(guò)findFragmentByTag來(lái)保證內(nèi)存回收前后的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

        //切換的點(diǎn)擊事件
        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){
            //默認(rèn)為0
            prePos = 0;
            fragments = new ArrayList<>(3);
            buildFragmentList();
        }else{
            //內(nèi)存被回收了,fragments的list也被回收了,重新add進(jìn)去
            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一個(gè)
            fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
            fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
            fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
        }

        //設(shè)置默認(rèn)
        setDefaultFragment(prePos);
    }
    //設(shè)置默認(rèn)
    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);
        //保存上一個(gè)位置
        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;
    }
}

雖然很簡(jiǎn)單,又說(shuō)的很啰嗦,但是感覺(jué)還是挺實(shí)用的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android有一個(gè)回收機(jī)制,當(dāng)內(nèi)存不足時(shí),會(huì)自動(dòng)回收相關(guān)內(nèi)存。 我們使用FragmentActivity放入Fr...
    簡(jiǎn)單Liml閱讀 1,714評(píng)論 0 0
  • Fragment,俗稱碎片,自 Android 3.0 開(kāi)始被引進(jìn)并大量使用。然而就是這樣耳熟能詳?shù)囊粋€(gè)東西,在開(kāi)...
    亦楓閱讀 24,295評(píng)論 9 84
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,637評(píng)論 18 399
  • 保持著執(zhí)拗 有些執(zhí)著 我并非是好人 也未必是壞人 現(xiàn)在的我不缺什么突然害怕了閉眼之后的什么 現(xiàn)在的你不要什么經(jīng)歷的...
    克塞爾閱讀 172評(píng)論 0 2
  • 田蛙哭夏逝,夜夜泣無(wú)休。 陌上呱呱咒,孰憐萬(wàn)物憂?
    童姥閱讀 311評(píng)論 0 2

友情鏈接更多精彩內(nèi)容