前言
上一篇blog(處女男學Android(八)---Fragment初體驗之實作Tab導航)記錄了fragment的基本概念和基本的使用方法,本篇将逐漸深入記錄關于fragment的幾個重要知識點,包括:fragment的生命周期、fragment的back stack(回退棧)等等,下面就從fragmeng的生命周期說起。
一、fragment生命周期概述
與Activity類似,Fragment作為一個容器也必定有它自己的生命周期,如果能熟練掌握一個fragment從建立到銷毀過程中的每一個方法,以及它們的調用時機,那麼我們将可以更好的去管理一個fragment,比如我們可以根據需求,在不同的狀态中做一些有用的事情,下面看一下官方給出的流程圖:
下面對這11個方法做一下大緻的解釋。
onAttach(Activity activity)
---Called when the fragment has been associated with the activity (the
Activity
is passed in here).
---當該fragment被添加到Activity時回調,該方法隻會被調用一次。
onCreat(Bundle savedInstanceState)
---建立fragment時被回調。該方法隻會調用一次。
onCreatView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState)
---Called to create the view hierarchy associated with the fragment.
---每次建立、繪制該fragment的View元件時回調該方法,fragment将會顯示該方法的View元件。
onActivityCreated(Bundle savedInstanceState)
---Called when the activity's
onCreate()
method has returned.
---當fragment所在的Activity被建立完成後調用該方法。
onStart()
---啟動fragment時被回調。
onResume()
---恢複fragment時被回調,onStart()方法後一定會回調onResume()方法。
onPause()
---暫停fragment時被回調。
onStop()
---停止fragment時被回調。
onDestroyView()
---Called when the view hierarchy associated with the fragment is being removed.
---銷毀該fragment所包含的View元件時調用。
onDestory()
---銷毀fragment時被回調,該方法隻會被調用一次。
onDetach()
---Called when the fragment is being disassociated from the activity.
---當該fragment從Activity中被删除、被替換完成時回調該方法,onDestory()方法後一定會回調onDetach()方法。該方法隻會被調用一次。
其實學習生命周期最好的方法還是在程式中通過列印語句來觀察每個階段的狀态和順序,下面就通過一個簡單的例子來看一下一個fragment完整的生命周期的過程。
二、一個簡單的例子
Layout代碼(fragment.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="#FFCC00"
android:gravity="center"
android:text="TextView2 in Fragment" />
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:entries="@array/heros" >
</ListView>
</LinearLayout>
MyFragment代碼:
package com.example.fragmentlifecycledemo;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MyFragment extends Fragment {
private final String TAG = "--MyFragment--> ";
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
System.out.println(TAG + "onAttach");
super.onAttach(activity);
}
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
System.out.println(TAG + "onCreate");
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
System.out.println(TAG + "onCreateView");
View view = inflater.inflate(R.layout.fragment, null);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
System.out.println(TAG + "onActivityCreated");
super.onActivityCreated(savedInstanceState);
}
@Override
public void onStart() {
// TODO Auto-generated method stub
System.out.println(TAG + "onStart");
super.onStart();
}
@Override
public void onResume() {
// TODO Auto-generated method stub
System.out.println(TAG + "onResume");
super.onResume();
}
@Override
public void onPause() {
// TODO Auto-generated method stub
System.out.println(TAG + "onPause");
super.onPause();
}
@Override
public void onStop() {
// TODO Auto-generated method stub
System.out.println(TAG + "onStop");
super.onStop();
}
@Override
public void onDestroyView() {
// TODO Auto-generated method stub
System.out.println(TAG + "onDestroyView");
super.onDestroyView();
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
System.out.println(TAG + "onDestroy");
super.onDestroy();
}
@Override
public void onDetach() {
// TODO Auto-generated method stub
System.out.println(TAG + "onDetach");
super.onDetach();
}
}
可以看到上面的代碼是fragment類和它所要加載的布局檔案,并在fragment中聲明了生命周期中的每個方法以及列印語句。fragment的布局檔案很簡單,隻有一個TextView和一個固定内容的ListView。下面看看Activity的代碼和布局檔案。
Layout代碼(activity_main.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.fragmentlifecycledemo.MainActivity" >
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#FFDDFF"
android:gravity="center"
android:height="0dp"
android:text="TextView1 in MainActivity" />
<FrameLayout
android:id="@+id/lt_frame"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3" >
</FrameLayout>
</LinearLayout>
MainActivity代碼:
package com.example.fragmentlifecycledemo;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
public class MainActivity extends Activity {
private final String TAG = "--MainActivity---> ";
private FragmentManager manager;
private FragmentTransaction transaction;
@Override
protected void onCreate(Bundle savedInstanceState) {
System.out.println(TAG + " onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manager = getFragmentManager();
transaction = manager.beginTransaction();
transaction.add(R.id.lt_frame, new MyFragment());
transaction.commit();
}
@Override
protected void onStart() {
// TODO Auto-generated method stub
System.out.println(TAG + " onStart");
super.onStart();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
System.out.println(TAG + " onResume");
super.onResume();
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
System.out.println(TAG + " onPause");
super.onPause();
}
@Override
protected void onStop() {
// TODO Auto-generated method stub
System.out.println(TAG + " onStop");
super.onStop();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
System.out.println(TAG + " onDestroy");
super.onDestroy();
}
}
類似的,同樣聲明了Activity生命周期中的所有方法以及列印語句,并且當Activity建立時将MyFragment動态添加到activity_main.xml中的FrameLayout布局檔案裡,可以看到這個布局檔案是空的,是以它就相當于用來裝fragment的容器了。
下面看一下運作效果和LogCat控制台的列印結果:
可以看到fragment正常被加載到了Activity的FrameLayout中了,那麼現在看看LogCat:
可以看到首先是Activity被建立,緊接着fragment與Activity綁定(onAttach),然後fragment被建立(onCreat),fragment的視圖被建立(onCreatView),fragment所在的Activity的onCreate()方法已經被傳回(onActivityCreated),最後Actvity啟動,fragment啟動,Activity處于運作狀态,fragment處于運作狀态。
當我點傳回鍵之後,再看一下運作效果和LogCat控制台的列印結果:
可以看到已經點選傳回鍵退出應用了,那麼現在看看LogCat:
可以看到退出的時候,首先是fragment暫停、Activity暫停,然後fragment停止、Activity停止,然後銷毀fragment的視圖(onDestoryView,與onCreatView相對應)、與之前綁定的Activity解除關聯(onDetach,與onAttach相對應),最後是Activity的銷毀。
通過上面的分析我們可以證明:
fragment的生命周期确實是伴随着Activity的生命周期變化的,并且它是依附于Activity的。先建立Activity,再建立fragment,先銷毀fragment,再銷毀Activity,這種層次結構的設計也是非常合理的,而且和“棧”的結構有些相似,即:“後進先出”。下面将記錄fragment的另一個重要的概念,即“回退棧(back stack)”。
三、回退棧(back stack)
其實回退棧很簡單,fragment的回退棧和Activity的回退棧是類似的,就是如果把一個fragment事務添加到回退棧,那麼點傳回按鈕的時候也就可以看到上一次儲存的fragment,不至于直接退出Activity。也就是通過這個方法來将fragment事務添加到回退棧的:
transaction.addToBackStack(String name);
下面通過例子來看看回退棧的應用,順便再說說我在學習回退棧的同時學到的其它相關知識,對于我來說還是很有意義的。需求是這樣的:同樣是上面的英雄清單,當我點選ListView的某一項時,下面的fragment就替換成一個新的界面,這個界面有一個文本框,需要輸入目前項的英雄名稱,還有一個确定按鈕,點選确定時,如果輸入的和目前項的英雄名一緻,就彈出土司提示正确并附加英雄名,如果輸入的和目前項的英雄名不一緻,也彈出土司提示錯誤,但不顯示英雄名。重點是,我在二級界面點選傳回的時候,要跳回ListView的界面,下面貼上我重構後的代碼。
Layout代碼(f1.xml-->二級界面的布局檔案):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_textView_f1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.7"
android:background="#F7F7F7"
android:gravity="center"
android:text="我是HERO,我的名字是?"
android:textSize="20sp" />
<EditText
android:id="@+id/et_editText_e1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.15"
android:inputType="textPersonName"
android:hint="請輸入英雄名" />
<Button
android:id="@+id/btn_button_b1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.15"
android:text="确定" />
</LinearLayout>
MyFragment代碼(隻重構了onCreatView方法):
package com.example.fragmentlifecycledemo;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
public class MyFragment extends Fragment {
private final String TAG = "--MyFragment--> ";
private ListView listView1;
private FragmentManager manager;
private FragmentTransaction transaction;
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
System.out.println(TAG + "onAttach");
super.onAttach(activity);
}
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
System.out.println(TAG + "onCreate");
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
System.out.println(TAG + "onCreateView");
View view = inflater.inflate(R.layout.fragment, container, false);
listView1 = (ListView) view.findViewById(R.id.iv_listView_1);
manager = getFragmentManager();
transaction = manager.beginTransaction();
listView1.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// 建立二級Fragment對象
MySubFragment msf = new MySubFragment();
// 擷取目前項的TextView
TextView tv1 = (TextView) view;
// 通過Bundle對象傳遞資料
Bundle bundle = new Bundle();
bundle.putCharSequence("heroName", tv1.getText().toString());
msf.setArguments(bundle);
// 當點選Item時将ListView替換成二級Fragment
transaction.replace(R.id.lt_frame, msf);
// 将事務添加到回退棧使得可以傳回到上一級
transaction.addToBackStack(null);
transaction.commit();
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
System.out.println(TAG + "onActivityCreated");
super.onActivityCreated(savedInstanceState);
}
@Override
public void onStart() {
// TODO Auto-generated method stub
System.out.println(TAG + "onStart");
super.onStart();
}
@Override
public void onResume() {
// TODO Auto-generated method stub
System.out.println(TAG + "onResume");
super.onResume();
}
@Override
public void onPause() {
// TODO Auto-generated method stub
System.out.println(TAG + "onPause");
super.onPause();
}
@Override
public void onStop() {
// TODO Auto-generated method stub
System.out.println(TAG + "onStop");
super.onStop();
}
@Override
public void onDestroyView() {
// TODO Auto-generated method stub
System.out.println(TAG + "onDestroyView");
super.onDestroyView();
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
System.out.println(TAG + "onDestroy");
super.onDestroy();
}
@Override
public void onDetach() {
// TODO Auto-generated method stub
System.out.println(TAG + "onDetach");
super.onDetach();
}
}
二級Fragment代碼:
package com.example.fragmentlifecycledemo;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MySubFragment extends Fragment implements OnClickListener {
private Button mButton;
private EditText editText1;
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View v = inflater.inflate(R.layout.f1, container, false);
editText1 = (EditText) v.findViewById(R.id.et_editText_e1);
mButton = (Button) v.findViewById(R.id.btn_button_b1);
mButton.setOnClickListener(this);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
@Override
public void onClick(View v) {
// 根據鍵值擷取存放的英雄名字
String name = (String) getArguments().getCharSequence("heroName");
// 擷取使用者輸入的文本
String input = editText1.getText().toString();
if (name.equals(input)) {
Toast.makeText(getActivity(), "回答正确!我的名字是:" + name,
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), "回答錯誤!我不告訴你我的名字!", Toast.LENGTH_LONG)
.show();
}
}
}
下面再看一下重構後的運作效果:
可以看到點選Item跳轉之後,再點選傳回按鈕又可以成功傳回到ListView,而不會直接退出程式,這就是fragment回退棧的作用了,其實在重構這些代碼時我思考更多的是如何将ListView中Item的值傳到二級fragment中,之前的做法是:直接在MyFragment的onItemClick方法中得到Item的View和二級Fragment的View,通過xxx.getText(“”+xxx.setText)這種方式去做,結果一直報錯,後來整理了思路,結合上面學習的生命周期,我了解了fragment的View都是在各自的onCreatView方法中完成的,這樣把代碼分開才最終解決了問題。
四、結合回退棧再看生命周期
加了回退棧之後,我們有必要看看點選傳回按鈕之後生命周期的變化,下面就貼上從桌面進入APP、點選一項Item、點選傳回、再點選傳回退出APP的完整的生命周期輸出資訊。
注意紅色方框一級Fragment跳到二級Fragment的時候隻調用的onDestoryView(),并沒有調用onDestory,而點選傳回的時候,自然就直接調用onCreatView,這也正是回退棧的作用了。
五、總結
其實我想重構這個例子隻是測試一下回退棧,但設計需求的同時又想把ListView中每個Item項的文本資訊傳到跳轉之後的二級fragment中,是以我先理清思路,發現問題所在,并仔細思考了如何傳遞資料、在哪裡儲存資料又應該在哪裡讀取資料,最後完成需求。這樣在無形之中又學到了更多,我也明白了學一個知識的時候不能僅僅滿足于表面,往往在你發掘需求的同時就又會發現一些更有意思的事情,本篇先記錄到這裡,以後還要繼續加油!更加努力!