2012年4月24日 星期二

第一支Android Fragment程式--HelloFragment


最近手機升級到Android 4.0.3版,想說來試試看寫個有 Fragment 程式,整理了一下學習心得。
隨著平板電腦及多螢幕系統的出現, Fragment 的設計是為了讓大尺寸的螢幕有更動態更彈性的UI設計。

下圖是官網中Design Philosophy所提供的一個範例,用這張圖舉個例子,如果要設計動態新聞資訊時,在手機平台 Activity 1 設計使用 Fragment 1 (新聞列表) ,Fragment 2 (新聞內容);在平板則設計左邊為 Fragment 1 (新聞列表) ,右邊畫面為 Fragment 2 (新聞內容) 。

Fragment 有兩種,一種是具有UI介面作為將一個 ViewGroup 增加到 Activity 中(見實作1),另一種是沒有UI介面直接動態加入 Activity 中(見實作2),另外如果作為背景工作使用也採用此種方式(見實作3)。
Fragment (碎片)不能獨立存在必須依附在Activity內,Fragment擁有自己的生命周期,但會受到Activity生命週期的約束。所以使用時要考慮設計成模組化及可重複使用性,通常是作為更新頻繁的列表之類的功能,
每個 Fragment 定義自己的佈局(layout)、生命週期行為,並配合不同螢幕解析度或佈局配置下優化螢幕空間。這樣的作法最主要的目的是當使用者操作該介面時只要更新Fragment所負責的介面,而不用更新到整個Activity;
這樣可以避免掉Activity在做換頁時進行多次的onDestroy()等工作,常使用在 Fragment 在 Activityt提供了一個Back Stack來儲存操作資訊,然後透過選單或是按鈕之類的方式對Fragment進行回復或退回,這樣避免掉Activity與Activity切換時所浪費的時間,大大提高程式的執行效率。

在Android 系統中,透過 Stack方式來管理 Activity ,一般來說由於Android由於設計在手持裝置上所以不能像Windows一樣可以將多個應用程式介面並排或重疊在一個畫面上,當開啟一個 Activity 時系統會按照使用時間先後自動將該Activity放置在一個Stack中,並將前景使用的Activity放置在Stack最前面。
同樣,管理Fragment也是Stack方式差別在於這個Stack是由Activity來管理,當呼叫 addToBackStack方法時Fragment才會被加入Stack內。因此,Fragment不是個應用及的元件,所以不需要再 AndroidManifest.xml 中去定義Fragment相關參數。

另外,需要注意的地方還有每個Fragment需要一個唯一的標識,這樣能夠在Activity被重啟時系統使用這個ID來恢復Fragment。
Fragment提供ID的方法有三種方式:
A. Supply the android:id attribute with a unique ID. (使用android:id屬性來設定唯一ID;)
B. Supply the android:tag attribute with a unique string.(使用android:tag屬性來設定唯一的字串;)
C. you provide neither of the previous two, the system uses the ID of the container view.(如果沒有設定前面兩個屬性,系統會使用Fragment容器View ID。)

實作1: 第一個 Fragment
1.建立Fragment (MyFirstFragment.java、firstfragment.xml)
firstfragment.xml


<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/Fragment1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical|center_horizontal"
    android:text="My first Fragment !!"
    android:textAppearance="?android:attr/textAppearanceLarge" />


MyFirstFragment.java

package com.demo.fragment;

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

public class MyFirstFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

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

程式中,定義Fragment返回的UI介面,我們用到了 onCreateView ,其中有定義了三個參數,第一個 LayoutInflater 用來從介面佈局中初始化View;第二個 ViewGroup 是儲存 Fragment 返回UI View的ViewGroup物件;第三個Bundle用來保留上一個 Fragment 訊息用於狀態回復。
2.MainActivity
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:background="#FFFFFF"
        android:text="Main Activity"
        android:textColor="#0000FF" />

    <fragment
        android:id="@+id/first"
        android:name="com.demo.fragment.MyFirstFragment"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_weight="2" />

</LinearLayout>


MainActivity.java

package com.demo.fragment;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

3.結果:


實作2: 動態增加  Fragment
1.建立Fragment (MyFirstFragment.java、firstfragment.xml)
firstfragment.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/Fragment1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical|center_horizontal"
    android:text="My first Fragment !!"
    android:textAppearance="?android:attr/textAppearanceLarge" />

MyFirstFragment.java

package com.demo.fragment;

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

public class MyFirstFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

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

2.MainActivity
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/MainActivityUI"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:background="#FFFFFF"
        android:text="Main Activity"
        android:textColor="#0000FF" />

</LinearLayout>


MainActivity.java

package com.demo.fragment;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        addFragment();
    }

    void addFragment() {

        // Instantiate a new fragment.
        Fragment newFragment = new MyFirstFragment();

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.MainActivityUI, newFragment, "first");
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        ft.commit();
    }
}

增加一個 Fragment 部分的程式碼在addFragment()中實現,首先建立一個 MyFirstFragment 的實例(Instantiate),
在使用getFragmentManager()獲得FragmentTransaction物件,並呼叫 beginTransaction() 開始執行Transaction;
過程中使用FragmentTransaction物件add()的方法將Fragment增加到Activity中。
add()有三個參數,第一個是Fragment的ViewGroup;第二個是Fragment 的實例(Instantiate);第三個是Fragment 的Tag。
一旦FragmentTransaction出現變化,必須要呼叫commit()使之生效。

3.結果:



實作3: 動態增加沒有UI的 Fragment
1.這部分程式在Android ApiDemos 中有範例請參考:
 SDK根目錄\samples\android-15\ApiDemos\src\com\example\android\apis\app\FragmentRetainInstance.java
或 http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.html

3.結果:

這部分主要是透過 FragmentTransaction 中的 add(Fragment fragment, String tag) 在Activity中來增加Fragment,與上述兩個實例不一樣地方在於不用呼叫onCreateView()。
如果Activity 取得 Fragment,就要利用Tag方式在程式中可以使用 findFragmentByTag()方法達成。

至於其他相關資料例如 Fragment生命週期、FragmentManager 、在Android 2.x ~ 1.6版設備使用Fragment 等,有空時再整理分享。

參考:
1.The Android 3.0 Fragments API
http://android-developers.blogspot.com/2011/02/android-30-fragments-api.html

2.Fragments For All
http://android-developers.blogspot.com/2011/03/fragments-for-all.html

3.Fragments
http://developer.android.com/guide/topics/fundamentals/fragments.html (英文版)
http://leybreeze.com/blog/?p=902 (簡體中文版)

4.Android在平版電腦上的Activity設計和智慧型手機上有些不同
http://cheng-min-i-taiwan.blogspot.com/2011/03/androidactivity.html




==============延伸閱讀=====================

1.第一支Android Fragment程式--HelloFragment
http://cheng-min-i-taiwan.blogspot.tw/2012/04/android-fragment-hellofragment.html

2.Android Fragments 的生命週期
http://cheng-min-i-taiwan.blogspot.tw/2012/12/android-fragments.html

3.Fragment間的資料傳遞
http://cheng-min-i-taiwan.blogspot.tw/2013/01/fragment.html

4.Fragment與Activity間的資料傳遞
http://cheng-min-i-taiwan.blogspot.tw/2013/02/fragmentactivity.html

5.Fragment子類別之ListFragment
http://cheng-min-i-taiwan.blogspot.tw/2013/02/fragmentlistfragment.html

6.Fragment子類別之DialogFragment
http://cheng-min-i-taiwan.blogspot.tw/2013/02/fragmentdialogfragment.html

7.Fragment子類別之PreferenceFragment
http://www.cheng-min-i-taiwan.blogspot.tw/2013/02/fragmentpreferencefragment.html

8.Fragment子類別之WebViewFragment
http://cheng-min-i-taiwan.blogspot.tw/2013/02/fragmentwebviewfragment.html

3 則留言:

  1. 版主妳好:
    近期正在摸索android這個領域
    遇到fragment這部分的問題
    是否方便請教版主呢?
    謝謝

    回覆刪除
    回覆
    1. 可以的,但我不一定可以回覆您的正確答案ㄡ...

      刪除
  2. 老師:
    可不可以寫一篇 GOOGLE TV模擬器教學
    網路上好像也沒有類似的教學文章~~

    回覆刪除