Android入門——Fragment詳解之基本概念與用法(一)
引言
Android在3.0中引入了Fragments的概念,其目的是用在大螢幕裝置上–例如平板電腦上,支援更加動態和靈活的UI設計。平板電腦的螢幕要比手機的大得多,有更多的空間來放更多的UI元件,並且這些元件之間會產生更多的互動。Fragment允許這樣的一種設計,而不需要你親自來管理 Viewhierarchy的複雜變化。 通過將Activity的佈局分散到Fragment中, 你可以在執行時修改Activity的外觀,並在由Activity管理的back stack中儲存那些變化。
一、Fragment概述
Android開發過程中,我們可以把Fragment想成Activity中的模組,這個模組有自己的佈局,有自己的生命週期,單獨處理自己的輸入,在Activity執行的時候可以載入或者移除Fragment模組。從一定程度上來說,與Web開發中的iframe和DIV佈局思想有點類似。例像網易新聞、易車等應用程式將一個fragment放在左邊顯示文章列表,在右邊用另一個Fragment來顯示一篇文章——兩個Fragment在同一個Activity中並排著,並且每個Fragment都有其自己的生命週期回撥方法序列用以處理各自的使用者輸入事件。因此,使用者可以在同一個Activity中選擇和閱讀文章,而不是在一個Activity中選擇,在另一個Activity中閱讀。這樣設計的好處就是當程式執行在平板尺寸螢幕裝置上時,可以在Activity A中嵌入兩個Fragment。但是,當執行在手機尺寸螢幕上,就沒有足夠的空間容納兩個Fragment了,因此Activity A只能引用包含文章列表的Fragment,在當用戶選擇一篇文章時,可以啟動Activity B,它包含了用來閱讀文章的第二個fragment。這樣,應用程式通過以不同組合的複用fragments支援了平板電腦和手機,再比如官方的這個例子:
二、Fragment的生命週期
因為Fragment必須嵌入在Acitivity中使用,所以Fragment的生命週期和它所在的Activity是密切相關的。如果Activity是暫停狀態,其中所有的Fragment都是暫停狀態;如果Activity是stop狀態,這個Activity中所有的Fragment都不能被啟動;如果Activity被銷燬,那麼它其中的所有Fragment都會被銷燬。但是,當Activity在活動狀態,可以獨立控制Fragment的狀態,比如加上或者移除Fragment。當這樣進行fragment transaction(轉換)的時候,可以把fragment放入Activity的back stack中,這樣使用者就可以進行返回操作。
方法 | 功能說明 |
---|---|
onAttach | 在Fragment與Activity關聯之後被呼叫。雖然初始化Fragment引數可以通過getArguments()獲得,但當Fragment附加到Activity之後,就無法再呼叫setArguments()。so除了在最開始時,其它時間都無法向初始化引數新增內容 |
onCreate | Fragment初次建立時呼叫。但此刻Activity還沒有建立完成,因為我們的Fragment也是Activity建立的一部分。所以當你嘗試在在這無法獲取Activity的一些資源 |
onCreateView | 主要用於建立Fragment自身的UI,可用使用LayoutInflater物件的inflater()方法把佈局xml對映為view,所以返回值必須是Fragment自身UI對應的view,但如果fragment沒有使用者介面可以返回null |
onActivityCreated | 在Activity的OnCreate()結束後,會呼叫此方法。即此時Fragment和Activity均已建立完畢。所以我們可以在這個方法裡使用Activity的所有資源 |
onStart | 當Fragment對使用者就是可見時,但使用者還未開始與Fragment互動。因為是與Activity的OnStart()繫結的。寫在Activity的OnStart()中處理的邏輯,換成用Fragment來實現時,依然可以放在OnStart()中來處理。 |
onResume | 當Fragment對使用者可見並且正在執行時呼叫。即Fragment與使用者互動之前的最後一個回撥。與onStart類似,也是與Activity的OnResume是相互繫結的,它依賴於包含它的Activity的Activity.onResume。當OnResume()執行完畢之後,就可以正式與使用者互動了 |
onPause | 與Activity的OnPause()繫結,意義也一樣。當用戶離開Fragment時第一個呼叫這個方法(但並不總意味著Fragment將被銷燬)。按照經驗,當用戶結束會話之前,我們可以在這提交一些變化 |
onStop | 與Activity繫結,已停止的Fragment可以直接返回到OnStart()回撥,然後呼叫OnResume()。 |
onDestroyView | 當Fragment將被結束或者儲存,那麼下一個回撥將是onDestoryView(),將會刪除在onCreateView建立的檢視。下次這個Fragment若要顯示,將會重新建立新檢視。這會在onStop之後和onDestroy之前呼叫。經測試這個方法的呼叫同onCreateView是否返回非null檢視無關。它會在的在這個檢視狀態被儲存之後以及它被它的父Activity回收之前呼叫 |
onDestory | 當Fragment不再使用時被呼叫。但即使呼叫了onDestroy()階段,仍可以從其依附的父Activity中找到,因為它還沒有Detach。 |
onDetach | 當Fragment和Activity分離的時候呼叫,Fragment就不再與Activity相繫結,它也不再擁有檢視層次結構,它的所有資源都將被釋放 |
一旦Activity進入resumed狀態(也就是running狀態),我們就可以自由地新增和刪除Fragment了。因此,只有當Activity在resumed狀態時,Fragment的生命週期才能獨立的運轉,其它時候是依賴於Activity的生命週期變化的。
三、Fragment生命週期分析
1. 當建立Fragment時,它會經歷以下狀態.
onAttach()——>onCreate()——>onCreateView()——>
onActivityCreated()——>
2. 當Fragment對使用者可見的:
onStart()——>onResume()——>
3. 當Fragment進入“後臺模式”的時:
onPause()——>onStop()——>
4. 當Fragment被銷燬了(或者持有它的Activity被銷燬了)時:
onPause()——>onStop()——>onDestroyView()——>onDestroy() ——>onDetach()——>
5. 與Activity一樣,在以下的狀態中,可以使用Bundle物件儲存一個Fragment的物件。
onCreate()——>onCreateView()——>onActivityCreated()——>
四、Fragment的狀態
- 執行狀態:但Fragment處於前臺,使用者可見,可獲取焦點
- 暫停狀態:其他Activity位於前臺,該Fragment依然可以見,但不能獲取焦點
- 停止狀態:該Fragment完全不可見,失去焦點
- 銷燬狀態:該Fragment被完全刪除或所依附的Activity被結束。
五、Fragment的基本操作和使用
要使用Fragment,可通過繼承Fragment類,複寫相關方法來實現載入Fragment自身的UI並初始化Fragment相關變數,控制Fragment與Activity的互動,通常都需要指定一個UI,但也可以為Activity建立一個沒有UI只提供後臺行為的Fragment。
1、建立Fragment分為:Java程式碼動態建立與xml靜態建立
1.1、xml靜態建立
- 首先在依附的Activity的佈局檔案中定義fragment節點(其中fragment節點裡的id和name必填,name對應著我們自定義的Fragment的子類的完整類名,還有一個tag屬性也應該是唯一的可以通過findFragmentByTag(tag)獲取對應的Fragement)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:text="Hello Fragment!" android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TitleStyle"/>
<!--name:對應著我們繼承Fragment的子Fragment類-->
<fragment
android:id="@+id/id_fragment_main"
android:name="com.crazymo.fragmentsdemo.MainFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
- 再繼承Fragment重寫相關方法
package com.crazymo.fragmentsdemo;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by cmo on 16-4-19.
*/
public class MainFragment extends Fragment {
private final String TAG="FramentDemo";
@Override
public void onAttach(Context context) {
Log.d(TAG, "onAttach:剛剛與Activity對接");
super.onAttach(context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate:初始化Fragment物件");
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView:初始化Fragment的UI");
return inflater.inflate(R.layout.fragment_main, container, false);//引數依次為:佈局id,依附的容器
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated:Activity和Fragment都已經建立好了");
super.onActivityCreated(savedInstanceState);
}
@Override
public void onStart() {
Log.d(TAG, "onStart:Fragment變為可見,待互動");
super.onStart();
}
@Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
}
@Override
public void onPause() {
Log.d(TAG, "onPause:與Activity的OnPause繫結");
super.onPause();
}
@Override
public void onStop() {
Log.d(TAG, "onStop:與Activity的OnStop繫結");
super.onStop();
}
@Override
public void onDestroyView() {
Log.d(TAG, "onDestroyView:Fragment即將被儲存或者刪除");
super.onDestroyView();
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy:還與Activity藕斷絲連中可以在Activity中找到");
super.onDestroy();
}
@Override
public void onDetach() {
Log.d(TAG, "onDetach:徹底和Activity分手了");
super.onDetach();
}
}
- 然後再定義Fragment自身的佈局xml
<!--fragment_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TitleStyle"
android:text="Fragment詳解(一)"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
- 接著在onCreatedView方法裡把佈局xml對映為view並返回(一般通過Inflater物件的inflate(R.layout.fragment_main, container, false)方法)
return inflater.inflate(R.layout.fragment_main, container, false);//引數依次為:佈局id,依附的容器
1.2、java程式碼動態建立
getFragmentManager().beginTransaction().add(R.id.id_fragment_main,new MainFragment()).commit();//第一個引數為容器的Id,這裡為FrameLayout的Id,第二個引數為Fragment物件例項
getFragmentManager().beginTransaction().add(R.id.id_fragment_main,new MainFragment(),"MainFragment").commit();//第三個引數為tag
- 首先還是定義Fragemnt對應的佈局檔案和繼承Fragment實現自己的Fragment
- 然後在依附的Activity的佈局檔案中線使用FrameLayout佔位
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:baselineAligned="false" >
<FrameLayout
android:id="@+id/id_fragment_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
- 再獲取FragmentManager(在V4包中通過getSupportFragmentManager())
FrgamentManager fragmentManager=getFragmentManager();
- 通過beginTransaction方法開啟事務FragmentTransaction
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
- 使用FragmentTransaction 的add或者replace方法向容器內新增Fragment(需要傳入容器的id和Fragment的例項物件。)
fragmentTrancsaction.add(id_fragment_main,new MainFragment());//容器的id,Fragment的例項物件
- 提交事務
fragmentTransaction.commit();
2、Fragment的刪除remove
Fragment的刪除也還是藉助FragmentManager和FragmentTransaction,主要有兩個步驟:
- 先通過FragmentManager物件的findFragmentById和findFragmentByTag獲取對應的Fragment
- 再傳入到remove方法中,並commit。
3、Fragment的替換replace
Fragment的替換replace這個方法真的有點奇怪,並沒有像我們正常的邏輯,至少應該提供源Fragment和目標Fragment兩個引數,但並沒有只是提供了一個容器Id和目標Fragment(即將被新增到容器裡展示的Fragment),當年學習的時候也覺得很奇怪,這裡先不深究,以後再去分析。
fragmentManager.beginTransaction().replace(R.id.id_fragment_main,new OtherFragment()).commit();//容器Id,新的Fragment物件
4、Fragment的隱藏hide和顯示show
對於Fragment的hide或者show只需要傳遞我們一個將要hide或者show的Fragment即可。(需要注意的是無論是hide還是show都是針對頂層Fragment,也就是假設你要show的Fragment不是頂層的是show不出來的)
private void hideFragment(String tag){
Fragment mainFragment = fragmentManager.findFragmentByTag(tag);
fragmentManager.beginTransaction().hide(mainFragment).addToBackStack(tag).commit();
}
private void showFragment(String tag){
Fragment mainFragment = fragmentManager.findFragmentByTag(tag); fragmentManager.beginTransaction().show(mainFragment).addToBackStack(tag).commit();
}
5、Fragment 的attach和detach
- public abstract FragmentTransaction attach(Fragment fragment); 它利用Fragment物件的onCreateView()來重建檢視,並重新把UI附著到fragment上(由於是將fragment新增到佇列,且只能新增到列隊頭部),所以attach()操作的結果是,最新操作的頁面始終顯示在最前面,此時呼叫fragment.isAdded()將返回True。
fragmentManager.beginTransaction().attach(mainFragment).addToBackStack(tag).commit();
- public FragmentTransaction detach(Fragment fragment):與attach所做的工作相反,它將引數中的fragment與UI分離,此時的狀態與把fragment放入返回棧時一樣,雖然將view從viewtree中刪除,並將fragment從Activity的佇列中移除!但對應的fragment例項並不會刪除( 在使用detach()後,使用fragment.isAdded()返回的值是false),還是可以被fragmentManager管理的,所以通過FragmentManager.findViewByTag()仍然是會有值的。
fragmentManager.beginTransaction().detach(mainFragment).addToBackStack(tag).commit();
六、Fragment的基本應用完整例子
主佈局的xml檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/id_fragment_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<Button
android:id="@+id/id_addfragment_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="新增Fragment1"/>
<Button
android:id="@+id/id_replacefragment_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="替換為Fragment2"/>
<Button
android:id="@+id/id_removefragment_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="移除Fragment1"/>
<Button
android:id="@+id/id_hidefragment_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hide MainFragement"/>
<Button
android:id="@+id/id_showfragment_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="show MainFragement"/>
</LinearLayout>
MainFragment的佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TitleStyle"
android:text="MainFragment"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
OtherFragment的佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TitleStyle"
android:text="OtherFragment"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_blue_launcher"/>
</LinearLayout>
MainActivity的程式碼:
package com.crazymo.fragmentsdemo;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.nfc.Tag;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity implements View.OnClickListener {
private final String TAG="FramentDemo";
private Button mAddBtn,mRemoveBtn,mRelpaceBtn,mHideBtn,mShowBtn;
private FragmentManager fragmentManager=getFragmentManager();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Activity onCreate");
setContentView(R.layout.activity_main);
init();
}
private void init(){
getViews();
setClickListener();
}
private void getViews(){
mAddBtn= (Button) findViewById(R.id.id_addfragment_btn);
mRelpaceBtn=(Button)findViewById(R.id.id_replacefragment_btn);
mRemoveBtn= (Button) findViewById(R.id.id_removefragment_btn);
mHideBtn= (Button) findViewById(R.id.id_hidefragment_btn);
mShowBtn= (Button) findViewById(R.id.id_showfragment_btn);
}
private void setClickListener(){
mAddBtn.setOnClickListener(this);
mRemoveBtn.setOnClickListener(this);
mRelpaceBtn.setOnClickListener(this);
mHideBtn.setOnClickListener(this);
mShowBtn.setOnClickListener(this);
}
@Override
protected void onStart() {
Log.d(TAG, "Activity onStart");
super.onStart();
}
@Override
protected void onRestart() {
Log.d(TAG,"Activity onReStart");
super.onRestart();
}
@Override
protected void onResume() {
Log.d(TAG,"Activity onResume");
super.onResume();
}
@Override
protected void onPause() {
Log.d(TAG,"Activity onPause");
super.onPause();
}
@Override
protected void onStop() {
Log.d(TAG,"Activity onStop");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG,"Activity onDestory");
super.onDestroy();
}
private void addFragemnt(@StringRes int viewGroupId,@NonNull Fragment fragment,String tag){
fragmentManager=getFragmentManager();
fragmentManager.beginTransaction().add(viewGroupId,fragment,tag)
.addToBackStack(tag).commit();
}
private void removeFragment(String tag){
Fragment fragment = fragmentManager.findFragmentByTag(tag);
fragmentManager.beginTransaction().remove(fragment).commit();
}
private void replaceFragment(@StringRes int viewGroupId,@NonNull Fragment fragment){
fragmentManager.beginTransaction().add(viewGroupId, fragment).commit();
}
private void hideFragment(String tag){
Fragment mainFragment = fragmentManager.findFragmentByTag(tag);
fragmentManager.beginTransaction().hide(mainFragment).addToBackStack(tag).commit();
}
private void showFragment(String tag){
Fragment mainFragment = fragmentManager.findFragmentByTag(tag);
fragmentManager.beginTransaction().show(mainFragment).addToBackStack(tag).commit();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.id_addfragment_btn:
int viewGroupId=R.id.id_fragment_main;
addFragemnt(viewGroupId,new MainFragment(),"MainFragment");
break;
case R.id.id_removefragment_btn:
removeFragment("MainFragment");
break;
case R.id.id_replacefragment_btn:
//Fragment fragment2 = fragmentManager.findFragmentByTag("MainFragment");
//fragmentManager.beginTransaction().remove(fragment2).commit();
int viewGroup=R.id.id_fragment_main;
replaceFragment(viewGroup,new OtherFragment());
break;
case R.id.id_hidefragment_btn:
hideFragment("MainFragment");
break;
case R.id.id_showfragment_btn:
showFragment("MainFragment");
break;
default:
break;
}
}
}
MainFragment的程式碼
package com.crazymo.fragmentsdemo;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by cmo on 16-4-19.
*/
public class MainFragment extends Fragment {
private final String TAG="FramentDemo";
@Override
public void onAttach(Context context) {
Log.d(TAG, "onAttach:剛剛與Activity對接");
super.onAttach(context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate:初始化Fragment物件");
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView:初始化Fragment的UI");
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated:Activity和Fragment都已經建立好了");
super.onActivityCreated(savedInstanceState);
}
@Override
public void onStart() {
Log.d(TAG, "onStart:Fragment變為可見,待互動");
super.onStart();
}
@Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
}
@Override
public void onPause() {
Log.d(TAG, "onPause:與Activity的OnPause繫結");
super.onPause();
}
@Override
public void onStop() {
Log.d(TAG, "onStop:與Activity的OnStop繫結");
super.onStop();
}
@Override
public void onDestroyView() {
Log.d(TAG, "onDestroyView:Fragment即將被儲存或者刪除");
super.onDestroyView();
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy:還與Activity藕斷絲連中可以在Activity中找到");
super.onDestroy();
}
@Override
public void onDetach() {
Log.d(TAG, "onDetach:徹底和Activity分手了");
super.onDetach();
}
}
OtherFragment的程式碼
package com.crazymo.fragmentsdemo;
import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by cmo on 16-4-21.
*/
public class OtherFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_other,null);
}
}