Android – Fragment 基本觀念

Fragment 代表Activity 中的一部分使用者介面,在Activity中可以合併多個Fragment,來建置多窗格 UI 或者在多個 Activity 中重複使用Fragment。同一個介面手機裝置上能有很好的展現,但在平板電腦上就未必能有好的展現,由於平板電腦的螢幕比手機大上許多,同個介面放在平板電腦上可能被拉長、或間距變大等情況,這時就能使用Fragment來解決這些問題 。

例如:以應用程式範例加以說明,在手機裝置上由於螢幕大小不足以容納兩個Fragment,所以Actvity1只會包含Fragment A,當使用者選取選項後跳換至Activity2,然後由Fragment B顯示內容。不過平板電腦有更多空間可結合及交換 UI 元件,能夠將兩個Fragment都包含進來,不用在兩個Activity中換,只需要在一個介面裡操兩個Fragmet畫面。

通常對於一個app專案開發而言, 一個Activity用來處理一個視覺版面上的商業邏輯. 而往往在一個視覺版面上也可能會有部分差異的區隔, 例如以下的版面設計, 在最上方提供了旅遊, 美食, 住宿及購物四大功能, 使用者可以非常直覺性的點擊或是滑動操作切換, 在這種狀況之下, 開發模式仍然是以一個Activity來處理, 但是因為商業邏輯的差異, 則將會以Fragment來切割出不同的商業邏輯, 進一步可以達到專案維護性的提高.

 

建立Fragment


要使用Fragment必須先建立一個 Fragment 的子類別,Fragment與Activity十分相似,例如 onCreate()、onStart()、 onPause() 和 onStop()的生命周期回乎方法,但至少必須實作以下生命週期方法:onCreateView(), 當 Fragment 初次顯示期介面時系統會呼叫此方法。您需要透過此方法來顯示 Fragment 的 UI,能夠在這裡加入對UI的控制來達到您所要的需求。 如果 Fragment 並未提供 UI 的話,則可以傳回空值。

為了讓系統能夠呼叫Fragment其版面配置,首先必須實作 onCreateView() 回呼方法,實作這個方法時必須傳回 View, 也就是呈現出該 Fragment 所要呈現的畫面。

以下進行建立 Fragment 的基本程序, 首先先建立一個空白的基本專案. 接著下來先處理版面配置,

頂端由一個LinearLayout來配置了四個Button, 用來切換下面目前是空白的FrameLayout, 也就是說, FrameLayout當作是一個容器, 用來切換呈現之後要表現的Fragmemt.

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"
    android:orientation="vertical"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="changeToPage1"
            android:text="Page1"
            />
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="changeToPage2"
            android:text="Page2"
            />
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="changeToPage3"
            android:text="Page3"
            />
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="changeToPage4"
            android:text="Page4"
            />
    </LinearLayout>
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</LinearLayout>

再來就在專案Package下. 透過選單導引建立一個Fragment,  File → New → Fragment → Fragment(Blank), 輸入 Fragment Name: Page1Fragment, 而不需要勾選 Include fragment factory methods 以及 Include interface callbacks

於是就自動建立出一個最基本的Fragment類別, 以及其所搭配的版面配置, 作為第一個Page1.

先來處理其版面配置, 設定了一個TextView, 呈現出目前為第一頁.

 

<FrameLayout 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="tw.brad.myproj3.Page1Fragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="36sp"
        android:text="第一頁" />

</FrameLayout>

 

回到 Page1Fragment.java 中, 看到了實作的Override方法: onCreateView().

 

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

透過傳遞的參數LayoutInflater的物件實體inflater, 呼叫了inflater(), 傳遞了第一個參數, 即為前面所設計的版面資源R.layout.fragment_page1.

此處注意一個小地方, 就是該程式的 import 部分, Fragment 是 android.app.Fragment.

 

…...
import android.app.Fragment;
…...

如上相同的方式, 分別建立出其他三個Fragment(Page2Fragment, Page3Fragment及Page4Fragment)之後, 進行MainActivity.java的處理. 有以下幾個關鍵處理程序:

  • 透過findViewById()取得之前的版面配置的容器container(FrameLayout)物件實體
  • 將會呼叫getFragmentManager()取得FragmentManager物件實體, 來管理Fragment的運作.
  • 分別建立四個Fragment的物件實體
  • 先在onCreate()中將第一頁的Fragment, 以FragmentManager物件取得的FragmentTranscation物件呼叫add()與container進行結合.
  • 最後針對按下Button後的事件, 分別進行replace()的處理.

 

import android.app.Fragment;
import android.os.Bundle;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;

public class MainActivity extends AppCompatActivity {
    private FragmentManager fmgr;
    private FragmentTransaction fragmentTransaction;
    private ViewGroup container;
    private Fragment page1Fragment, page2Fragment,
            page3Fragment, page4Fragment;


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

        // 取得 container, 做為容器使用
        container = (ViewGroup) findViewById(R.id.container);

        // 取得 FragmentManager物件實體
        fmgr = getFragmentManager();

        // 建立四個Fragment物件實體
        page1Fragment = new Page1Fragment();
        page2Fragment = new Page2Fragment();
        page3Fragment = new Page3Fragment();
        page4Fragment = new Page4Fragment();

        // 取得交易物件
        fragmentTransaction = fmgr.beginTransaction();

        // 初始加入第一頁, 並與 container 結合
        fragmentTransaction.add(R.id.container, page1Fragment);

        // 實現動作程序
        fragmentTransaction.commit();
    }

    public void changeToPage1(View view){
        fragmentTransaction = fmgr.beginTransaction();
        fragmentTransaction.replace(R.id.container, page1Fragment);
        fragmentTransaction.commit();
    }
    public void changeToPage2(View view){
        fragmentTransaction = fmgr.beginTransaction();
        fragmentTransaction.replace(R.id.container, page2Fragment);
        fragmentTransaction.commit();
    }
    public void changeToPage3(View view){
        fragmentTransaction = fmgr.beginTransaction();
        fragmentTransaction.replace(R.id.container, page3Fragment);
        fragmentTransaction.commit();
    }
    public void changeToPage4(View view){
        fragmentTransaction = fmgr.beginTransaction();
        fragmentTransaction.replace(R.id.container, page4Fragment);
        fragmentTransaction.commit();
    }

}

 

經過以上的開發建置, 即可以清楚看到了整個運作的主要觀念如下:

  • 整體的運作仍然以Activity的生命週期為主
  • Fragment用來處理每個分頁的內容商業邏輯, 專案的維護性提高
  • Fragement將會存在於Activity的一個Layout中(容器的觀念)
  • FragementTransaction物件是用來進行增刪及替換使用的目的

 

Fragement 的運作設計


在任一的Fragment中的UI元件, 通常會在onCreateView中進行取得及處理, 以在fragment_page1.xml 中為例, 設計了一個Button及TextView, 改寫如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="36sp"
        android:text="第一頁" />
    <View
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="#0000ff"
        />
    <Button
        android:id="@+id/page1Btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="按下測試"
        />
    <TextView
        android:id="@+id/page1Mesg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        />

</LinearLayout>

此時開發的重點將會是Fragement中的onCreateView(). 首先將原先以LayoutInflater呼叫inflater()傳回的View, 宣告為屬性變數 mainView. 當第一次建立Fragment物件實體時, 所傳遞的ViewGroup container將會是null, 之後如果版面元件沒有異動的情況下, 將以下方式處理:

 

…
private View mainView;
…
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        if (mainView == null) {
            mainView = inflater.inflate(R.layout.fragment_page1, container, false);
…... // UI元件的取得及設定
        }
        return mainView;
    }

 

而要控制的元件, 也將會在此處進行取得及設定:

textViewMesg = (TextView) mainView.findViewById(R.id.page1Mesg);
buttonClick = (Button) mainView.findViewById(R.id.page1Btn);

buttonClick.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        doClick();
    }
});

 

 

 

 

 

 

 

 

 

%d bloggers like this: