Android基礎—Fragment

Chaos_WX發表於2019-03-01

設計理念

Android3.0(API11)開始引入Fragment,主要是為了在大螢幕上更好的支援動態和靈活的UI設計。以平板為例,有足夠大的空間容納更多的元件,管理起來無疑過於麻煩。Fragment可以分割Activity的layout,並且具有自己的生命週期,從而使得元件獨立管理成為可能。正因為fragment具有獨立的生命週期,所以可以定義自己的layout和行為,從而一個fragment可以在多個activity中重複呼叫。所以我們在設計fragment的時候,要儘量追求模組化和可重用性。一般在平板和TV上普遍使用。

舉例如下:在平板上,activity使用一個fragment展示文章列表,使用另一個fragment展示文章內容;在手機上,activity使用一個fragment展示文章列表,點選跳轉到另一個activity展示。

Fragment模組化和複用性
Fragment模組化和複用性

Fragment建立

fragment的生命週期和activity非常類似,都有onCreate()、onStart()、onStop()、onPause()回撥方法。fragment一般至少要實現三個回撥方法:

  1. onCreate():fragment建立時呼叫,主要初始化一些重要的元件
  2. onCreateView():fragment繪製介面時呼叫(setContentView(id)),如果fragment有介面的話需要返回一個view;如果fragment沒有UI,直接返回空即可
  3. onPause():使用者離開fragment,一般要儲存資料(使用者可能不會再回來)

fragment型別

常見的有三種fragment,分別是DialogFragment、ListFragment、PreferenceFragment

  1. DialogFragment:展示浮動對話方塊
  2. ListFragment:展示一列資料
  3. PreferenceFragment:以列表形式展示首選項物件的層級關係

建立fragment

fragment一般具有自己的UI,併成為activity UI的一部分。為了給fragment建立UI,必須實現onCreateView()回撥方法,並返回一個view(fragment的根layout)。

如果fragment是ListFragment的子類,onCreateView()預設返回ListView,不需要重寫。

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}複製程式碼

container是activity的layout,fragment的layout將會被插入其中;savedInstanceState即儲存的fragment被異常銷燬前的狀態;inflate()有三個引數:

  1. id:flagment的view的資源id
  2. container:activity的layout,fragment的layout將插入其中
  3. false:false表示fragment僅為container提供layout 引數(動態新增、刪除、替代fragment時使用);true表示container成為fragment layout的父view(在activity佈局檔案中定義fragment使用)

把fragment新增到activity中

一般來說,fragment的layout會被嵌入成為activity view層級的一部分。有兩種方式可以把fragment新增到activity的layout中:1.靜態載入;2.動態載入。

  1. 靜態載入:在activity layout中宣告fragment,程式碼如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
     app:layout_behavior="@string/appbar_scrolling_view_behavior"
     tools:context="com.example.chaos.myapplication.MainActivity"
     tools:showIn="@layout/activity_main">
    
     <fragment
         android:id="@+id/fragment_person_list"
         android:name="com.example.chaos.myapplication.PersonListFragment"
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_weight="1" />
    
     <fragment
         android:id="@+id/fragment_person_detail"
         android:name="com.example.chaos.myapplication.DetailFragment"
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_weight="1" />
    </LinearLayout>複製程式碼

    系統建立該activity的layout時,會將指定的fragment全部例項化並分別呼叫他們的onCreateView()回撥。然後檢索fragment的layout,並將onCreateView()返回的view取代<fragment>元素直接插入activity的layout中。

activity重啟時,需要恢復fragment的資料,這就要求fragment要有個唯一的識別符號(否則無法管理fragment)。有三種方式可以為fragment賦予唯一的id

  1. 使用android:id屬性賦予id
  2. 使用android:tag屬性賦予唯一標籤
  3. 如果id和tag都沒提供的話,系統模式使用container view的id

2.. 動態載入:動態新增fragment到ViewGroup中
只要在activity在執行,都可以新增fragment到activity的layout中,只需要指定ViewGroup存放fragment就行。
為了在activity中操作fragment的事務(add、remove、replace),必須使用FragmentTransaction()。程式碼如下:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();複製程式碼

新增fragment使用add()方法,指定ViewGroup和要新增的fragment,對fragmentTransaction做的任何改動都要執行commit()方法才能生效。

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();複製程式碼

新增一個沒有UI的fragment

並非所有fragment都必須有UI,fragment也可以用來執行後臺操作,此時,無需UI也可以。該如何新增一個沒有UI的fragment呢?

首先,需要先通過findFragmentByTag()找到,因為tag是唯一能標識它的(只能通過add(fragment,string)主動設定tag)。

如果findFragmentByTag()返回null的話,需要通過add(fragment,string)為fragment主動設定tag並新增。

Fragment管理

fragment的管理是由FragmentManager實現的,可以在activity中通過getFragmentManager()獲取FragmentManager。FragmentManager的功能如下:

  1. 使用findFragmentById()(針對在activity layout提供一個UI的fragment)和findFragmentByTag()(針對沒有UI的fragment)獲取activity中已存在的fragment
  2. 呼叫popBackStack()將fragment從返回棧彈出(類似於使用者點選back)
  3. 呼叫addOnBackStackChangedListener()註冊監聽器監聽返回棧的變化

靜態載入的fragment一定具有id或tag;動態載入的id一定具有container view的id或tag(fragment如果沒有唯一識別符號,則無法進行有效管理),動態載入一共有三個方法:

  1. add(Fragment fragment, String tag):適用於有UI和無UI的fragment
  2. add(@IdRes int containerViewId, Fragment fragment):適用於有UI的fragment
  3. add(@IdRes int containerViewId, Fragment fragment,String tag);:適用於有UI的fragment

所以沒有UI的fragment,只能通過findFragmentByTag()獲取;有UI的fragment可以通過id或tag獲取。

也可以使用FragmentManager開啟FragmentTransaction,從而操作fragment的事務,如新增或移除。

Fragment事務處理

一個事務就是一系列變化,事務具有原子性、一致性、隔離性、永續性。可以使用FragmentTransaction實現事務,也可以把事務放到由activity管理的返回棧中,便於使用者返回之前的fragment的狀態.

可以從FragmentManager獲取FragmentTransaction例項

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();複製程式碼

FragmentTransactioni可以藉由add()、remove()、replace()實現對事務的操作,而且每次操作必須實現commit()才能生效。

如果想把fragment加入到返回棧,以便使用者退回到fragment原先的狀態的話,需要在commit()之前呼叫addBackStack()方法:

 DetailFragment detailFragment = new DetailFragment();
 fragmentTransaction.add(detailFragment,"");
 fragmentTransaction.addToBackStack(null);
 fragmentTransaction.commit();複製程式碼

為了檢索返回棧中的fragment,必須監聽onBackPressed()事件,否則的話如果棧中存在其他activity的話,就會直接返回到其他activity中;如果沒有activity的話,就會直接退出應用。

@Override
    public void onBackPressed() {
        if (getFragmentManager().getBackStackEntryCount() > 0){
            getFragmentManager().popBackStack();
        }else {
            super.onBackPressed();
        }
    }複製程式碼

新增改變到Transaction中的順序無關緊要,除非:

  1. 最後呼叫了commit()方法
  2. 新增了多個改變到同一個container中,新增的順序決定了container中view層級的順序

如果沒有呼叫addBackStack(),當remove一個fragment時,commit之後會立刻銷燬,使用者無法返回;如果新增到返回棧中,remove一個fragment時,commit之後會處於stop狀態,使用者可以返回到之前的狀態(resume)

commit()這個操作執行在主執行緒中,也就意味著呼叫commit()之後要排序執行,並不會立刻執行事務。如果想立刻執行的話,可以在commit()之前呼叫excutePendingTransactions(),不過一般並無此必要。

commit()必須在onSaveInstanceState()之前呼叫,因為在此之後,activity不會儲存資料,導致commit()呼叫會丟擲異常;如果允許狀態資料丟失的話,可以呼叫 commitAllowingStateLoss()。

與Activity的通訊

雖然fragment作為物件的實現和activity存在很大不同,但是作為activity的一部分,fragment又和activity緊密聯絡。

  1. 從fragment中呼叫activity
    View listView = getActivity().findViewById(R.id.list);
  2. 從activity中呼叫fragment
    DetailFragment detailFragment1 = (DetailFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_person_detail);複製程式碼

    為activity建立介面

    常見的模型:activity包含兩個fragment,左邊fragment顯示標題列表,右邊fragment顯示內容。點選左側的fragment的標題,更新右側的fragment內容。實現方式就是在左邊fragment建立介面,並在介面中實現傳值方法,activity實現該介面,監聽方法,然後將接受到的值傳給右邊的fragment。

fragment建立介面和方法:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}複製程式碼

把activity轉換成介面,從而實現activity監聽

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}複製程式碼

在fragment的onListItemClick事件中呼叫傳值方法給activity

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}複製程式碼

Fragment生命週期

和activity一樣,fragment也有三種基本狀態:

  1. 執行態:fragment在正在執行的activity中處於可見狀態
  2. 暫停態:另一個activity位於前臺並獲取焦點,fragment所在的activity仍然可見(前臺activity半透明或沒有全部覆蓋fragment所在activity)
  3. 停止態:fragment所在activity處於stop狀態,或者fragment被remove掉,但是新增到返回棧中了。處於暫停態的fragment仍然存活(系統仍然保留狀態資訊和成員變數),但是已不再可見,且activity被銷燬時,fragment也會被銷燬。

fragment和activity生命週期的一個最大差別在於二者在返回棧中的儲存。activity的返回棧是由系統管理的;fragment的返回棧是由activity管理的(當fragment被remove時,呼叫addBackStack())。

fragment如果需要context物件的話,可以呼叫getActivity(),但是這個方法要在onAttach()之後、onDetach()之前呼叫才有效,否則將會返回null。

fragment和activity生命週期比較
fragment和activity生命週期比較

相關文章