3.2 Fragment的用法與通信大全

點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖

本節例程下載地址:WillFlowFragment

究竟要如何使用碎片才能充分地利用平板屏幕的空間呢?假如我們正在開發一個新聞應用,其中一個界面使用 ListView 展示了一組新聞的標題,當點擊了其中一個標題,就打開另一個界面顯示新聞的詳細內容。如果是在手機中設計,我們可以將新聞標題列表放在一個活動中,將新聞的詳細內容放在另一個活動中。

一、靜態創建 Fragment

這是使用Fragment最簡單的一種方式,把Fragment當成普通的控件,直接寫在Activity的佈局文件中就可以了。

靜態創建 Fragment 的步驟:

1、繼承Fragment,重寫onCreateView決定Fragemnt的佈局
2、在Activity中聲明此Fragment,就當和普通的View一樣

下面我們用一個例子來展示靜態創建 Fragment 的過程。

新建一個左側碎片佈局 left_fragment.xml,代碼如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button"
        android:layout_marginTop="96dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

這個佈局非常簡單,只放置了一個按鈕,並讓它水平居中顯示。

然後新建右側碎片佈局 right_fragment.xml,代碼如下所示:
<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fc6243"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textColor="#4352fc"
        android:text="這個是右側的Fragment"
        android:textSize="22sp"
        android:layout_marginTop="96dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/textView" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="236dp"
        android:layout_height="236dp"
        android:layout_marginTop="40dp"
        app:srcCompat="@drawable/willflow"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

可以看到,我們將這個佈局的背景色設置成淺紅色,並放置了一個 TextView 用於顯示一段文本。

接着新建一個 LeftFragment 類,繼承自 Fragment,然後在關鍵的生命週期方法中插入代碼(就和在處理 Activity 時一樣)。注意:創建 Fragment 時,必須重寫 onCreateView() 回調方法來定義佈局。事實上,這是唯一一個爲使 Fragment 運行起來需要重寫的回調方法。

LeftFragment的代碼如下所示:
public class LeftFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.left_fragment, container, false);
        return view;
    }
}

這裏僅僅是重寫了 Fragment 的 onCreateView()方法,然後在這個方法中通過 LayoutInflater的 inflate()方法將剛纔定義的 left_fragment 佈局動態加載進來,整個方法再簡單明不過了。

接着我們用同樣的方法新建 RightFragment,代碼如下:
public class RightFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.right_fragment, container, false);
        return view;
    }
}

基本上代碼都是相同的,相信已經沒有必要再做什麼解釋了。

接下來修改 activity_main.xml中的代碼,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.wgh.willflowfragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <fragment
        android:id="@+id/right_fragment"
        android:name="com.wgh.willflowfragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

可以看到,我們使用了標籤在佈局中添加碎片,其中指定的大多數屬性我們都是熟悉的,只不過這裏還需要通過 android:name 屬性來顯式指明要添加的碎片類名,注意一定要將類的包名也加上。

編譯運行看效果:

注意: 當通過 XML 佈局文件的方式將 Fragment 添加進 Activity 時,Fragment 是不能被動態移除的。如果想要在用戶交互的時候把 Fragment 切入與切出,必須在 Activity 啓動後,再將 Fragment 添加進 Activity,我們接下來學習這種方法。

二、動態創建 Fragment

在剛纔的例子當中,我們已經學會了在佈局文件中添加Fragment的方法,不過Fragment真正的強大之處在於,它可以在程序運行時動態地添加到Activity當中。根據具體情況來動態地添加Fragment,我們就可以將程序界面定製得更加多樣化。

新建 another_right_fragment.xml,代碼如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffaa00"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="33dp"
        android:text="這個是另一個右側的Fragment"
        android:textColor="#cb43fc"
        android:textSize="20sp" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="136dp"
        android:layout_height="136dp"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="24dp"
        android:src="@mipmap/ic_launcher" />

</RelativeLayout>

這個佈局文件的代碼和 right_fragment.xml 中的代碼基本相同,只是將背景色改成了橘黃色,並將顯示的文字改了改。

然後新建 AnotherRightFragment 作爲另一個右側碎片,代碼如下所示:
public class AnotherRightFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.another_right_fragment, container, false);
        return view;
    }
}

代碼同樣非常簡單,在 onCreateView() 方法中加載了剛剛創建的 another_right_fragment 佈局。這樣我們就準備好了另一個Fragment,接下來看一下如何將它動態地添加到活動當中。

修改 activity_main.xml,代碼如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.wgh.willflowfragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <fragment
            android:id="@+id/right_fragment"
            android:name="com.wgh.willflowfragment.AnotherRightFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</LinearLayout>

可以看到,現在將右側Fragment放在了一個 FrameLayout 中,還記得這個佈局嗎?在之前我們學過,這是 Android 中最簡單的一種佈局,它沒有任何的定位方式,所有的控件都會擺放在佈局的左上角。由於這裏僅需要在佈局裏放入一個Fragment ,因此非常適合使用 FrameLayout 幀佈局。之後我們將在代碼中替換 FrameLayout 裏的內容,從而實現動態添加Fragment 的功能。

修改MainActivity 中的代碼,如下所示:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button_lf);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AnotherRightFragment fragment = new AnotherRightFragment();
                FragmentManager fragmentManager = getFragmentManager();
                FragmentTransaction transaction = fragmentManager.beginTransaction();
                transaction.replace(R.id.right_layout, fragment);
                transaction.commit();
            }
        });
    }

可以看到,首先我們給左側Fragment 中的按鈕註冊了一個點擊事件,然後將動態添加Fragment 的邏輯都放在了點擊事件裏進行。

這樣就完成了在活動中動態添加Fragment的功能,接下來運行看效果:

動態添加Fragment主要分爲五步:
  1. 創建待添加的Fragment實例。
  2. 獲取到 FragmentManager,在Activity中可以直接調用 getFragmentManager() 方法得到。
  3. 開啓一個事務,通過調用 beginTransaction() 方法開啓。
  4. 向容器內加入Fragment,一般使用 replace() 方法實現,需要傳入容器的 id 和待添加的Fragment實例。
  5. 提交事務,調用 commit()方法來完成。

三、Fragment 的通信

1、與Activity通信

方法一:findViewById()

儘管 Fragment 是作爲獨立於 Activity 的對象實現,並且可在多個 Activity 內使用,但 Fragment 的給定實例會直接綁定到包含它的 Activity。

具體地說,Fragment 可以通過 getActivity() 訪問 Activity 實例,並輕鬆地執行在 Activity 佈局中查找視圖等任務。

View listView = getActivity().findViewById(R.id.listView);

同樣地,我們的 Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通過從 FragmentManager 獲取對 Fragment 的引用來調用Fragment中的方法。例如:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
方法二:創建對 Activity 的事件回調

在某些情況下,我們可能需要通過 Fragment 與 Activity 共享事件。執行此操作的一個好方法是,在Fragment內定義一個回調接口,並要求宿主 Activity 實現它。 當 Activity 通過該接口收到回調時,可以根據需要與佈局中的其他片段共享這些信息。

例如,如果一個新聞應用的 Activity 有兩個Fragment:一個用於顯示文章列表(Fragment A),另一個用於顯示文章(Fragment B)— 那麼Fragment A 必須在列表項被選定後告知 Activity,以便它告知Fragment B 顯示該文章。

在下面的例子中,OnArticleSelectedListener 接口在 FragmentA 內聲明:

public static class FragmentA extends ListFragment {
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
}

然後,該Fragment的宿主 Activity 會實現 OnArticleSelectedListener 接口並替代 onArticleSelected(),將來自Fragment A 的事件通知Fragment B。爲確保宿主 Activity 實現此接口,Fragment A 的 onAttach() 回調方法(系統在向 Activity 添加Fragment時調用的方法)會通過轉換傳遞到 onAttach() 中的 Activity 來實例化 OnArticleSelectedListener 的實例:

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");
        }
    }
}

如果 Activity 未實現接口,則Fragment會引發 ClassCastException。實現時,mListener 成員會保留對 Activity 的 OnArticleSelectedListener 實現的引用,以便Fragment A 可以通過調用 OnArticleSelectedListener 接口定義的方法與 Activity 共享事件。例如,如果Fragment A 是ListFragment的一個擴展,則用戶每次點擊列表項時,系統都會調用片段中的 onListItemClick(),然後該方法會調用 onArticleSelected() 以與 Activity 共享事件:

    public static class FragmentA extends ListFragment {
        OnArticleSelectedListener mListener;
        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
            mListener.onArticleSelected(noteUri);
        }
    }

傳遞到 onListItemClick()的 id 參數是被點擊項的行 ID,即 Activity(或其他片段)用來從應用的 ContentProvider獲取文章的 ID。

2、與其它Fragment通信

爲了重用 Fragment UI 組件,我們應該把每個 Fragment 都構建成完全自包含的、模塊化的組件,即定義它們自己的佈局與行爲。一旦我們定義了這些可重用的 Fragment,我們就可以通過應用程序邏輯讓它們關聯到 Activity,以實現整體的複合 UI。

通常 Fragment 之間可能會需要交互,比如基於用戶事件的內容變更。所有 Fragment 之間的交互應通過與之關聯的 Activity 來完成,也就是說兩個 Fragment 之間不應直接交互。

(1)定義接口

爲了讓 Fragment 與包含它的 Activity 進行交互,可以在 Fragment 類中定義一個接口,並在 Activity 中實現。該 Fragment 在它的 onAttach() 方法生命週期中獲取該接口的實現,然後調用接口的方法,以便與 Activity 進行交互。

以下是 Fragment 與 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);

        // 確認容器 Activity 已實現該回調接口。否則,拋出異常
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(
             activity.toString() + " 必須實現這個接口!");
        }
    }
}

現在 Fragment 可以通過調用 mCallback(OnHeadlineSelectedListener 接口的實例)的 onArticleSelected() 方法(也可以是其它方法)與 Activity 進行消息傳遞。

例如當用戶點擊列表條目時,Fragment 中的下面的方法將被調用:
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // 向宿主 Activity 傳送事件
        mCallback.onArticleSelected(position);
    }

##### (2)實現接口
爲了接收回調事件,宿主 Activity 必須實現在 Fragment 中定義的接口。

例如,下面的 Activity 實現了上面例子中的接口:

public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
    public void onArticleSelected(int position) {
        // 在這裏做點什麼...
    }
}
(3)向 Fragment 傳遞消息

宿主 Activity 通過 findFragmentById() 獲取Fragment 的實例,然後直接調用 Fragment 的 public 方法向 Fragment 傳遞消息。

例如,假設上面所示的 Activity 可能包含另一個 Fragment,該 Fragment 用於展示從上面的回調方法中返回的指定的數據。在這種情況下,Activity 可以把從回調方法中接收到的信息傳遞到這個展示數據的 Fragment。

public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{

    public void onArticleSelected(int position) {

        ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            articleFrag.updateArticleView(position);
        } else {
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);

            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);
            transaction.commit();
        }
    }
}

這裏面涉及到了返回棧的概念,我們接下來給大家簡單介紹下。

在碎片中模擬返回棧:

上面的代碼中我們成功實現了向Activity中動態添加Fragment的功能,不過通過點擊按鈕添加了一個Fragment之後,這時按下 Back 鍵程序就會直接退出而不是返回。如果這裏我
們想模仿類似於返回棧的效果,就需要將一個事務添加到返回棧中。修改 MainActivity 中的代碼如下即可:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button_lf);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AnotherRightFragment fragment = new AnotherRightFragment();
                FragmentManager fragmentManager = getFragmentManager();
                FragmentTransaction transaction = fragmentManager.beginTransaction();
                transaction.replace(R.id.right_layout, fragment);
                transaction.addToBackStack(null);
                transaction.commit();
            }
        });
    }
添加返回棧後的效果:

點此進入:GitHub開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:


聯繫方式:

簡書:WillFlow
CSDN:WillFlow
微信公衆號:WillFlow

微信公衆號:WillFlow

發佈了81 篇原創文章 · 獲贊 21 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章