Pro Android學習筆記(三九):Fragment(4):基礎小例子-續
Step 3:實現簡介顯示類DetailFragment
在Activity的佈局xml中,對DetailFragment並沒有指定class屬性,故在setContentView()中不會自動呼叫該類,而是通過編寫showDetail(int index)來呼叫,該函式的具體編碼以後在描述,我們先來看看DetailFragment類的實現。
建立例項
在TitleFragment中,通過在xml中指定class屬性來呼叫fragment。而對於右邊部分,則是定義了FrameLayout容器,因此需要通過程式碼來建立例項。下面是通用的建議程式碼,通過靜態函式來建立例項,並設定引數。當fragment被儲存並重構時,系統回撥用預設的建構函式,並接著重新獲取初始化引數。
public class DetailFragment extends Fragment{
//以靜態函式的方式,通過指定書名序號,來獲取例項,並將書名序號通過setArguments()方式設為給fragment的引數
public static DetailFragment newInstance (int index){
//1、建立fragment例項
DetailFragment df = new DetailFragment();
//2、設定fragment的引數,在fragment中可以通過getArguments()來獲取
args.putInt("index", index);
df.setArguments(args);
return df;
}
//通過bundle方式攜帶書名序號,來獲取例項
public static DetailFragment newInstance (Bundle bundle){
int index = bundle.getInt("index");
return newInstance(index);
}
… 略 …
可以通過setArguments()來設定引數,對於fragment的生命週期而言,必須在fragment完成與activity關聯之前設定引數。即必須在onAttach(之前進行,即在構造階段或者onInflate。不是每個fragment都有onInflate狀態,例如DetailFragment,不是通過xml的<fragment>進行構造,故沒有onInflate狀態。
編寫所需的生命週期程式碼
實際上並不需要對每個生命週期都進行程式碼編寫。由於在xml中沒有指定class,不是activity中通過setContentView()來呼叫,因此沒有經歷onInflate()狀態,而是從onAttach()開始。我們需要的是為fragment建立view佈局,並具體填入資訊,可以在onCreateView()和onActivityCreated()中實現。
public class DetailFragment extends Fragment{
private int mIndex = 0;
... 略 …
@Override //在生命週期的早期onCreate(),獲取引數(書名序號)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIndex = getArguments().getInt("index",0);
//在建立例項過程中將index設定為引數,可以在任何生命週期中獲取,此處只為了使用方面進行提取。為何我們不直接newInstance()中將mIndex=index,而是設定成為引數,我們需要考慮到fragment recreate的情況,例如螢幕轉向。此時,系統將呼叫預設的建構函式,不會執行newInstance()程式碼,但在onCreate()我們仍可從引數中獲取index。
}
public int getShowIndex(){
return mIndex;
}
@Override //在onCreateView中建立view層級,並返回。LayoutInflater可用於inflate佈局。如果ViewGroup非空,則可以通過infalte()來獲取佈局,如果為null,說明沒有viewGroup容器可以關聯,在此所設定的,不會在UI中真正顯示。
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
if(container == null)
return null;
//1、根據xml的佈局檔案建立view層級並將之返回。注意:第3個引數為false,不要將view和viewGroup進行關聯,系統會自動進行,否則會出現異常。
View v = inflater.inflate(R.layout.details, container,false);
//2、設定層級中的view
TextView tv = (TextView)v.findViewById(R.id.text1);
tv.setText(BooksInfo.DIALOGUE[mIndex]);
return v;
}
}
在onCreateView中,返回的是View hierarchy,這和引數ViewGroup contianer是什麼關係。其實這兩者都是容器。一個表示fragment所佔的空間的parent容器,就是XML檔案中的FrameLayout;一個表示填充這個空間的child容器,圖示如下:
這個用於填充FrameLayout的子容器佈局很簡單,下面為details.xml檔案。注意,我們並不需要在程式碼中將子容器放入FrameLayout中,系統會自動完成,如果我們將兩者關聯,反而會出現異常。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout …. >
<ScrollView android:id="@+id/scroller" android:layout_width… >
<TextView android:id="@+id/text1" android:layout_width… />
</ScrollView>
</LinearLayout>
Step 4:實現showDetail(int index),如何管理fragment
fragment的切換
右邊的fragment在使用者點選不同書目時進行切換。一個fragment必須在某個view容器中,即onCreateView中的ViewGroup。在XML中,我們設定了FrameLayout作為fragment的容器,因為FrameLayout比其他的簡單。在容器中可以進行fragment的切換。通過FragmentTranscation可以替換當前UI。FragmentTransaction是Fragment操作的API介面集合。相關程式碼如下:
public class FragmentBasicTest extends Activity{
... 略 …
public void showDetails(int index){
//【1】、獲取當前與FrameLayout相關的fragment的物件。對fragment進行處理需要通過fragment管理器,FragmentManager可以通過 Activity的getFragmentManager()和Fragment的getFragmentManager()來獲取,通過管理器,可以獲得fragement transaction,根據id,tag獲取fragment。
DetailFragment detail = (DetailFragment)getFragmentManager().findFragmentById(R.id.details);
//【2】、如果fragment物件不存在(第一次建立)或者書目變更,將進行切換
if(detail == null || detail.getShowIndex() != index ){
// 2.1)建立先的fragemnt物件
detail = DetailFragment.newInstance(index);
// 2.2)beginTransaction()有點詞不達意,並不是說要獲取第一個Transaction,Transaction沒有分第幾個,它是進行Fragment操作的API介面集合。beginTransaction()是說明要通過Fragment管理器對fragment進行編輯,編輯完後通過commit()進行確認。
FragmentTransaction ft = getFragmentManager().beginTransaction();
// 編輯的內容是replace():在FrameLayout中替換現有的UI。具體為replace(int containerId, Fragment fragment<, String tag>); tag是fragment的可選屬性,不設定即為null。
ft.replace(R.id.details, detail);
ft.commit();
}
}
}
到此,我們已經完成了對fragment的基礎小例子。
回退堆疊back stack
我們點選返回鍵,將退出activity。但是有時我們會希望,右邊的fragment能退回到上一次點選的書目簡介。在編寫相關程式碼之前,我們跟蹤一下小例子目前右邊fragment的生命週期。
利用FragmentTrasaction,將新的fragment物件加入back stack中,當用戶按回退鍵時,從back stack中pop出fragment物件,實現回退效果。back stack由fragment管理器管理,程式碼如下:
public void showDetails(int index){
DetailFragment detail = (DetailFragment)getFragmentManager().findFragmentById(R.id.details);
if(detail == null){
addFirstFragment(index);
}else if(detail.getShowIndex() != index ){
addFragmentToStack(index);
}
}
private void addFirstFragment(int index){
DetailFragment detail = DetailFragment.newInstance(index);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.details, detail);
ft.commit();
}
private void addFragmentToStack(int index){
DetailFragment detail = DetailFragment.newInstance(index);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.details, detail);
ft.addToBackStack(null); //加入回退堆疊,將原有(準備被替換)的fragment加入堆疊。經過跟蹤,貌似也將新的fragment進行儲存,但是並不會進行回退顯示,回退時進行Destroy,除非下一次,作為原有fragment加入堆疊。(這裡的說法有些羅嗦和含混,如果我們考慮每次到加入回退堆疊可以處理,但是如果某次加入,某次不加入,跟蹤狀態,就很清楚)
ft.commit();
}
我們同樣跟蹤一下現在小例子的執行情況。
如果我們不區分是否第一次,均使用addFragmentToStack,即在第一次,也執行ft.addToBackStack(null)。將原有的(準備被替換)的fragment加入堆疊,即將null加入堆疊,在回退時,最後增加回退一步,回退到一個空白的fragment,非吾等所期。
按返回鍵,fragment回退,仍然在同一個activity中,直至回退堆疊為空。