Fragment數據傳遞及回退棧

上一篇文章分析了使用replace 和 使用hide/show 兩種方式實現Fragment的切換,及對應的生命週期,這一篇文章在介紹Fragment回退棧之前先介紹一下FragmentManager和FragmentTransaction。

一、動態創建Fragment與FragmentManager

上一章最後,我們已經實現了一個通過FragmentManager、FragmentTransaction動態創建Fragment的例子,關鍵代碼如下:

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();

首先創建了一個Fragment的實例,接着調用getSupportFragmentManager()(如果使用app包下的Fragment使用getFragmentManager())獲取到FragmentManager對象,該對象負責維護Fragment列表及回退棧,然後通過FragmentManager的beginTransaction()方法獲取FragmentTransaction對象,FragmentTransaction對象是對單個Fragment進行添加、隱藏、移除、替換以及將Fragment添加進回退棧等操作。

FragmentTransaction完成對Fragment的操作後需要通過commit()方法提交才能生效

並且一個FragmentTransaction對象無法執行兩次commit(),每次需要commit()都要調用FragmentManager的beginTransaction()方法獲取FragmentTransaction對象。

二、Fragment與Activity的交互

大家都知道Activity之間一般時通過Intent攜帶Bundle實現數據的交互,但Activity向Fragment發送數據無法使用Intent,並且不建議使用帶參數的構造方法,因爲Activity重啓後會重新調用Fragment無參數的構造方法,導致我們傳遞的數據丟失,這裏我們就需要使用Android SDK爲我們提供的setArguments(Bundle args)方法來解決這個問題。該方法原型如下:

    public void setArguments(Bundle args) {
        if (mIndex >= 0 && isStateSaved()) {
            throw new IllegalStateException("Fragment already active and state has been saved");
        }
        mArguments = args;
    }

如果在Activity中調用該方法,就可以將Activity的數據通過Bundle發送給Fragment了,但是有一點需要注意,該方法的註釋上有一句This method cannot be called if the fragment is added to a FragmentManager,就是說這個方法要在FragmentTransaction.commit()之前調用。這與方法回調時機有關,fragment的創建涉及子線程,FragmentTransaction.commit()之前調用該方法能保證在Fragment的生命週期回調中能獲取到傳遞的參數。

實例化Fragment常用的一種寫法:

  public static MyFragment getInstance(Bundle bundle){
        MyFragment fragment = new MyFragment();
        fragment.setArguments(bundle);
        return fragment;
    }

fragment.setArguments(bundle);這個方法中將bundle設爲Fragment的成員變量,並且對其數據進行了保存,在Activity重建後,也能獲取到該數據,避免了數據丟失的情況。

三、Fragment回退棧

Fragment回退棧由FragmentManager負責出棧及監聽,FragmentTransaction調用addToBackStack方法負責Fragment入棧。

具體來說就是每次調用FragmentTransaction的commit之前,調用一下FragmentTransaction的addToBackStack(String)這個方法,參數String指定了回退棧記錄的名稱,該參數能傳null。

這樣調用commit時就將該commit操作加入了回退棧,使用FragmentManager的popBackStack()方法能對該操作進行出棧,從而實現Fragment的切換。值得注意的是,一個Fragment對象可以多次入棧,直到回退棧中不再存在該Fragment對象,纔會執行該Fragment對象的最終銷燬方法(onDestroy,onDetach),否則該F ragment對象的生命週期將一直在onCreateView和onDestroyView之間徘徊。

寫段代碼驗證一下,主體還是基於上一篇文章

public class FragmentShowActivity extends AppCompatActivity {
    RelativeLayout mRoot;
    ShowFragment show1;
    ShowFragment show2;
    Button jump;
    Button pop;
    FragmentManager fm;
    boolean is1 = true;

    private static final String TAG = "FragmentShowActivity";

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

        mRoot = findViewById(R.id.rl);
        jump = findViewById(R.id.jump);
        pop = findViewById(R.id.pop);

        show1 = ShowFragment.getInstance("show1");
        show2 = ShowFragment.getInstance("show2");

        jump.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fm = getSupportFragmentManager();
                FragmentTransaction ft = fm.beginTransaction();
                if (!is1) {
                    ft.replace(R.id.fragment,show2);
                    ft.addToBackStack(null);
                    ft.commit();
                } else {
                    ft.replace(R.id.fragment,show1);
                    ft.addToBackStack(null);
                    ft.commit();
                }
                is1 = !is1;
            }
        });

        pop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("出棧","poppoppoppop");
                fm = getSupportFragmentManager();
                fm.popBackStack();
            }
        });
    }
}

activity佈局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/rl"
    tools:context=".FragmentShowActivity">
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fragment"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="jump"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="80dp"
        android:layout_marginBottom="50dp"
        android:id="@+id/jump"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="pop"
        android:layout_marginRight="80dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="50dp"
        android:id="@+id/pop"/>
</RelativeLayout>

 Fragment部分的代碼與上一篇文章一致,主要就是打印了各個生命週期方法。

啓動app,連續執行兩次入棧操作,依次將Fragment show1、show2 入棧。生命週期打印如下

可以看出,Fragment show1並沒有被完全銷燬,生命週期終止在了onDestroyView。

再執行一次出棧操作,打印如下:

 由於棧中不再有Fragment show2,所以show2被完全銷燬,而show1仍然存在,所以show1生命週期是從onCreateView開始執行。

我們可以推測,再次執行出棧操作,show1將被完全銷燬,生命週期執行順序爲onPause,onStop,onDestroyView,onDestroy,onDetach,看看是不是這樣。

點擊出棧,查看日誌

與預測結果一致,到此,算是摸到了Fragment的皮毛,生活還在繼續,大家繼續努力。

衝鴨~!

 

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