使用Fragment建立動態UI
爲了在Android上爲用戶提供動態的、多窗口的交互體驗,我們需要將UI組件和Activity操作封裝成模塊進行使用,使得我們可以在activity中對這些模塊進行切入切出操作。可以用Fragment來創建這些模塊,Fragment就像一個嵌套的activity,擁有自己的佈局(layout)並管理自己的生命週期。接收自己的輸入事件,可以在acvitity運行過程中添加或者移除(有點像"子activity",可以在不同的activity裏面重複使用)。一個fragment定義了自己的佈局後,它可以在activity中與其他的fragment生成不同的組合,從而爲不同的屏幕尺寸生成不同的佈局(一個小的屏幕一次也許只能一個fragment,大的屏幕則可以顯示更多)。
一 . 創建一個Fragment類
創建一個fragment,首先需要繼承Fragment類,然後在關鍵的生命週期方法中插入APP的邏輯,就像Activity一樣。其中一個區別是當創建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(LayoutInflaterinflater, ViewGroup container,
Bundle savedInstanceState){
// Inflate the layout for this fragment
return inflater.inflate(R.layout.article_view,container,false);
}
}
就像activity一樣,當fragment從activity添加或者移除、當activity生命週期發生變化時,fragment通過生命週期回調函數管理其狀態。例如,當activity的onPause()被調用時,它裏面的所有fragment的onPause()方法也會被觸發。
二 .用XML將Fragment添加到Activity
fragments是可重用的,模塊化的UI組件,每個Fragment的實例都必須與一個FragmentActivity關聯。我們可以在activity的XML佈局文件中定義每一個fragment來實現這種關聯。FragmentActivity是Support Library提供的一個特殊activity ,用於處理API11版本以下的fragment。如果我們APP中的最低版本大於等於11,則可以使用普通的Activity。
<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:id="@+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment
android:id="@+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
然後再將這個佈局添加到Activity中
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
@Override
public void onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
}
}
如果用的是 v7 appcompat library,activity應該改爲繼承ActionBarActivity,ActionBarActivity是FragmentActivity的一個子類。當通過XML佈局文件的方式將Fragment添加進activity時,Fragment是不能被動態移除的。如果想要在用戶交互的時候把fragment切入與切出,必須在activity啓動後,再將fragment添加進activity。
三 .建立靈活動態的UI
比如,一個手持設備可能適合一次只有一個fragment的單面板用戶交互。而在更大屏幕尺寸的平板電腦上,我們可能更想要兩個fragment並排在一起,用來向用戶展示更多信息。兩個fragments,在同一個activity不同屏幕尺寸中用不同的配置來展示。在大屏幕上,兩個fragment被並排放置,在手持設備上,一次只放置一個fragment,所以在用戶導航中,兩個fragment必須進行替換。FragmentManager類爲在activity運行時對fragment進行添加,移除,替換等操作提供了方法,來實現動態的用戶體驗
四 .在Activity運行時動態添加Fragment
比起用<fragment>標籤在activity的佈局文件中定義fragment,我們還可以在activity運行時動態添加fragment,如果打算在activity的生命週期內替換fragment,這是必須的。爲了執行fragment的增加或者移除操作,必須通過FragmentManager創建一個FragmentTransaction對象,FragmentTransaction提供了用來增加、移除、替換以及其它一些操作的APIS。如果我們的activity允許fragment移除或者替換,我們應該在activity的onCreate()方法中添加初始化fragment(s)。
運用fragment(尤其是那些在運行時添加的)的一個很重要的規則就是在佈局中必須有一個容器View,fragment的layout將會放在這個view裏面。在activity中,用Support Library APIs調用 getSupportFragmentManager()方法獲取FragmentManager對象,然後調用 beginTransaction() 方法創建一個FragmentTransaction對象,然後調用add()方法添加一個fragment.可以使用同一個 FragmentTransaction進行多次fragment事務。完成這些變化操作,準備開始執行改變時,必須調用commit()方法。
下例顯示瞭如何添加一個fragment到之前的layout中
importandroid.os.Bundle;
importandroid.support.v4.app.FragmentActivity;
publicclassMainActivityextendsFragmentActivity {
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
if (findViewById(R.id.fragment_container) !=null) {
if (savedInstanceState !=null) {
return;
}
HeadlinesFragment firstFragment =newHeadlinesFragment();
firstFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}
五 .替換Fragment
替換fragment的過程類似於添加過程,只需要將add()方法替換爲 replace()方法。記住在執行fragment事務時,如移除或者替換,我們經常要適當地讓用戶可以向後導航與"撤銷"這次改變。爲了讓用戶向後導航fragment事務,我們必須在FragmentTransaction提交前調用addToBackStack()方法。
Note:當移除或者替換一個fragment並把它放入返回棧中時,被移除的fragment的生命週期是stopped(不是destoryed).當用戶返回重新恢復這個fragment,它的生命週期是restarts。如果沒有把fragment放入返回棧中,那麼當它被移除或者替換時,其生命週期是destoryed。
下面是一個fragment替換的例子
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
addToBackStack()方法提供了一個可選的String參數爲事務指定了一個唯一的名字。除非打算用FragmentManager.BackStackEntry APIs來進行一些高級的fragments操作,這個名字不是必須的。
六 . Frgament 之間的交互
通常fragment之間可能會需要交互,比如基於用戶事件改變fragment的內容。所有fragment之間的交互需要通過他們關聯的activity,兩個fragment之間不應該直接交互。
1 . 定義一個接口
爲了讓Fragment與activity交互可以在Fragment中定義一個接口並在Activity中實現,Fragment在它們的生命週期的onAttach()方法中獲取接口的實現然後調用接口的方法與Activity交互。
public class TestFragment extends ListFragment {
OnHeadlineSelected mCallBack;
Int position;
public interface OnHeadlineSelected{
public void onArticleSelected(int position);
}
public void onAttach(Activity activity){
super.onAttach(activity);
mCallBack = (OnHeadlineSelected)activity;
}
public void getPosition(){
this.position = position;
}
}
現在Fragment就可以通過OnHeadlineSelectedListener 接口實例mCallBack中的OnArticleSelected()方法與Activity傳遞消息。
比如在Fragment中點擊ListView的一個條目時調用OnArticleSelected()方法Fragment通過回調接口來傳遞事件給父Activity。
2 . 在Activity中實現接口
public class TestActivity extends Activity implements TestFragment.OnHeadlineSelected{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onArticleSelected(int position) {
/**
* doing something
*/
}
}
3 . 傳消息給Fragment
在Activity中獲取Fragment實例然後直接調用getPosition()方法向Fragment傳遞消息。
假定上面的Activity中包含另外一個Fragment這個Fragment用來展示從上面的回調方法中返回的指定的數據,在這種情況下Activity可以把回調方法中接收到的消息傳遞給這個展示數據的Fragment。
public class TestActivity extends FragmentActivity implements TestFragment.OnHeadlineSelected{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment);
TestFragment fragment = (TestFragment)getSupportFragmentManager().findFragmentById(R.id.fragment);
if(fragment != null) {
fragment.setPosition(0);
} else {
TestFragment testFragment = new TestFragment();
Bundle bundle = new Bundle();
bundle.putInt("position", 0);
testFragment.setArguments(bundle);
android.support.v4.app.FragmentTransaction transaction =
getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment, testFragment);
transaction.commit();
}
}
@Override
public void onArticleSelected(int position) {
/**
* doing something
*/
}
}