[Android] 使用Fragment創建動態UI

使用Fragment創建動態UI

要在Android上面創建一個動態的、多面板的UI,你需要將這些UI組件和activity的行爲封裝成模塊(modules),這樣就可以在你的activity中進行交互。你可以使用Fragment類來創建這些模塊,這個類的行爲有點像是嵌套的activity,可以定義自己的佈局(layout)以及管理它自己的生命週期。

當一個fragment指定了它的佈局,它就可以和其他的fragment組成不同的組合放在activity裏,當運行在屏幕尺寸不同的device上時可以修改佈局配置(一個小屏幕可能同時只能顯示一個fragment,而一個大屏幕可能可以顯示兩個或更多)。

這節課來介紹的是使用fragment創建動態的UI並提升你的app在不同尺寸的設備上的用戶體驗,所有內容都繼續支持那些還運行在Android 1.6上點設備。

創建一個Fragment

你可以把Fragment想成是activity的一個模塊化的部分,有它自己的生命週期,可以接收輸入事件,並且在activity運行的時候也可以進行添加或移除的動作(有點像是“子activity”的感覺,可以在不同的activity中重複使用)。這節課介紹的是怎樣使用Support Library繼承(extend)fragment,這樣你的app就算是在運行着Android 1.6的設備上也可以保持兼容。

注:如果你決定自己app的最小API level是11或者是更高的話,那麼就不用再使用Support Library,取而代之地可以使用framework內建的Fragment類以及相關的API。請注意,這節課重點關注的是Support Library裏的API,使用的是特定的包簽名(package signature),可能和導入在platform中的版本在API名字上略有差異。

在開始這節課之前,你需要使用Support Library創建你的Android Project。如果你之前沒有使用過Support Library,那就跟着Support Library Setup這篇文檔(需要翻牆)使用v4 library建立你的project。你也可以使用v7 appcompat library在你的activity中引入app bar,這個庫與Android 2.1(API level 7)兼容,並且也包含了Fragment API。

創建一個Fragment類

要創建一個fragment,先繼承Fragment類,然後重寫(override)關鍵的生命週期方法來插入你自己的代碼邏輯,這和使用Activity類的方法很相似。

一個重要的區別是,在創建fragment時,你必須使用onCreateView() callback來定義佈局。事實上,要讓你的fragment運行起來,這個是你唯一需要的callback。舉個例子,下面是一個簡單的fragment,指明瞭它自己的佈局:

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

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

就像是activity一樣,fragment也需要實現其他的生命週期callback,來允許你當它在activity中添加或移除時、或者是activity在它的生命週期轉換時來管理自己的狀態。例如當activity的onPause()方法被調用了之後,所有在這個activity中的fragment也會收到onPause()的調用。

更多關於fragment的生命週期及callback方法可以參考developer guide中的Fragments章節。

使用XML向Activity添加Fragment

由於fragment是可以重複使用的、模型化的UI組件,每一個Fragment類的實例(instance)都要與一個父FragmentActivity相關聯。你可以通過在activity的佈局XML文件中定義各個fragment來實現這種關聯。

:FragmentActivity是一個特殊的activity,由Support Library提供,用來處理版本低於API level 11的系統中的fragment,如果你的app最低系統版本爲API level 11或者更高,那麼你就可以使用正常的Activity類。

如下的示例佈局文件是在當device的屏幕被認爲是“large”的時候向activity中添加兩個fragment(在路徑名中使用large限定符指明):

res/layout-large/news_articles.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <fragment android:name="com.example.android.fragments.HeadlinesFragment"
              android:id="@+id/headlines_fragment"
              android:layout_weight="1"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

    <fragment android:name="com.example.android.fragments.ArticleFragment"
              android:id="@+id/article_fragment"
              android:layout_weight="2"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

</LinearLayout>

提示:想要了解更多關於創造不同屏幕尺寸的佈局,請閱讀Support Different Screen Sizes

然後在你的activity中使用這個佈局:
譯者注:文件位置爲 java/com/example/android/fragments/MainActivity.java
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);
    }
}

如果你使用的是v7 appcompat library,你的MainActivity應該繼承的是AppCompatActivity,這是FragmentActivity的一個子類。要獲取更多信息,請閱讀Adding the APP Bar

:如果你在佈局XML文件中定義了fragment來將它們添加到activity的佈局,那麼在運行時你將沒辦法將這些fragment移除。如果你想要在與用戶交互時可以替換你的fragment,那麼你必須讓activity先開始,然後再添加fragment,即後續章節所講。

創建一個靈活的UI

爲了將你的應用設計成可以支持廣泛的屏幕尺寸範圍,你可以在不同的佈局配置中重新使用你的fragment,基於現有的屏幕空間來提高用戶體驗。

例如,在單視圖的手機上同時只顯示一個fragment可能是更恰當的;相反的,你可能想要在平板上並排顯示這些fragment,因爲它有更大的屏幕尺寸,一個給用戶顯示更多的信息。



圖1: 兩個fragment,對於同一個activity在不同的屏幕尺寸上顯示不同的配置。在大屏幕上,兩個fragment可以並排擺放;然而在手機上,同一時間只有一個fragment是比較合適的,所以在用戶操作時fragment之間會互相替換。

FragmentManager類提供了在運行時可以向activity添加、移除或替換fragment的方法,以實現動態的體驗。

在運行時向Activity添加Fragment

與其在佈局文件中爲activity定義fragment(之前所提到的使用<fragment>元素),你可以在activity運行時向activity添加fragment。如果你打算在activity的生命週期中改變fragment,那麼這種方法是必須的。

要執行一項事務(transcation),例如添加或移除fragment,你需要使用FragmentManager來創建一個FragmentTranscation,它提供了添加、移除、替換以及執行其他fragment事務的API。

如果你的activity允許fragment被移除或替換,那麼你應該在activity 的onCreate()方法中向activity添加初始fragment(s)。

處理fragment時的一個非常重要的原則(特別時在運行時添加fragment)就是你的activity佈局必須包含了一個View容器,可以將fragment插在裏面。

下面的這種佈局可以作爲一種替代選擇,替代之前所提到的在同一時間只能顯示一個fragment的佈局。爲了使fragment能夠被互相替換,activity的佈局包含了一個空的Framelayout,這個framelayout扮演者fragment容器的角色。

要注意的是這個佈局文件要與之前的layout文件同名,但是佈局的路徑並不含有large限定符,所以實際上這個佈局是使用在設備的屏幕比large要小的時候,因爲這時屏幕並不適合同時顯示兩個fragment。

res/layout/new_articles.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

在你的activity類中,調用getSupportFragmentManager()使用Support Library APIs得到FragmentManager。然後調用beginTranscation()來創建一個FragmentTranscation,然後調用add()來添加一個fragment。

你可以使用同一個FragmentTranscation對activiy執行多個fragment事務。當你準備好要讓這些更改生效時,你必須調用commit()。

下面這個例子介紹瞭如何向上面的這個佈局添加fragment:
譯者注:文件位置爲 java/com/example/android/fragments/MainActivity.java
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);

        // Check that the activity is using the layout version with
        // the fragment_container FrameLayout
        if (findViewById(R.id.fragment_container) != null) {

            // However, if we're being restored from a previous state,
            // then we don't need to do anything and should return or else
            // we could end up with overlapping fragments.
            if (savedInstanceState != null) {
                return;
            }

            // Create a new Fragment to be placed in the activity layout
            HeadlinesFragment firstFragment = new HeadlinesFragment();
            
            // In case this activity was started with special instructions from an
            // Intent, pass the Intent's extras to the fragment as arguments
            firstFragment.setArguments(getIntent().getExtras());
            
            // Add the fragment to the 'fragment_container' FrameLayout
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, firstFragment).commit();
        }
    }
}

因爲這個fragment是在運行時被添加到了Framelayout這個容器中(而不是在activity的佈局中使用<fragment>元素),所以activity也可以將這個fragment移除,亦或者是使用其他的fragment來代替之前的這個。

替換Fragment

替換fragment的步驟與添加fragment相似,但需要調用的方法是replace(),而不是add()。

請記住,當你執行fragment事務,例如替換或移除fragment的時候,允許用戶會退到之前並“取消”之前的改變往往是必要的。爲了允許用戶在fragment事務中操作回退,你需要在提交FragmentTranscation之前調用addToBackStack()。

注:當你移除或替換了一個fragment並且把事務添加到回退棧(back stack)時,這個被移除/替換的fragment處在停止(stopped)的狀態,而不是銷燬了(destoryed)的狀態。如果用戶操作回來並恢復這個fragment,它會重新開始(restart)。如果你沒有把這個事務添加到回退棧的話,這個fragment在替換或移除時就會被銷燬。

替換fragment的示例:
譯者注:文件位置爲 java/com/example/android/fragments/ArticleFragment.java
// code snippet
// Create fragment and give it an argument specifying the article it should show
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();
// code snippet

addToBackStack()方法有一個可選的字符串輸入變量,可以指明唯一的事務的名字。除非你想要使用FragmentManager.BackStackEntry API執行高級的fragment操作,否則這個名字是不需要的(譯者注:平常帶null就好)。

與其他Fragment通信

爲了能夠重複使用fragment UI組件,你需要將每一個fragment構建成完全獨立的、模塊化的組件,並且定義有自己的佈局及行爲。一旦你定義好了這些可以重複使用的fragment,你就可以將它們關聯到activity,使用你的應用邏輯連接它們,來實現一個整體複合的UI。

時常地你會想要讓一個fragment可以與其他的fragment進行通信,例如基於用戶事件來改變內容。所有的fragment到fragment的通信都是通過與之關聯的activity來完成的。兩個fragment之前永遠都不能直接通信。

定義一個接口

爲了允許fragment向它的activity通信,你可以在fragment類裏定義一個接口(interface)並在activity裏實現(implement)它。Fragment會在它生命週期的onAttach()方法中捕獲到接口的實現,然後可以調用這個接口方法來和activity通信。

以下是fragment到activity通信的舉例:
譯者注:文件位置爲 java/com/example/android/fragments/HeadlinesFragment.java
public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // Container Activity must implement this interface
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }
    
    // ...
}

現在,通過調用onArticleSelected()方法(或者是接口裏的其他方法),fragment可以通過使用OnHeadlineSelectedListener接口的實例mCallback向activity傳遞信息。

舉個例子,當用戶在列表項目上進行點擊時,fragment裏的如下方法就會被調用。Fragment使用callback接口向父activity傳遞事件。
譯者注:文件位置爲 java/com/example/android/fragments/HeadlinesFragment.java
// ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Send the event to the host activity
        mCallback.onArticleSelected(position);
    }
// ...

實現這個接口

爲了從fragment接收事件回調,盛裝fragment的activity比需要實現在fragment類中定義的藉口。

例如,如下activity實現了上面例子中的藉口。
譯者注:文件位置爲 java/com/example/android/fragments/MainActivity.java
public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(int position) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article
    }
}

向Fragment傳遞信息

父activity可以通過findFragmentById()捕獲Fragment實例來向fragment傳遞信息,然後可以直接調用fragment的公有方法。

舉個例子:想象一下,上面的activity有可能包含了另外一個fragment,用來顯示通過上面的callback方法返回的數據所指明的項目。在這種情況下,activity可以將從callback方法中得到的信息傳遞給另一個fragment,這個fragment將會顯示那些項目:

譯者注:文件位置爲 java/com/example/android/fragments/MainActivity.java
public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(int position) {
        // The user selected the headline of an article from the HeadlinesFragment

        // Capture the article fragment from the activity layout
        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // If article frag is available, we're in two-pane layout...

            // Call a method in the ArticleFragment to update its content
            articleFrag.updateArticleView(position);

        } else {
            // If the frag is not available, we're in the one-pane layout and must swap frags...

            // Create fragment and give it an argument for the selected article
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();
        }
    }
}

原始地址

谷歌開發者training(需要翻牆):https://developer.android.com/training/basics/fragments/index.html


代碼下載

http://download.csdn.net/detail/qiwenhe1990/9369199



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章