天天看點

Fragment的詳細解析介紹與應用如何使用FragmentFragment的事務管理Fragment與Activity的通信Fragment的生命周期

介紹與應用

Fragment:可以了解為碎片,但也可以了解為片段。多個Fragment組合在一個 Activity 中來建構多窗格 UI,以及在多個 Activity 中重複使用某個Fragment。

Fragment有自己的生命周期,Fragment必須始終“嵌入”在 Activity 中使用,那麼,它的生命周期受Activity的限制。隻有在Activity處于活動狀态才可以操作,如添加和移除。受限制的同時,fragment的生命的周期也是Activity同步的,當activity的 onPause() 方法被回調時,所在activity中的fragment也都會回調到 onPause().方法。如果該Activity 已停止,它内部沒有碎片可以啟動; 當活動被銷毀,所有片段将被銷毀。

Fragment是在Android 在 Android 3.0(API 級别 11)中引入的,主要是為了給大螢幕(如平闆電腦、TV)上更加動态和靈活的 UI 設計提供支援。但目前來看,在開發過程中,其實也不隻是應用在大螢幕中,手機也會經常應用,比如QQ、微信、新聞端、幾乎多會用到,下面幾個Tab用來切換Fragment;指導界面或者廣告位的切換,也用到Fragment,已成為程式員必備技能。

關于導包:

Fragment是在3.0版本引入的,如果你使用的是3.0之前的系統,需要先導入android-support-v4的jar包才能使用Fragment功能:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
           

如果是在Android 3.0以上的版本使用,可以導入:

但是,目前來看幾乎很少使用那麼低版本的系統,是以導包看項目而定吧。如果不會在Android 3.0以下使用,建議導入後者,對于初學者,避免不必要的麻煩,而且屬性動畫也是3.0才引用的,如果需要使用屬性動畫,更需要導入app下的包,(以下的測試導入的是:import android.app.Fragment;)

如何使用Fragment

開始建立Fragment

Fragment的使用與Activity非常相似,Activity需要繼承的是Activity,而Fragment繼承的是Fragment,而且多需要實作類似的回調方法 ,如 onCreate()、onStart()、onPause() 。其中一個不同的地方是必須使用 onCreateView() 的回調來定義Layout。

代碼開始撸起來吧,如下:

public class FirstFragment extends Fragment
{
    @Override
    public View onCreateView(LayoutInflater inflater,  ViewGroup container,  Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.fragment_first, container, false);
    }
 }
           

onCreateView( ) : 系統會在Fragment首次繪制其使用者界面時調用此方法。 要Fragment繪制 UI,此方法中傳回的 View 必須是Fragment布局的根視圖。如果片段未提供 UI,可以傳回 null。

container :是Fragment布局将插入到的父 容器(來自 Activity 的布局);

savedInstanceState: 是在恢複Fragment時,提供上一Fragment執行個體相關資料的 Bundle;

其中,R.layout.fragment_first 是Fragment對應的布局檔案。

向Activity添加Fragment

Fragment作為 Activity 總體視圖層次結構的一部分嵌入到 Activity 中。可以通過兩種方式向 Activity 布局添加片段:

a. 使用XML添加fragment到activity(靜态添加)

<?xml version="1.0" encoding="utf-8"?>
<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="com.gotechcn.testfagment.MainActivity">
    <fragment
        android:id="@+id/first_fragment"
        android:name="com.gotechcn.testfagment.FirstFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </fragment>
</RelativeLayout>
           

其中fragment 标簽中的 android:name 屬性指定要在布局中執行個體化的 Fragment 類。

是的,在xml檔案中靜态加入,就是那麼的簡單。但是,雖然簡單,在運作的過程中可能也有一些坑:

1.出現 ClassCastException 異常

fragment可以重用,是子產品化的元件,每一個fragment的執行個體都與父類FragmentActivity有依賴,你可以通過定義每一個fragment的XML來實作這樣的聯系。 FragmentActivity 是一個在Support Library中的特殊的activity,使用它來向Level 11以下的系統進行相容。是以,你要是導入是v4的包,那麼你目前的FirstActivity必須繼承FragmentActivity;如果是在app下的包,則可以繼承通常的 Activity。同時,擷取FragmentManager對象時,在v4包下是通過getSupportFragmentManager擷取的。(這也是我上面說的,可以的話,建議不導入v4包)

2.出現IllegalArgumentException異常

每個fragment都需要一個唯一的辨別符,重新開機 Activity 時,系統通過使用該辨別符來恢複片段。是以,有可能在布局檔案裡面沒有指定id, 可以通過三種方式為片段提供 ID:

為 android:id 屬性提供唯一 ID。

為 android:tag 屬性提供唯一字元串。

如果您未給以上兩個屬性提供值,系統會使用容器視圖的 ID。

b. 運作時添加fragment到activity(動态添加)

在Activity運作時中執行片段事務(如添加、移除或替換片段),必須使用 FragmentManager 來建立一個FragmentTransaction,通過它來執行Fragment事務的操作,并且送出事務,MainActivity的代碼如下:

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

        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.layout, new FirstFragment());
        fragmentTransaction.commit();

    }
           

其中,add() 的第一個參數是 ViewGroup,即應該放置fragment的位置,由資源 ID 指定,第二個參數是要添加的fragment。

一旦您通過 FragmentTransaction 做出了更改,就必須調用 commit() 以使更改生效

對于實際的開發中,最為常用的是第二種方式。

Fragment的事務管理

前面也使用過,Fragment是通過FragmentManager來管理的,可以通過Activity 調用 getFragmentManager()擷取。

有了事務管理,可以使用 add()、remove() 和 replace() 等方法為給定事務設定你想要執行的所有更改。最後記得必須調用 commit(),事務才會有效執行。

當你執行fragment的置換或者移除等切換動作時請注意:因為使用者很可能想做後退與撤銷的動作,為了讓使用者能夠回退到之前的狀态,必須在你commit FragmentTransaction 之前執行 addToBackStack(),如:

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

其中,addToBackStack() 的方法會需要一個可選的string參數來指定這個特定的動作。除非你需要使用FragmentManager.BackStackEntry的API來做一些更加進階複雜的操作,一般是不需要傳遞的。

當你執行移除或者置換操作并且把這個動作添加到back stack的時候,被移除的fragment并沒有被銷毀而是stopped狀态。如果使用者執行回退的操作來恢複那個fragment,它會被restart。如果你沒有把那個動作添加到back stack,那麼fragment會被銷毀。

通過事務,還可以執行以下操作:

  • 對于每個片段事務,您都可以通過在送出前調用 setTransition() 來應用過渡動畫。
  • 通過findFragmentById()(對于在 Activity 布局中提供 UI 的片段)或findFragmentByTag()(對于提供或不提供 UI 的片段)擷取 Activity 中存在的fragment;
  • 通過 addOnBackStackChangedListener() 注冊一個偵聽傳回棧變化的偵聽器。
  • 通過 popBackStack()(模拟使用者發出的BACK指令)将fragment從傳回棧中彈出。

Fragment與Activity的通信

在使用的過程中,Fragment與Activity、Fragment與Fragment多需要互動通信,那麼他們是怎樣互動的呢?

兩個fragment之間沒有辦法直接互動,所有的Fragment-to-Fragment之間的互動都是基于activity進行操作的。

一般他們之間的通信可以使用這2種方式:

第一種:

當Fragment需要擷取它所在的Activity,可以通過 getActivity() 通路 Activity 執行個體,并輕松地執行在 Activity 布局中查找視圖等任務,如

當 Activity 擷取它包含的Fragment,調用Activity關聯的FragmentManager ,使用 findFragmentById() 或 findFragmentByTag(),擷取需要的Fragment之後,再執行其方法,例如:

FirstFragment fragment = (FirstFragment) getFragmentManager().findFragmentById(R.id.first_fragment);
fragment.cleanFoucs();
           

第二種:

執行該操作的一個好方法是,在Fragment内定義一個回調接口,并要求宿主 Activity 實作它。這樣Fragment可以調用改回調方法将資料傳給Activity, 當 Activity 通過該接口收到回調時,可以根據需要與布局中的其他片段共享這些資訊。以下是官方的一個例子:

public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // Activity必須實作該接口
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }
}
           

為確定宿主 Activity 實作此接口,Fragment 的 onAttach() 回調方法(系統在向 Activity 添加片段時調用的方法)會通過轉換傳遞到 onAttach() 中的 Activity 來執行個體化 OnHeadlineSelectedListener 的執行個體。

接口的方法會在使用者點選listitem的時候被call到。Fragment使用這個callback接口來傳遞事件給父activity。

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
    // 發送事件給宿主Activity
    mCallback.onArticleSelected(position);
}
           

這樣子,Fragment就完事了,Activity隻要實作該接口就OK了:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{

    public void onArticleSelected(int position) {
        // 做一些事...
    }
}
           

Fragment的生命周期

Fragment的生命周期與Activity很相似(不熟悉Activity生命周期的可以參考這裡),主要也是以這三種狀态存在:

  • Resumed:Fragment在運作中的 Activity 中可見;
  • Paused:另一個 Activity 位于前台并具有焦點,但此Fragment所在的 Activity 仍然可見(前台 Activity 部分透明,或未覆寫整個螢幕)。
  • Stopped: Fragment不可見。宿主 Activity 已停止,或Fragment已從Activity 中移除,但已添加到傳回棧。 停止Fragment仍然處于活動狀态(系統會保留所有狀态和成員資訊)。 不過,它對使用者不再可見,如果Activity 被終止,它也會被終止。

前面說到,Activity的生命周期會影響Fragment的生命周期,而且具體流程又是非常相似的。有點抽象,那我們用實踐來看看他們具體的生命流程。

實踐中,最好是和Activity一起比較,這樣能夠清晰看到的差別:

Fragment的代碼如下:

public class FirstFragment extends Fragment
{

    public static final String TAG = "Fragment Life";

    @Override
    public void onAttach(Context context)
    {
        Log.e(TAG,"onAttach()");
        super.onAttach(context);
    }


    @Override
    public void onCreate( Bundle savedInstanceState)
    {
        Log.e(TAG,"onCreate");
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,  ViewGroup container,  Bundle savedInstanceState)
    {
        Log.e(TAG,"onCreateView");
        return inflater.inflate(R.layout.fragment_first, container, false);
    }


    @Override
    public void onActivityCreated( Bundle savedInstanceState)
    {
        Log.e(TAG,"onActivityCreated");
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onStart()
    {
        Log.e(TAG,"onStart");
        super.onStart();
    }

    @Override
    public void onResume()
    {
        Log.e(TAG,"onResume");

        super.onResume();
    }

    @Override
    public void onPause()
    {
        Log.e(TAG,"onPause");
        super.onPause();
    }

    @Override
    public void onStop()
    {
        Log.e(TAG,"onStop");
        super.onStop();
    }


    @Override
    public void onDestroyView()
    {
        Log.e(TAG,"onDestroyView");
        super.onDestroyView();
    }

    @Override
    public void onDestroy()
    {
        Log.e(TAG,"onDestroy");
        super.onDestroy();
    }

    @Override
    public void onDetach()
    {
        Log.e(TAG,"onDetach");
        super.onDetach();
    }
}
           

其布局很簡單,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:background="#66006600"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <TextView
        android:textSize="30sp"
        android:text="First Fragment"
        android:gravity="center"
        android:textColor="@android:color/white"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
           

Activity的代碼如下:

public class MainActivity extends Activity
{

    private static final String TAG = "Activity Life";

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e(TAG, "onCreate()...");

    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.e(TAG, "onRestart()...");
    }

    @Override
    protected void onStart() {
        // The activity is about to become visible.
        super.onStart();
        Log.e(TAG, "onStart()...");

    }

    @Override
    protected void onResume() {
        // The activity has become visible (it is now "resumed").
        super.onResume();
        Log.e(TAG, "onResume()...");
    }

    @Override
    protected void onPause() {
        // Another activity is taking focus (this activity is about to be "paused").
        super.onPause();
        Log.e(TAG, "onPause()...");
    }

    @Override
    protected void onStop() {
        // The activity is no longer visible (it is now "stopped")
        super.onStop();
        Log.e(TAG, "onStop()...");
    }

    @Override
    protected void onDestroy() {
        // The activity is about to be destroyed.
        super.onDestroy();
        Log.e(TAG, "onDestroy()...");
    }

}
           

其布局是将Fragment添加進來,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="horizontal"
    tools:context="com.gotechcn.testfagment.MainActivity">
    <fragment
        android:id="@+id/first_fragment"
        android:name="com.gotechcn.testfagment.FirstFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1">
    </fragment>
</LinearLayout>
           

好了,測試代碼已經準備好了,我們運作起來看看log

首先,直接運作,其生命周期:

Fragment的詳細解析介紹與應用如何使用FragmentFragment的事務管理Fragment與Activity的通信Fragment的生命周期

接着,按Home鍵,将應用暫停,其生命周期:

Fragment的詳細解析介紹與應用如何使用FragmentFragment的事務管理Fragment與Activity的通信Fragment的生命周期

然後,再點選應用喚醒,其生命周期:

Fragment的詳細解析介紹與應用如何使用FragmentFragment的事務管理Fragment與Activity的通信Fragment的生命周期

最後,按傳回鍵,關閉應用,其生命周期:

Fragment的詳細解析介紹與應用如何使用FragmentFragment的事務管理Fragment與Activity的通信Fragment的生命周期

如果了解Activity的生命周期,相信大家對于這個,一看也就明白了。是的,幾乎是相同的流程,這不足為怪,Fragment需要嵌入在Activity才能運作,相同的生命周期更好處理他們 的關系。

官方也有形象的生命周期流程圖,如圖所示:

Fragment的詳細解析介紹與應用如何使用FragmentFragment的事務管理Fragment與Activity的通信Fragment的生命周期

稍微總結一下他們的流程的差別:

相同點:

可以看出,Fragment所在的 Activity 的生命周期會直接影響Fragment的生命周期,其表現為,Activity 的每次生命周期回調都會引發每個Fragment的類似回調。 例如,當 Activity 收到 onPause() 時,Activity 中的每個Fragment也會收到 onPause()。

在其他方面,管理Fragment生命周期與管理 Activity 生命周期非常相似。 是以,管理 Activity 生命周期的做法同樣适用于Fragment。

對于狀态的儲存也是一樣的,假使 Activity 的程序被終止,而需要在重建 Activity 時恢複Fragment狀态,也可以使用 Bundle 保留Fragment的狀态。您可以在Fragment的 onSaveInstanceState() 回調期間儲存狀态,并可在 onCreate()、onCreateView() 或 onActivityCreated() 期間恢複狀态。

不同點:

其中,以下生命周期是與Activity不同的:

  • onAttach(Content) :在Fragment已與 Activity 關聯時調用(Content傳遞到此方法内)。
  • onCreateView(LayoutInflater, ViewGroup, Bundle)建立并傳回與該Fragment相關聯的視圖。
  • onActivityCreated(Bundle):在 Activity 的 onCreate() 方法已傳回時調用。
  • onDestroyView() 在移除與Fragment關聯的視圖層次結構時調用。
  • onDetach() 在取消Fragment與 Activity 的關聯時調用。
  • onDestroy() 所謂做片段的狀态的最後的清理工作。

Activity 生命周期與Fragment生命周期之間的最顯著差異在于它們在其各自傳回棧中的存儲方式。 預設情況下,Activity 停止時會被放入由系統管理的 Activity 傳回棧(以便使用者通過傳回按鈕回退到 Activity)。而Fragment,僅當在移除Fragment的事務執行期間通過顯式調用 addToBackStack() 儲存執行個體時,系統才會将片段放入由宿主 Activity 管理的傳回棧。

Fragment的特點大緻如下:

Fragment可以被多個Activity重複的利用;

Fragment可以用通過FragmentManager管理,進行動态的添加、移除、替換等操作;

Fragment作為Activity的組成部分,其通信互動是通過調用getActivity()擷取目前的Activity,Activity通過FragmentManager調用findFragmentById()來擷取Fragment,或者通過回調的方式通信;

Fragment可以響應自己的事件,擁有自己的生命周期,但同時又被宿主Activity的生命周期影響;

好了,Fragment的介紹就到這裡了。每次總結完,對于還是菜鳥的我來說,有那麼一瞬間找到大牛的感覺,哈哈。

如有錯誤,歡迎指出!