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;
    }
}

雖然很簡單,又說的很啰嗦,但是感覺還是挺實用的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android有一個回收機制,當內存不足時,會自動回收相關內存。 我們使用FragmentActivity放入Fr...
    簡單Liml閱讀 1,669評論 0 0
  • Fragment,俗稱碎片,自 Android 3.0 開始被引進并大量使用。然而就是這樣耳熟能詳的一個東西,在開...
    亦楓閱讀 24,068評論 9 84
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • 保持著執拗 有些執著 我并非是好人 也未必是壞人 現在的我不缺什么突然害怕了閉眼之后的什么 現在的你不要什么經歷的...
    克塞爾閱讀 161評論 0 2
  • 田蛙哭夏逝,夜夜泣無休。 陌上呱呱咒,孰憐萬物憂?
    童姥閱讀 265評論 0 2