Android 筆記:在Android Studio上製作分頁效果(FragmentTabHost 使用方法)

這次的筆記內容是利用 FragmentTabHost 和 Fragment 來製作分頁的效果。

因為最近想要製作一些多功能的 App,想把各種功能透過多個分頁來實現,因此來學習如何製作分頁效果。

主要的介面是利用 FragmentTabHost 來配置, FragmentTabHost 來當作容器,並放入許多 Fragment 來當作分頁。

步驟一:製作 FragmentTabHost 的 xml 檔


我常提醒我的學弟或是同學,如果有可以請 IDE 幫忙做完的工作就竟量讓 IDE 幫忙完成,所以我個人的習慣是:如果要創建Activity時都會選擇他們的預設選項,讓 IDE 幫我把 xml 和 java 檔一起建出來。

但這次,Activity 中沒有 FragmentTabHost 的預設選項,因此預設時做出來的 xml 檔需要大幅修改。

先來看一下主頁 FragmentTabHost 的 Layout 程式:
<android.support.v4.app.FragmentTabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        

        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

        <!-- 此 FrameLayout 不知為何會存在,若有高手知道歡迎指教! -->

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <!-- 以下為顯示的區塊 -->

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

    </LinearLayout>

</android.support.v4.app.FragmentTabHost>

讀者可以使用預設做出來的 xml 檔進行修改,可以轉到 Text 模式,然後將程式碼通通刪除,直接手動複製貼上上述程式碼或者手動打出自己的個性化 Layout。

在上面程式中你可以看到在主要的結構為 FragmentTabHost ,而在 FragmentTabHost 中固定要有以下三種子物件:一個 TabWidget 和 兩個 FrameLayout。

其中 realtabcontent 的 FrameLayout 為顯示每個分頁用的區塊,而至於 tabcontent 為一定要有的一個 FrameLayout ,但在本次筆記中卻不為用到。至於為什麼要有這個 FrameLayout 筆者也不知道,以下有找到一個大陸網友的資源網站有說明為什麼會有兩個 FrameLayout 但筆者也是看不太懂,如果有高手讀者經過歡迎指教。

而下面的程式中 FragmentTabHost 的 id 為引用系統的 android.R.id.tabhost (至於為什麼呢?這我也不知道,如果有厲害的讀者可以為我解答歡迎在下面留言喔!感恩!),因此這邊這一行在 id 中的設定為固定的。

步驟二:製作 FragmentTabHost 的 java 檔


廢話不多說,先來看程式碼吧:
package eric.mytabtest3;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;

public class MainActivity extends FragmentActivity {

   private FragmentTabHost mTabHost;

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

        //透過 id 取得 Layout 上的 FragmentTabHost 物件
        mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);

        //初始化 FragmentTabHost,第二個參數中為需要從系統取得 Fragment 的管理員。
        //第三個參數為要用來顯示 Fragment 的 FrameLayout 。 (Fragment 都會使用 FrameLayout 來掛載。)
        //第一個參數為所在的 Activity ,因為自己(MainActivity)就是主畫面,因此直接放入 this 即可。
        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);

        //加入分頁 apple 到 TabHost 中
        mTabHost.addTab(mTabHost.newTabSpec("apple").setIndicator("Apple"),
                AppleFragment.class,
                null);

        //加入分頁 banana 到 TabHost 中
        mTabHost.addTab(mTabHost.newTabSpec("banana").setIndicator("Banana"),
                BananaFragment.class,
                null);

    }
}

這段程式其實很單純,就是直接取得 FragmentTabHost  物件,並將分頁插入其中。

這邊要比較注意的地方是 FragmentTabHost 的 id 為系統的引用值,因此固定使用 android.R.id.tabhost。

接著, setup() 方法的功能為初始化設定 FragmentTabHost 物件。這是一定要有的程式,在取得了 FragmentTabHost 實體後一定要執行的一行方法。其中第一個參數為所屬的 Context 物件,因為該 FragmentTabHost 是屬於 MainActivity 的,因此該參數直接輸入 this 即可。第二個參數為 FragmentManager 物件,該物件為系統物件,因此要取得的方法就是使用 Activity 內建的方法: getSupportFragmentManager,就可以直接向系統取得 FragmentManager 的實體。而 FragmentManager 物件對於 Fragment 而言相當重要,他是管理所有 Fragment 的生成與消滅還有排序的重要物件。第三個參數為要用來顯示各 Fragment 的 FrameLayout。

而 addTab 也很淺顯易懂,就是加入分頁,以下為介紹 addTab 的說明:
參數一:mTabHost.newTabSpec("apple").setIndicator("Apple")
此參數要求為一個  TabHost.TabSpec 物件,而可以透過 FragmentTabHost 的動態方法 newTabSpec 製作出一個 TabHost.TabSpec 物件,其中 newTabSpec 只需要一個參數 string tag 。

tag 的概念為該 tab 的 id ,事後若還有需要取用到該 tab 就必須用這個 tag 去追蹤它。

而 setIndicator 方法中為設置該 Tab 的顯示方法,有兩種顯示方法:1. 只使用文字顯示,2. 使用文字和圖示(icon)來顯示。而本次筆記中只使用文字來顯示,若要使用文字+圖示,則 setIndicator 中就需要引用兩個參數,第一個依舊是要顯示文字,而第二個則是要放入的圖片的id。
參數二:AppleFragment.class
這個參數為要設丁該分頁的 Fragment 類別,很簡單,將希望顯示在該分頁的 Fragment 的類別放入該參數即可。
參數三:null
這個參數為一個 Bundle 物件,應該是便於個分頁間傳遞資料用的,但目前我們不需要,所以將該參數放入 null 物件。

步驟三、製作 Fragment


我有兩個 Fragment ,先貼上我的 Fragment 程式碼:

1. Apple 分頁的 AppleFragment
Apple 分頁圖示

AppleFragment.java
package eric.mytabtest3;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class AppleFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment_apple, container, false);
    }
}
fragment_apple.xml
<?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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".AppleFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_blank_fragment" />

</LinearLayout>
這篇 Fragment 的部分大概看看就好,我並沒有加入功能,只是一個單純有個 TextView 的介面,什麼事都不能做。目的只是區別與 BananaFragment 的不同。
2. Banana 分頁的 BananaFragment
Banana 分頁圖示

BananaFragment.java
package eric.mytabtest3;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class BananaFragment extends Fragment {

    MainActivity tmpA;
    TextView tvShow;
    EditText etEnter;
    Button btnEnter;


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        tmpA = (MainActivity)context;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_banana, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {

        tvShow = (TextView) getView().findViewById(R.id.tvShow);
        etEnter = (EditText) getView().findViewById(R.id.etEnter);
        btnEnter = (Button) getView().findViewById(R.id.btnEnter);
        btnEnter.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tvShow.setText(etEnter.getText());
            }
        });


        super.onActivityCreated(savedInstanceState);
    }

}
fragment_banana.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="顯示在此"
        android:id="@+id/tvShow"
        android:layout_gravity="center_horizontal"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <EditText
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:id="@+id/etEnter"
        android:layout_below="@+id/tvShow"
        android:layout_centerHorizontal="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Enter"
        android:id="@+id/btnEnter"
        android:layout_below="@+id/etEnter"
        android:layout_centerHorizontal="true" />

</RelativeLayout>
可以在 java 檔中看到, Fragment 的製作過程其實跟 Activity 很像,而且不用在意主頁 MainActivity,主頁 MainActivity 中的 FragmentTabHost 只是成為容器的用途,負責承載 Fragment ,而 Fragment 的形成可自行定義,並不用在 MainActivity 加入任何程式。非常方便。

在 BananaFragment.java 中我們可以看到,直接在 Activity 剛形成的瞬間(onActivityCreated),執行與畫面上的物件做連結的程式即可。與我們常在 Activity 的 onCreate 中做的事相同。

其中 onAttach 為 Fragment 中最早執行的一個方法。其執行時機為當 Activity 有要與 Fragment 進行連接,達成直屬關係時,就會執行,可以想像成 Fragment 開始工作時的第一個方法。但是注意,在 onAttach 時畫面並沒有準備好,若在此時執行任何與畫面物件連接的程式(ex: findViewById)時會發生錯誤。

若要執行 findViewById,請於 onCreateView 中或之後再執行為最佳,顧名思義,此方法執行過後畫面才會正式生成。


OK! 到目前為止就算正式完成了,接著叫執行看看吧!
感謝各位讀者!!!!


留言

  1. 加油加油!!
    自己整理一些學習心得,能與人分享真的是一件很棒的事~

    回覆刪除
  2. 不好意思
    請問版主能不能直接分享github檔案做參考呢

    回覆刪除

張貼留言

這個網誌中的熱門文章

MySQL 筆記:Join 合併表格,使用方式

Debian, Parallels desktop 筆記:如何建立自己的 Shared Folder / Debian and PD(Parallels Desktop) notes: How to create your own shared folder in the Linux VM in Parallels Desktop