fragment使用技巧

以前寫多個fragment切換是經常使用這種方法切換fragment:

/**
  * 使用replace切換頁面
  * 顯示fragment
  */
 private void showFragment(Fragment fg){

     FragmentTransaction transaction = fragmentManager.beginTransaction();
     transaction.replace(R.id.content, fg);
     transaction.commit();

 }

replace():該方法只是在上一個Fragment不再需要時採用的簡便方法,弊端就是如果需要重複使用該fragment時,需要每次都要重新加載一次。比如我在第一個fragment輸入信息後,切換第二個fragment後再切換回去,就會造成數據丟失

而且,如果每切換一次就實例化一次的話,FragmentManager管理下的棧也會爆滿,最終會導致手機卡頓,這很明顯不是正確的Fragment使用姿勢,這時,我們就需要使用show()、hide()、add()了,正確的切換方式是add(),切換時hide(),add()另一個Fragment;再次切換時,只需hide()當前,show()另一個就行了,代碼修改如下:


/**
  * 使用show() hide()切換頁面
  * 顯示fragment
  */
 private void showFragment(Fragment fg){

     FragmentTransaction transaction = fragmentManager.beginTransaction();

     //如果之前沒有添加過
     if(!fg.isAdded()){
         transaction
                 .hide(currentFragment)
                 .add(R.id.content,fg);
     }else{
         transaction
                 .hide(currentFragment)
                 .show(fg);
     }

    //全局變量,記錄當前顯示的fragment
     currentFragment = fg;

     transaction.commit();

 }
即使切換到別的fragment,再切換回來,數據還依然存在,這就避免了Fragment切換時佈局重新實例化。

安卓app有一種特殊情況,就是 app運行在後臺的時候,系統資源緊張的時候導致把app的資源全部回收(殺死app的進程),這時把app再從後臺返回到前臺時,app會重啓。這種情況下文簡稱爲:“內存重啓”。(屏幕旋轉等配置變化也會造成當前Activity重啓,本質與“內存重啓”類似)在系統要把app回收之前,系統會把Activity的狀態保存下來,Activity的FragmentManager負責把Activity中的Fragment保存起來。在“內存重啓”後,Activity的恢復是從棧頂逐步恢復,Fragment會在宿主Activity的onCreate方法調用後緊接着恢復

當我們不退出軟件,只是後臺掛着去幹別的事,當系統內存不足回收我們這個app時,再切換回來,app的這幾個Fragment界面會重疊。

三個fragment全部疊在了一起,而且點擊上面菜單也不能消除重疊。顯然,這並不是我們想要的,沒事,繼續解決問題,使用findFragmentByTag:即在add()或者replace()時綁定一個tag,一般我們是用fragment的類名作爲tag,然後在發生“內存重啓”時,通過findFragmentByTag找到對應的Fragment,並hide()需要隱藏的fragment。,修改如下:

/**
  * 使用show() hide()切換頁面
  * 顯示fragment
  */
 private void showFragment(Fragment fg){

     FragmentTransaction transaction = fragmentManager.beginTransaction();

     //如果之前沒有添加過
     if(!fg.isAdded()){
         transaction
                 .hide(currentFragment)
                 .add(R.id.content,fg,fg.getClass().getName());  //第三個參數爲當前的fragment綁定一個tag,tag爲當前綁定fragment的類名
     }else{
         transaction
                 .hide(currentFragment)
                 .show(fg);
     }

     currentFragment = fg;

     transaction.commit();

 }
別急,還沒完,在當前Activity的onCreate()方法裏面添加一下代碼:
if (savedInstanceState != null) { // “內存重啓”時調用

   //從fragmentManager裏面找到fragment
   fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName());
   fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName());
   fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName());

   //解決重疊問題show裏面可以指定恢復的頁面
   fragmentManager.beginTransaction()
           .show(fgOne)
           .hide(fgTwo)
           .hide(fgThree)
           .commit();

   //把當前顯示的fragment記錄下來
   currentFragment = fgOne;

}else{      //正常啓動時調用

   fgOne = new OneFragment();
   fgTwo = new TwoFragment();
   fgThree = new ThreeFragment();

   showFragment(fgOne);
}

OK,當app後臺時遇到“內存重啓”的情況下,再返回我們的app,就會恢復到show(fgOne)頁面,而且還不會造成重疊問題!


很顯然,這樣結束是不道德的,因爲有人會問了,如果想記錄當前退出的狀態以至於下次恢復時直接顯示之前的fragment頁面怎麼辦,恩,對於這個問題,我們可以在activity的onSaveInstanceState()方法中記錄一下“內存重啓”之前的Fragment的頁面,然後在oncreate()中取出來,根據保存的頁面來顯示到指定的fragment,代碼如下:

@Override
protected void onSaveInstanceState(Bundle outState) {

    //“內存重啓”時保存當前的fragment名字
    outState.putString(STATE_FRAGMENT_SHOW,currentFragment.getClass().getName());
    super.onSaveInstanceState(outState);
}
然後在oncreate()方法中添加(修改上面的那個代碼)

if (savedInstanceState != null) { // “內存重啓”時調用

    //獲取“內存重啓”時保存的fragment名字
    String saveName = savedInstanceState.getString(STATE_FRAGMENT_SHOW);

    //從fragmentManager裏面找到fragment
    fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName());
    fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName());
    fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName());

    //如果爲空就默認操作
    if(TextUtils.isEmpty(saveName)){
        //解決重疊問題
        fragmentManager.beginTransaction()
                .show(fgOne)
                .hide(fgTwo)
                .hide(fgThree)
                .commit();

        //把當前顯示的fragment記錄下來
        currentFragment = fgOne;

    }else{

        if(saveName.equals(fgOne.getClass().getName())){    //如果推出之前是OneFragment

            //解決重疊問題
            fragmentManager.beginTransaction()
                    .show(fgOne)
                    .hide(fgTwo)
                    .hide(fgThree)
                    .commit();

            //把當前顯示的fragment記錄下來
            currentFragment = fgOne;

        }else if(saveName.equals(fgTwo.getClass().getName())){  //如果推出之前是TwoFragment

            //解決重疊問題
            fragmentManager.beginTransaction()
                    .show(fgTwo)
                    .hide(fgOne)
                    .hide(fgThree)
                    .commit();

            //把當前顯示的fragment記錄下來
            currentFragment = fgTwo;

        }else{    //如果推出之前是ThreeFragment

            //解決重疊問題
            fragmentManager.beginTransaction()
                    .show(fgThree)
                    .hide(fgTwo)
                    .hide(fgOne)
                    .commit();

            //把當前顯示的fragment記錄下來
            currentFragment = fgThree;

        }

    }


}else{      //正常啓動時調用

    fgOne = new OneFragment();
    fgTwo = new TwoFragment();
    fgThree = new ThreeFragment();

    showFragment(fgOne);
}
OK,這樣就可以了,我們通過保存當前顯示的fragment的類名,當我們在第二個fragment頁面時後臺,等到“內存重啓”後返回該app時,就根據之前保存的類名來判斷加載指定的fragment,而且,重疊的問題也解決了!

當然,有網友問了,如果fragment比較多,那麼多if多麻煩,是啊,上面的代碼主要就是讓大家理解一下思路,具體開發不建議那麼寫,下面,就說一下上面的代碼該如何精簡吧:





創建一個List用來存所有的fragment,給fragment設置tag可以使用當前的索引index,然後在onSaveInstanceState中保存當前的索引,在恢復時通過索引來找到fragment

全部代碼修改精簡後如下:::

//當前顯示的fragment
private static final String CURRENT_FRAGMENT = "STATE_FRAGMENT_SHOW";

private TextView tvone;
private TextView tvtwo;
private TextView tvthree;
private FragmentManager fragmentManager;

private Fragment currentFragment = new Fragment();
private List<Fragment> fragments = new ArrayList<>();

private int currentIndex = 0;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.tvthree = (TextView) findViewById(R.id.tv_three);
    this.tvtwo = (TextView) findViewById(R.id.tv_two);
    this.tvone = (TextView) findViewById(R.id.tv_one);

    fragmentManager = getSupportFragmentManager();

    tvthree.setOnClickListener(this);
    tvtwo.setOnClickListener(this);
    tvone.setOnClickListener(this);


    if (savedInstanceState != null) { // “內存重啓”時調用

        //獲取“內存重啓”時保存的索引下標
        currentIndex = savedInstanceState.getInt(CURRENT_FRAGMENT,0);

        //注意,添加順序要跟下面添加的順序一樣!!!!
        fragments.removeAll(fragments);
        fragments.add(fragmentManager.findFragmentByTag(0+""));
        fragments.add(fragmentManager.findFragmentByTag(1+""));
        fragments.add(fragmentManager.findFragmentByTag(2+""));

        //恢復fragment頁面
        restoreFragment();


    }else{      //正常啓動時調用

        fragments.add(new OneFragment());
        fragments.add(new TwoFragment());
        fragments.add(new ThreeFragment());

        showFragment();
    }

}

@Override
protected void onSaveInstanceState(Bundle outState) {

    //“內存重啓”時保存當前的fragment名字
    outState.putInt(CURRENT_FRAGMENT,currentIndex);
    super.onSaveInstanceState(outState);
}

@Override
public void onClick(View v) {

    switch (v.getId()){

        case R.id.tv_one:

            currentIndex = 0;

            break;
        case R.id.tv_two:

            currentIndex = 1;

            break;
        case R.id.tv_three:

            currentIndex = 2;

            break;

    }

    showFragment();

}


/**
 * 使用show() hide()切換頁面
 * 顯示fragment
 */
private void showFragment(){

    FragmentTransaction transaction = fragmentManager.beginTransaction();

    //如果之前沒有添加過
    if(!fragments.get(currentIndex).isAdded()){
        transaction
                .hide(currentFragment)
                .add(R.id.content,fragments.get(currentIndex),""+currentIndex);  //第三個參數爲添加當前的fragment時綁定一個tag

    }else{
        transaction
                .hide(currentFragment)
                .show(fragments.get(currentIndex));
    }

    currentFragment = fragments.get(currentIndex);

    transaction.commit();

}

/**
 * 恢復fragment
 */
private void restoreFragment(){


    FragmentTransaction mBeginTreansaction = fragmentManager.beginTransaction();

    for (int i = 0; i < fragments.size(); i++) {

        if(i == currentIndex){
            mBeginTreansaction.show(fragments.get(i));
        }else{
            mBeginTreansaction.hide(fragments.get(i));
        }

    }

    mBeginTreansaction.commit();

    //把當前顯示的fragment記錄下來
    currentFragment = fragments.get(currentIndex);

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章