1. 程式人生 > >Android中Fragment監聽返回按鈕及返回棧BackStack的一些處理

Android中Fragment監聽返回按鈕及返回棧BackStack的一些處理

考慮到耦合性,這篇部落格的重點是在Fragment程式碼裡面處理返回按鈕的事件,達到返回上一個Fragment的目的,利用一些資料傳遞,tag,介面什麼的最終還是在activity的onBackPressed處理事件的方法就重點不介紹了

我們知道Fragment是沒有onBackPressed方法的,所有如果你想達到點選返回按鈕就跳轉到上一次開啟的Fragment:

1,返回棧addToBackStack(推薦)

簡單,方便
原理方法就是在replace Fragment的時候將將當前fragment加入返回棧:

  protected void addFragmentWithBackStack(BaseFragment fragment) {
        FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction(); transaction.replace(R.id.main_frame, fragment, fragment.getClass().getName()); transaction.addToBackStack(null);//將fragment加入返回棧 transaction.commit(); }

transaction.addToBackStack(null)是將當前的Fragment放入一個ArrayList裡面,當你點選返回按鈕以後就會destroy當前顯示的Fragment,並visible上一個Fragment,這裡建議是replace而不能是add,他們的區別就不多說了,好處就是用replace在點選返回按鈕的時候會重新onstart和onresume,這樣在返回的時候就可以更新頁面.
你可以在點選返回的時候監聽這個事件:
返回的監聽方法:

 public void onCreate(@Nullable Bundle savedInstanceState) {
        getFragmentManager().addOnBackStackChangedListener(this);//註冊監聽
        context = (AppCompatActivity) getActivity();
        super.onCreate(savedInstanceState);

    }
    ..........
    //執行的方法
     @Override
    public void onBackStackChanged
() { if (BaseFragment.this.isVisible()) { Log.i(this.getClass().getName(), "isVisible"); //當前fragment可見時(如果是介面的跟新在onstart或onresume就好了,isVisible()方法更大的用處是在於可以做些操作避免當前Fragment不被重複加入返回棧)..... } else { Log.i(this.getClass().getName(), "unisVisible"); //當前fragment不可見時候....... } Log.i(this.getClass().getName(), "change"); }

2 監聽返回按鈕事件KeyEvent.KEYCODE_BACK(有bug)

//FragmentTransaction tx = fragmentManager.beginTransation();
//tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" //).commit();
//If you require more detailed control (i.e. when some Fragments are visible, you want to suppress the back key) you can set an OnKeyListener on the parent view of your fragment:

//You need to add the following line for this solution to work; thanks skayred
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().requestFocus();
fragment.getView().setOnKeyListener( new OnKeyListener()
{
    @Override
    public boolean onKey( View v, int keyCode, KeyEvent event )
    {
        if( keyCode == KeyEvent.KEYCODE_BACK )
        {
            return true;
        }
        return false;
    }
} );

這個方法有一個很大的需求就是需要Fragment的rootview獲取焦點:
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().requestFocus();
當fragment裡editText等搶走焦點的話監聽就沒有作用了,還要想辦法再讓他獲得焦點(好像並不容易)

3介面

方法的主要思想是寫一個公共介面然後在activity裡的onBackPressed執行這個接口裡的方法在Fragment裡實現這個介面,這裡提供一個減少Fragment和activity之間耦合度的思想
要點:
1 介面例項化的物件寫在application裡
2 讓介面單利
3 onBackPressed使用完記得吧介面物件制空(記得要有判空操作).
這樣就可以避免把介面的例項化方法(set方法)寫在activity裡使得Fragment必須要呼叫activity裡的方法

此外網上還有很多其他方法,歡迎交流,共同進步

4,Activity定義一個靜態變數,在每個Fragment的適當的生命週期方法改變這個變數的值

public class MainActivity extends BaseActivity {
.......
   public static String tag="";
    @Override
    public void onBackPressed() {
       /* if (tag.equals("BaseFragment")) {
            doYourThingsWhenEquals();
           return;
        }*/
        if (!tag.equals(""))
        doYourThingsWhenEquals();
        super.onBackPressed();
    }


    private void doYourThingsWhenEquals() {
     FragmentManager fm = getSupportFragmentManager();
        fm.popBackStack(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        return;//直接結束,不走系統預設的onBackPressed
        tag="";//這裡重新置空,很重要


}

public class BaseFragment extends Fragment {
 @Override
    public void onstart(){
    MainActivity.tag=fragment.getClass().getName();
    }
}

補充 保持返回棧元素惟一

由於返回棧是一個list 所有返回棧可以保留多個相同Fragment的不同例項化的物件
例如:當你由於一些原因連續執行兩次方法1裡的addFragment(new MyFragment ) 那麼當你再點選返回按鈕會是當前Fragment的上一個例項化的物件:

這裡寫了3種錯誤的嘗試和一種正確的方法,歡迎大家跳過錯誤的嘗試繼續研究交流正確的方法.

首先是寫一個返回棧的方法(為了演示錯誤的嘗試):

 protected void addFragmentWithoutBackStack(BaseFragment fragment) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();
        transaction.replace(R.id.main_frame, fragment, fragment.getClass().getName());
        // transaction.addToBackStack(null);
        transaction.commit();
    }

然後修改最上述標題1裡的方法

protected void addFragment(BaseFragment fragment) {
        FragmentManager fm = getSupportFragmentManager();
        fm.popBackStack(fragment.getClass().getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);//這是當前fragment在開啟fragment相當於先按了返回鍵
      //這是目前我找到惟一保證Fragment二次開啟後返回棧元素惟一而又能重新整理的 無bug的方法

        FragmentTransaction transaction = fm.beginTransaction();
        transaction.replace(R.id.main_frame, fragment, fragment.getClass().getName());
        transaction.isAddToBackStackAllowed();
        transaction.addToBackStack(fragment.getClass().getName());
        transaction.commit();
    } 
//下面是3種**失敗**的嘗試作為參考
requestsFollowUpFragment 是RequestsFollowUpFragment的一個全域性變數
        //嘗試1 當Fragment是第二次開啟時的處理(requestsFollowUpFragment!=null,我們知道ondestory以後Fragment的例項化也並不會隨之被回收):不加入返回棧  有bug
        if (requestsFollowUpFragment == null) {
         requestsFollowUpFragment = new RequestsFollowUpFragment();
         addFragment(requestsFollowUpFragment);}
         else{
         addFragmentWithoutBackStack(requestsFollowUpFragment);
         }
        //嘗試2 當Fragment是第二次開啟時(requestsFollowUpFragment!=null)的處理:將當前fragmen變數重新賦值一個新的物件(new 一個新的),有bug ,因為單純的new 並不走生命週期方法
        if (requestsFollowUpFragment == null) {
         requestsFollowUpFragment = new RequestsFollowUpFragment();
         addFragment(requestsFollowUpFragment);}
         else{
         requestsFollowUpFragment = new RequestsFollowUpFragment();
         }
        //嘗試3 只當fragment不可見的時候執行此方法,否則什麼都不做,有bug ,因為這樣再第二次開啟的時候也不走生命週期方法,達不到更新的目的
        if(requestsFollowUpFragment==null){
         Log.i("+++++", "null");
         requestsFollowUpFragment = new RequestsFollowUpFragment();}
         if (requestsFollowUpFragment.isVisible()) {
         Log.i("+++++", "isVisible");
         } else {
         Log.i("++++", "unisVisible");
         //  addFragment(requestsFollowUpFragment);
         }

寫了一大堆其實只是在標題1 的方法基礎上添加了一句話

fm.popBackStack(fragment.getClass().getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);

它的作用先把標記為fragment.getClass().getName()及他之上的所有內容出棧並顯示上一個Fragment (這句話之後立即執行了replace,所有顯示基本看不到,但上一個頁面的確走了整個生命週期方法) ,但是讓前一個Fragment重新走了整個生命週期方法對我們來說浪費資源而且沒有什麼實質性的作用,所以,你懂得……..