編寫:fastcome1985 - 原文:https://developer.android.com/training/basics/fragments/creating.html
創(chuàng)建Fragment
可以把 Fragment 想象成 Activity 的模塊,它擁有自己的生命周期、接收輸入事件,可以在 Acvitity 運行過程中添加或者移除(有點像“子 Activity”,可以在不同的 Activity 里重復使用)。
創(chuàng)建Fragment
繼承并創(chuàng)建 Fragment,然后在關鍵的生命周期方法中插入代碼(就和在處理 Activity 時一樣)。
其中一個區(qū)別是:創(chuàng)建 Fragment 時,必須重寫 onCreateView() 回調方法來定義布局。事實上,這是唯一一個為使 Fragment 運行起來需要重寫的回調方法。比如,下面是一個自定義布局的示例 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) {
// 拉伸該 Fragment 的布局
return inflater.inflate(R.layout.article_view, container, false);
}
}
和 Activity 一樣,當 Fragment 從 Activity 添加或者移除、或 Activity 生命周期發(fā)生變化時,Fragment 通過生命周期回調函數管理其狀態(tài)。例如,當 Activity 的 onPause() 被調用時,它內部所有 Fragment 的 onPause() 方法也會被觸發(fā)。
更多關于 Fragment 的聲明周期和回調方法,詳見 Fragments 開發(fā)指南.
用 XML 將 Fragment 添加到 Activity
Fragments 是可重用的、模塊化的 UI 組件。每個 Fragment 實例都必須與一個 FragmentActivity 關聯。我們可以在 Activity 的 XML 布局文件中逐個定義 Fragment 來實現這種關聯。
注: FragmentActivity 是 Support Library 提供的一種特殊 Activity,用于處理 API 11 版本以下的 Fragment。如果我們 APP 中的最低版本大于等于 11,則可以使用普通的 Activity。
以下是一個 XML 布局的例子:當屏幕被認為是 "large"(用目錄名稱中的 large 字符來區(qū)分)時,它在布局中增加了兩個 Fragment。
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>
提示: 更多關于不同屏幕尺寸創(chuàng)建不同布局的信息,請閱讀 兼容不同屏幕尺寸。
然后將這個布局文件用到 Activity 中。
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 庫,Activity 應該改為繼承自 AppCompatActivity,AppCompatActivity 是 FragmentActivity 的子類(更多關于這方面的內容,請閱讀 添加 App Bar)。
注: 當通過 XML 布局文件的方式將 Fragment 添加進 Activity 時,Fragment 是不能被動態(tài)移除的。如果想要在用戶交互的時候把 Fragment 切入與切出,必須在 Activity 啟動后,再將 Fragment 添加進 Activity。
使用Fragment創(chuàng)建動態(tài)UI
你可以在 Activity 運行時向其添加 Fragment,而不用使用 <fragment> 元素在布局文件中為 Activity 定義 Fragment。如果你打算在 Activity 運行周期內更改 Fragment,就必須這樣做。
要執(zhí)行添加或移除 Fragment 等事務,你必須使用 FragmentManager 創(chuàng)建一個 FragmentTransaction,后者可提供用于執(zhí)行添加、移除、替換以及其他 Fragment 事務的 API。
如果 Activity 中的 Fragment 可以移除和替換,你應在調用 Activity 的 onCreate() 方法期間為 Activity 添加初始 Fragment(s)。
在處理 Fragment(特別是在運行時添加的 Fragment)時,請謹記以下重要規(guī)則:必須在布局中為 Fragment 提供 View 容器,以便保存 Fragment 的布局。
要用一個 Fragment 替換另一個 Fragment,Activity 的布局中需要包含一個作為 Fragment 容器的空 FrameLayout。
請注意,該文件名與XML中布局文件的名稱相同,但布局目錄沒有 large 這一限定符。因此,此布局會在設備屏幕小于“l(fā)arge”的情況下使用,原因是尺寸較小的屏幕不適合同時顯示兩個 Fragment。
res/layout/news_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 中,用 Support Library API 調用 getSupportFragmentManager() 以獲取 FragmentManager,然后調用 beginTransaction() 創(chuàng)建 FragmentTransaction,然后調用 add() 添加 Fragment。
你可以使用同一個 FragmentTransaction 對 Activity 執(zhí)行多 Fragment 事務。當你準備好進行更改時,必須調用 commit()。
例如,下面介紹了如何為上述布局添加 Fragment:
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);
// 確認 Activity 使用的布局版本包含 fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// 不過,如果我們要從先前的狀態(tài)還原,則無需執(zhí)行任何操作而應返回,否則
// 就會得到重疊的 Fragment。
if (savedInstanceState != null) {
return;
}
// 創(chuàng)建一個要放入 Activity 布局中的新 Fragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// 如果此 Activity 是通過 Intent 發(fā)出的特殊指令來啟動的,
// 請將該 Intent 的 extras 以參數形式傳遞給該 Fragment
firstFragment.setArguments(getIntent().getExtras());
// 將該 Fragment 添加到“fragment_container” FrameLayout 中
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}
由于該 Fragment 已在運行時添加到 FrameLayout 容器中,而不是在 Activity 布局中通過 <fragment> 元素進行定義,因此該 Activity 可以移除和替換這個 Fragment。
用一個 Fragment 替換另一個 Fragment
替換 Fragment 的步驟與添加 Fragment 的步驟相似,但需要調用 replace() 方法,而非 add()。
請注意,當你執(zhí)行替換或移除 Fragment 等 Fragment 事務時,最好能讓用戶向后導航和“撤消”所做更改。要通過 Fragment 事務允許用戶向后導航,你必須調用 addToBackStack(),然后再執(zhí)行 FragmentTransaction。
注: 當你移除或替換 Fragment 并向返回堆棧添加事務時,已移除的 Fragment 會停止(而不是銷毀)。如果用戶向后導航,還原該 Fragment,它會重新啟動。如果你沒有向返回堆棧添加事務,那么該 Fragment 在移除或替換時就會被銷毀。
替換 Fragment 的示例:
// 創(chuàng)建 Fragment 并為其添加一個參數,用來指定應顯示的文章
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// 將 fragment_container View 中的內容替換為此 Fragment,
// 然后將該事務添加到返回堆棧,以便用戶可以向后導航
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// 執(zhí)行事務
transaction.commit();
addToBackStack() 方法可接受可選的字符串參數,來為事務指定獨一無二的名稱。除非你打算使用 FragmentManager.BackStackEntry API 執(zhí)行高級 Fragment 操作,否則無需使用此名稱。
Fragment之間的交互
定義接口
為了讓 Fragment 與包含它的 Activity 進行交互,可以在 Fragment 類中定義一個接口,并在 Activity 中實現。該 Fragment 在它的 onAttach() 方法生命周期中獲取該接口的實現,然后調用接口的方法,以便與 Activity 進行交互。(譯注:意即,若該 Fragment 中實現了 onAttach() 方法,則會被自動調用。)
以下是 Fragment 與 Activity 交互的例子:
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// 容器 Activity 必須實現該接口
// (譯注:“容器 Activity”意即“包含該 Fragment 的 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()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
現在 Fragment 可以通過調用 mCallback(OnHeadlineSelectedListener 接口的實例)的 onArticleSelected() 方法(也可以是其它方法)與 Activity 進行消息傳遞。
例如,當用戶點擊列表條目時,Fragment 中的下面的方法將被調用。Fragment 用回調接口將事件傳遞給父 Activity。
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// 向宿主 Activity 傳送事件
mCallback.onArticleSelected(position);
}
實現接口
為了接收回調事件,宿主 Activity 必須實現在 Fragment 中定義的接口。
例如,下面的 Activity 實現了上面例子中的接口。
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// 用戶從 HeadlinesFragment 選擇了一篇文章的標題
// 在這里做點什么,以顯示該文章
}
}
向 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) {
// 用戶從 HeadlinesFragment 選擇了一篇文章的標題
// 在這里做點什么,以顯示該文章
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// 若 articleFrag 有效,則表示我們正在處理兩格布局(two-pane layout)……
// 調用 ArticleFragment 的方法,以更新其內容
articleFrag.updateArticleView(position);
} else {
// 否則,我們正在處理單格布局(one-pane layout)。此時需要 swap frags...
// 創(chuàng)建 Fragment,向其傳遞包含被選文章的參數
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// 無論 fragment_container 視圖里是什么,用該 Fragment 替換它。并將
// 該事務添加至回棧,以便用戶可以往回導航(譯注:回棧,即 Back Stack。
// 在有多個 Activity 的 APP 中,將這些 Activity 按創(chuàng)建次序組織起來的
// 棧,稱為回棧)
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// 執(zhí)行事務
transaction.commit();
}
}
}