Fragment生命週期及基本使用

原文出處:http://www.ccbu.cc/index.php/android/fragment-lifecycle.html

什麼是Fragment

Fragment實在是Android 3.0(API 11)中引入的,譯作“碎片”。Fragment作爲應用用戶接口或行爲的一部分而放置在Activity中。Fragment不能獨立存在,只能依賴與Activity存在。Fragment擁有自己的生命週期,其生命週期受宿主Activity的控制,狀態會隨着Activity的狀態的改變而發生改變。

因Android各個版本的Fragment有所差異,同時爲了兼容低版本,support-v4庫中提供了一套兼容Fragment API,最低兼容Android 1.6。

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

Fragment的特點

Fragment是爲了解決碎片態的用戶界面而產生的,在處理UI方面有者自己獨特的優勢。Fragment擁有自己的生命週期,可以處理用戶輸入事件。一個Activity中可以擁有多個Fragment,一個Fragment可以被多個Activity重用。在Activity中可以動態的添加,刪除,和管理Fragment。

基於上面所述的優勢,可以看出,Fragment擁有以下以下特點:

  • 獨立性 Fragment擁有獨立的生命週期,可以獨立處理所有的用戶交互事件
  • 模塊化 將某一功能封裝到一個Fragment中供Activity使用
  • 複用性 一個Fragment可以被多個Activity複用
  • 靈活性 Fragment不但可以按照特定功能進行單獨封裝,還可以在Activity中進行動態的添加,刪除和管理。

Fragment生命週期

上面多次提到Fragment擁有自己的生命週期,那他的生命週期是怎麼樣的呢,先看一下經典的Fragment生命週期圖。

其中,各個生命週期函數的說明如下:

  • onAttach():Fragment和Activity相關聯時調用。可以通過該方法獲取 Activity引用,還可以通過getArguments()獲取參數。
  • onCreate():系統在Fragment被創建時調用。
  • onCreateView():創建Fragment的佈局,如果片段未提供 UI,您可以返回 null。
  • onActivityCreated():當Activity完成onCreate()時調用。
  • onStart():當Fragment可見時調用。
  • onResume():當Fragment可見且可交互時調用。
  • onPause():當Fragment不可交互但可見時調用。
  • onStop():當Fragment不可見時調用。
  • onDestroyView():當Fragment的UI從視圖結構中移除時調用。
  • onDestroy():銷燬Fragment時調用。
  • onDetach():當Fragment和Activity解除關聯時調用。

Fragment的是依於Activity而存在的,從他的生命週期圖可以看出,Fragment的生命週期與Activity的生命週期是十分相似的。下圖展示了fragment的生命週期與Activity生命週期函數的對應關係。

Fragment的使用

下面通過Fragment的使用實例瞭解在實際應用中如何來使用Fragment構建UI,同時通過這些使用實例,進一步的理解其生命週期。

1. 創建一個新的Fragment

創建一個自己的Fragment,只需要繼承系統的Fragment或support-v4庫中的Fragment類,或者是繼承Fragment的子類,並實現需要的方法即可。其中一個默認的不帶參數的構造函數,onCreateView()函數是必須的。onCreate,onResume,onPause()等方法也是十分常用的。

public class BlankFragment extends Fragment {
    
    public BlankFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }
}

Fragment作爲Activity用戶界面的一部分,支持View操作是其最基本的功能;其中onCreateView用來加載Fragment的佈局,返回值即爲加載進來的View。一般onCreateView通過加載XML佈局文件來加載自己的佈局,onCreateView提供了加載佈局的LayoutInflater,專門供使用者來加載佈局。

Fragment還有幾個常用的子類,可以幫助我們來更方便的實現想要的特定功能的Fragment。包括以下幾個。

  • DialogFragment 顯示浮動對話框。使用此類創建對話框可有效地替代使用 Activity 類中的對話框幫助程序方法,因爲您可以將片段對話框納入由 Activity 管理的片段返回棧,從而使用戶能夠返回清除的片段。
  • ListFragment 顯示由適配器(如 SimpleCursorAdapter)管理的一系列項目,類似於 ListActivity。它提供了幾種管理列表視圖的方法,如用於處理點擊事件的 onListItemClick() 回調。
  • PreferenceFragment 以列表形式顯示 Preference 對象的層次結構,類似於 PreferenceActivity。這在爲您的應用創建“設置” Activity 時很有用處。

2.靜態加載Fragment

靜態加載Fragment即是將Fragment像其他普通View一樣,直接寫在layout的xml文件中。View加載時會自動進行加載。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <fragment
        android:name="cc.ccbu.canvassimple.BlankFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

在佈局文件中使用fragment標籤進行Fragment的添加,並指明android:name屬性制定對應的Fragment類。當系統創建此 Activity 佈局時,會實例化在佈局中指定的每個片段,併爲每個片段調用 onCreateView() 方法,以檢索每個片段的佈局。系統會直接插入片段返回的 View 來替代 <fragment> 元素。

每個片段都需要一個唯一的標識符,重啓 Activity 時,系統可以使用該標識符來恢復片段(您也可以使用該標識符來捕獲片段以執行某些事務,如將其移除)。 可以通過三種方式爲片段提供 ID:
1.爲 android:id 屬性提供唯一 ID。
2.爲 android:tag 屬性提供唯一字符串。
3.如果您未給以上兩個屬性提供值,系統會使用容器視圖的 ID。

3.動態加載fragment

靜態方式加載Fragment的方式還是顯得不夠靈活,所以在 Activity 中我們還可以可以根據需要來執行Fragment的添加、移除、替換以及其他操作。 提交給 Activity 的每組更改都稱爲事務,我們可以通過使用 FragmentTransaction 的 API 來執行一項事務。每次可以將每個事務保存到由 Activity 管理的返回棧內(addToBackStack),從事使能夠回退片到之前保持的狀態。

Fragment newFragment = new BlankFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

transaction.commit();

上例中,newFragment 會替換在 R.id.fragment_container ID 所標識的佈局容器中的內容;通過調用 addToBackStack() 可將事務保存到返回棧,當用戶按返回鍵時,會執行撤銷事務,如果返回棧有之前保存的事務項,則以出棧的形式回退到上一個事務項狀態。

如果向FragmentTransaction添加了多個更改(如add()remove()),並且調用了 addToBackStack(),則在調用commit() 前應用的所有更改都將作爲單一事務添加到返回棧,並且返回按鈕會將它們一併撤消。

對於每個片段事務,您都可以通過在提交前調用 setTransition() 來應用過渡動畫。

4.添加菜單項

Fragment可以通過實現onCreateOptionsMenu()項Activity的OptionsMenu中添加菜單項。不過需要注意的是,必須在 onCreate() 期間調用 setHasOptionsMenu(),Fragment的onCreateOptionsMenu方法纔會被調用。在Fragment中添加的所有菜單項都會被追加到現有菜單項之後。當菜單項被選中時,Fragment會收到對應的 onOptionsItemSelected() 回調。

儘管您Fragment會收到其添加的每個菜單項對應的菜單項回調,但當用戶選中菜單項時,Activity 會首先收到相應的回調。 如果 Activity 對菜單項回調的實現不會處理該菜單項,則系統會將事件傳遞到Fragment的回調。

Fragment通信

1.給Fragment設置參數

如果在創建Fragment時要傳入參數,必須要通過setArguments(Bundle bundle)方式添加,而不建議通過爲Fragment添加帶參數的構造函數,因爲通過setArguments()方式添加,在由於內存緊張導致Fragment被系統殺掉並恢復(re-instantiate)時能保留這些數據。使用setArguments,在創建Fragment的時候傳遞參數,然後在fragment的onCreate方法處獲取參數,但是需要注意的是setArguments()方法必須在fragment創建以後,add之前調用。

public static TestFragment newInstance(String str){
        Bundle bundle = new Bundle();
        bundle.putString("info", str);
        TestFragment fragment = new TestFragment();
        fragment.setArguments(bundle);
        return fragment;
    }

2.獲取實例

Fragment可以通過getActivity方法來獲取Activity的實例,從而執行Activity相關的UI操作。但使用getActivity前需要注意Fragment的生命週期與Activity生命週期的對應關係,在部分生命週期函數內調用getActivity是不會返回有效的實例,獲取到的是null值。

同樣的,Activity中也可以通過調用 findFragmentById() 或 findFragmentByTag()方法,從 FragmentManager 獲取Fragment 引用,進而來訪問Fragment相應的方法。

3.使用回調方法

有的時候,我們可以通過使用回調方法來實現Activity與Fragment的通信操作。

public class BlankFragment extends Fragment {
    ...
    private OnItemClickListener mItemClickListener = null;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
        mItemClickListener = (OnItemClickListener)activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnItemClickListener");
        }
    }
    
    public interface OnItemClickListener {
        void onItemClick(int itemId);
    }
}
public class MainActivity extends Activity implements BlankFragment.OnItemClickListener {
    ...
    @Override
    public void onItemClick(int itemId) {
        Log.d(TAG, "onItemClick : " + itemId);
    }
}

上面的例子中,BlankFragment定義了OnItemClickListener接口,宿主Activity必須要實現該接口,在BlankFragment的onAttach方法中,通過強制類型轉換,將Activity參數轉爲OnItemClickListener接口,如果宿主Activity沒有實現該接口,會拋出ClassCastException異常。

Fragment懶加載

懶加載主要用於ViewPager加載Fragment頁面的情況,且ViewPager的每個子頁面都是一個Fragment。默認情況下,ViewPager會執行預加載來提前加載一些頁面來使得UI左右滑動效果更加流暢。ViewPager可以通過setOffscreenPageLimit(int limit)設置預加載頁面數量,但有一個最小限制,保證知識加載兩個或三個頁面。在一些場景下,當ViewPager中的頁面不可見時,我們不希望他來加載數據時,那麼此時我們就需要通過懶加載方式來加載數據。懶加載的方式庫提供應用的初始化速度,同時也可以避免不必要的資源加載。

那Fragment的懶加載該如何實現呢,這裏首先來認識一個方法setUserVisibleHint(boolean isVisibleToUser)。該方法中的isVisibleToUser參數用來表示當前Fragment是否對用戶可見。當Fragment對用戶可見或不可見時,該方法都會被調用。所以我們可以依據該方法,在Fragment對用戶可見時在去加載數據。但需要注意的一點是,setUserVisibleHint(boolean isVisibleToUser)方法會多次回調,而且可能會在onCreateView()方法執行完畢之前回調。所以,在加載數據前,必須滿足兩個條件:

  1. setUserVisibleHint(boolean isVisibleToUser)參數爲true。
  2. onCreateView()方法已經執行
  3. 數據還未被加載

我們可以把這些操作封裝到一個基類裏。

public abstract class LazyLoadFragment extends Fragment {

    private boolean isUserVisible = false;
    private boolean isViewCreated = false;
    private boolean isDataLoaded = false;
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isUserVisible = isVisibleToUser;
        onLazyLoad();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isViewCreated = true;
        onLazyLoad();
    }

    public void onLazyLoad() {
        if (!isDataLoaded && isUserVisible && isViewCreated) {
            loadData();
        }
    }

    public abstract boolean loadData();
}

在子類中只用實現loadData()方法來加載數據即可。

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