Fragment 相關知識點都在這裡了

jeanboy發表於2019-03-25

簡介

Fragment (簡稱碎片)是 Android 3.0(API 11)提出的。為了相容低版本 support-v4 庫中也開發了一套Fragment API 最低相容到 Android 1.6 的版本。

過去 support-v4 庫是一個 jar 包,從 24.2.0 版本開始,將 support-v4 庫模組化為多個 jar 包。包含 support-fragment、 support-ui、support-media-compat 等。這麼做是為了減少 APK 包大小,專案中需要用哪個模組就引入哪個模組。

// 引入整個 support-v4 庫
compile 'com.android.support:support-v4:24.2.1'
//只引入 support-fragment 庫
compile 'com.android.support:support-fragment:24.2.1'
複製程式碼

因為 support 庫是不斷更新的,因此推薦使用 support 庫中的 android.support.v4.app.Fragment,而不要用系統自帶的 android.app.Fragment。如果使用 support 庫的 Fragment,Activity 就必須要繼承 FragmentActivity(AppCompatActivity 是 FragmentActivity 的子類)。

Fragment 的特點

  • Fragment 是依賴於 Activity 的,不能獨立存在的。
  • 一個 Activity 裡可以有多個 Fragment。
  • 一個 Fragment 可以被多個 Activity 重用。
  • Fragment 有自己的生命週期,並能接收輸入事件。
  • 可以在 Activity 執行時動態地新增或刪除 Fragment。

Fragment 的優勢

  • 模組化(Modularity):我們不必把所有程式碼全部寫在 Activity 中,可以把程式碼寫在各自的 Fragment 中。
  • 可重用(Reusability):多個 Activity 可以重用一個 Fragment。
  • 可適配(Adaptability):根據硬體的螢幕尺寸、螢幕方向,能夠方便地實現不同的佈局,這樣使用者體驗更好。

生命週期

Fragment 與 Activity 生命週期很相似,與 Activity 一樣,Fragment 也有三種狀態:

  • Resumed:Fragment 在執行中的 Activity 中可見。
  • Paused:另一個 Activity 處於最頂層,但是 Fragment 所在的 Activity 並沒有被完全覆蓋(頂層的 Activity 是半透明的或不佔據整個螢幕)。
  • Stoped:Fragment 不可見,可能是它所在的 Activity 處於 stoped 狀態或是 Fragment 被刪除並新增到後退棧中了,此狀態的 Fragment 仍然存在於記憶體中。

Fragment 生命週期

Activity 直接影響它所包含的 Fragment 的生命週期,所以對 Activity 的某個生命週期方法的呼叫也會產生對Fragment 相同方法的呼叫。例如:當 Activity 的 onPause() 方法被呼叫時,它所包含的所有的 Fragment 的onPause() 方法都會被呼叫。

Fragment 比 Activity 還要多出幾個生命週期回撥方法,這些額外的方法是為了與 Activity 的互動,如下:

  • onAttach()

當 Fragment 被加入到 Activity 時呼叫(在這個方法中可以獲得所在的 Activity)。

  • onCreateView()

當 Activity 要得到 Fragment 的 layout 時,呼叫此方法,Fragment 在其中建立自己的 layout (介面)。

  • onActivityCreated()

當 Activity 的 onCreated() 方法返回後呼叫此方法。

  • onDestroyView()

當 Fragment 的 layout 被銷燬時被呼叫。

  • onDetach()

當 Fragment 被從 Activity 中刪掉時被呼叫。

一旦 Activity 進入 resumed 狀態(也就是 running 狀態),你就可以自由地新增和刪除 Fragment 了。因此,只有當 Activity 在 resumed 狀態時,Fragment 的生命週期才能獨立的運轉,其它時候是依賴於 Activity 的生命週期變化的。

使用方式

這裡給出 Fragment 最基本的使用方式。首先,建立繼承 Fragment 的類,名為 BlankFragment:

public class BlankFragment extends Fragment {
    private static final String ARG_PARAM = "param_key";
    private String mParam;

    public BlankFragment() { }

    public static BlankFragment newInstance(String param) {
        BlankFragment fragment = new BlankFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM, param);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam = getArguments().getString(ARG_PARAM);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_blank, container, false);
        // View 初始化,findViewById() 等操作
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // 初始化資料,載入資料等...
    }
}
複製程式碼

靜態新增

通過 xml 的方式新增,缺點是一旦新增就不能在執行時刪除。

<fragment
    android:id="@+id/fg_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.jeanboy.text.ui.fragment.BlankFragment" />
複製程式碼

動態新增

執行時新增,這種方式比較靈活,因此建議使用這種方式。

這裡只給出動態新增的方式。首先 Activity 需要有一個容器存放 Fragment,一般是 FrameLayout,因此在 Activity 的佈局檔案中加入 FrameLayout:

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
複製程式碼

然後在 onCreate() 中,通過以下程式碼將 Fragment 新增進Activity中。

getSupportFragmentManager().beginTransaction()
        .add(R.id.container, BlankFragment.newInstance("hello world"), "f1")
        .commit();
複製程式碼

這裡需要注意幾點:

  • 因為我們使用了support庫的Fragment,因此需要使用 getSupportFragmentManager() 獲取 FragmentManager。

  • add() 是對 Fragment 眾多操作中的一種,還有 remove()replace() 等。

    第一個引數是根容器的 id(FrameLayout 的 id,即 @id/container),第二個引數是 Fragment 物件,第三個引數是 Fragment 的 tag 名,指定 tag 的好處是後續我們可以通過:

    Fragment1 frag = getSupportFragmentManager().findFragmentByTag("f1");
    複製程式碼

    從 FragmentManager 中查詢 Fragment 物件。

  • 在一次事務中,可以做多個操作,比如同時做 add().remove().replace()

  • commit() 操作是非同步的,內部通過 mManager.enqueueAction() 加入處理佇列。

    對應的同步方法為 commitNow()commit() 內部會有 checkStateLoss() 操作,如果開發人員使用不當(比如 commit() 操作在 onSaveInstanceState() 之後),可能會丟擲異常。而 commitAllowingStateLoss() 方法則是不會丟擲異常版本的 commit() 方法,但是儘量使用 commit(),而不要使用 commitAllowingStateLoss()

  • addToBackStack("fname") 是可選的。

    FragmentManager 擁有回退棧(BackStack),類似於 Activity 的任務棧,如果新增了該語句,就把該事務加入回退棧,當使用者點選返回按鈕,會回退該事務(回退指的是如果事務是 add(frag1),那麼回退操作就是 remove(frag1) );如果沒新增該語句,使用者點選返回按鈕會直接銷燬 Activity。

Fragment 通訊

Fragment 向 Activity 傳遞資料

首先,在 Fragment中 定義介面,並讓 Activity 實現該介面。

public interface OnFragmentCallback {
    void onCallback(String value);
}
複製程式碼

在 Fragment 的 onAttach() 中,將引數 Context 強轉為 OnFragmentCallback 物件:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentCallback) {
        callback = (OnFragmentCallback) context;
    } else {
        throw new RuntimeException(context.toString()
                                   + " must implement OnFragmentCallback");
    }
}
複製程式碼

Activity 向 Fragment 傳遞資料

Activity 向 Fragment 傳遞資料比較簡單,獲取 Fragment 物件,並呼叫 Fragment 的方法即可。比如要將一個字串傳遞給 Fragment,則在 Fragment 中定義方法:

public void setString(String data) { 
    this.data = data;
}
複製程式碼

並在 Activity 中呼叫 fragment.setString("hello") 即可。

Fragment 之間通訊

由於 Fragment 之間是沒有任何依賴關係的,因此如果要進行 Fragment 之間的通訊,建議通過 Activity 作為中介,不要 Fragment 之間直接通訊。

DialogFragment

DialogFragment 是 Android 3.0 提出的,代替了 Dialog,用於實現對話方塊。它的優點是:即使旋轉螢幕,也能保留對話方塊狀態。

如果要自定義對話方塊樣式,只需要繼承 DialogFragment,並重寫 onCreateView(),該方法返回對話方塊 UI。這裡我們舉個例子,實現進度條樣式的圓角對話方塊。

public class ProgressDialogFragment extends DialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        //消除Title區域
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        //將背景變為透明
        getDialog().getWindow()
            .setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        //點選外部不可取消
        setCancelable(false);
        View root = inflater.inflate(R.layout.fragment_progress_dialog, container);
        return root;
    }

    public static ProgressDialogFragment newInstance() {
        return new ProgressDialogFragment();
    }
}
複製程式碼

然後通過下面程式碼顯示對話方塊:

ProgressDialogFragment fragment = ProgressDialogFragment.newInstance();
fragment.show(getSupportFragmentManager(), "tag");//顯示對話方塊
fragment.dismiss();//關閉對話方塊
複製程式碼

我的公眾號

歡迎關注我的公眾號,分享各種技術乾貨,各種學習資料,職業發展和行業動態。

Android 波斯灣

如果你有什麼疑問或者問題,可以 點選這裡 提交 issue,也可以發郵件給我 jeanboy@foxmail.com

同時歡迎你

Android技術進階:386463747
來一起交流學習,群裡有很多大牛和學習資料,相信一定能幫助到你!

相關文章