Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android應用開發入門(四十五)UI控件Fragment的使用

Android應用開發入門(四十五)UI控件Fragment的使用

編輯:Android開發實例

前言

  本文將講解一下Android中Fragment的內容,必要的地方會提供相應的演示代碼,並且會在最後給出源碼下載。

  本文主要有以下內容:

  1. 什麼是Fragment
  2. 如何創建一個Fragment
  3. Fragment的生命周期
  4. 如何管理一個Fragment
  5. 在Fragment間如何交互
  6. Fragement向下兼容

 

什麼是Fragment

  Fragment,碎片,是Android3.0之後新增加的特性。主要是為了支持更多的UI設計在大屏幕設備上,如平板。因為現在設備的屏幕越來越大,使用Fragment可以更靈活的管理視圖層次的變化。像Activity一樣,可以創建Fragment來包含View,進行布局,但是Fragment必須嵌入在Activity中,不能單獨存在,而且一個Activity可以嵌入多個Fragment,同時一個Fragment可以被多個Activity重用。

  

  上圖是從官方文檔中掛載的,可以很清晰的說明Activity和Fragment的關系和優點。在平板中,因為屏幕大,顯示的內容全,如果還像手機哪樣通過Activity跳轉的方式去加載UI,太浪費屏幕資源了,而如上左圖,可以結合Fragment布局,使一個Activity左右分別包含一個Fragment,這樣可以通過對左邊Fragment的操作來影響右邊Fragment的顯示,例如:新聞閱讀,系統設置等。如果一個應用的是采用Activity+Fragment結合布局,那麼可以很方便的在平板與手機之間相互移植,大部分代碼是可以重用的,並且Fragment無需在AndroidManifest.xml清單文件中注冊。

如何創建一個Fragment

  上面已經介紹了Fragment,再來講講如何使用Fragment。使用Fragment必須繼承這個類或其子類,並且重寫其的onCreateView()方法,這個方法是用於指定Fragment在初次加載的時候,顯示的View。下面是這個方法的簽名:

    public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState)

  • inflater:當前布局的填充者,可以用inflater.inflate()方法去填充一個布局文件。
  • container:為包裹當前Fragment的容器,一般是一個布局控件。
  • savedInstanceState:當前實例化的狀態,一般用不上。

  onCreateView()返回一個View,用於Fragment的顯示,這裡使用inflater.inflate()方法動態膨脹一個View對象做返回值,inflate()的簽名如下:

    public View inflate(int resource,ViewGroup root,boolean attachToRoot)

  • resource:動態膨脹的布局資源ID。
  • root:膨脹出的View的上層布局對象,一般傳遞onCreateView的container參數即可。
  • attachToRoot:指定展開的布局是否依附到root這個ViewGroup中,一般傳遞false即可。

  inflate()的resource是一個普通的布局資源,和之前的布局沒有什麼特殊性。而在布局中使用Fragment使用<fragment/>標簽來在XML文件中布局,大多數屬性與UI控件一樣,但是其中有兩個屬性需要特別注意:

  • android:name:這個Fragment的實現類。
  • android:layout_weight:當前Fragment在Activity的權重,數值越大,在Activity中占的權重越大。

  下面通過一個示例,來演示一下Fragment在Activity中的應用。示例中在一個Activity中,添加了兩個Fragment。

  activity_fragment.xml:

 

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:layout_width="match_parent" 
  4.     android:layout_height="match_parent" 
  5.     android:orientation="horizontal" >  
  6.  
  7.     <fragment  
  8.         android:id="@+id/fragment1" 
  9.         android:name="com.example.fragmentSimple.Fragment1" 
  10.         android:layout_width="0px" 
  11.         android:layout_height="match_parent" 
  12.         android:layout_weight="2" />  
  13.  
  14.     <fragment  
  15.         android:id="@+id/fragment2" 
  16.         android:name="com.example.fragmentSimple.Fragment2" 
  17.         android:layout_width="0px" 
  18.         android:layout_height="match_parent" 
  19.         android:layout_weight="1" />  
  20.  
  21. </LinearLayout> 

 Fragment1:

  1. package com.example.fragmentSimple;  
  2.  
  3. import com.example.fragmentdemo.R;  
  4. import android.app.Fragment;  
  5. import android.os.Bundle;  
  6. import android.view.LayoutInflater;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9.  
  10. public class Fragment1 extends Fragment {  
  11.  
  12.     @Override 
  13.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  14.             Bundle savedInstanceState) {  
  15.         // 填充一個布局View到ViewGrope中  
  16.         return inflater.inflate(R.layout.fragment1, container, false);          
  17.     }  

Fragment2:

 

  1. package com.example.fragmentSimple;  
  2.  
  3. import com.example.fragmentdemo.R;  
  4.  
  5. import android.os.Bundle;  
  6. import android.app.Fragment;  
  7. import android.view.LayoutInflater;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10.  
  11. public class Fragment2 extends Fragment {  
  12.  
  13.     @Override 
  14.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  15.             Bundle savedInstanceState) {  
  16.         return inflater.inflate(R.layout.fragment2, container, false);          
  17.     }  

  啟動後顯示效果:

 

Fragment的生命周期

  Fragment有自己獨立的生命周期,但是它有是依托於Activity的,所以Fragment的生命周期直接受Activity的影響。下圖很直觀的描述了Activity的生命周期:

  從上圖中可以看出Fragment的生命周期大體上和Activity一樣,有兩個生命周期方法需要注意,onAttach()附加、onDetach()剝離,從這兩個方法的位置可以看出,Fragment在創建的時候,是先附加到Activity中,然後才開始從onCreateView()中加載View的,記住這一點很重要。並且在生命周期結束的時候,也是先銷毀onDestroy()然後才回調onDetach()從Activity中剝離這個Fragment。

如何管理一個Fragment

  在代碼中管理一個Fragment非常簡單,需要用到一個FragmentTransaction對象,這個對象通過getFragmentManager().beginTransaction()獲取,它將開啟一個事務,用於操作一個ViewGroup中的Fragment。

  FragmentTransaction的常用方法:

  • add():增加一個Fragment,具有多個重載。
  • replace():替換一個Fragment,具有多個重載。
  • remove():移除掉一個指定的Fragment。
  • addToBackStack():在事務中添加一個棧,用於回退。
  • commit():提交事務。

  其中add、replace、remove都是很常見的方法,無需過多介紹。但是addToBackStack()方法就需要額外講解一下,正常情況下,應用中的Activity是有一個任務棧去管理它的。默認情況下,當我們在不同的Activity中跳轉的時候,點擊回退總是能回到上一個Activity中。而Fragment是嵌套在Activity,所以默認無法向Activity的任務棧中添加,當點擊回退的時候只會回到上一個Activity,不會理會Fragment的操作(add、replace、remove),而使用addToBackStack()可以將當前的事務添加到另一個棧中,這個棧由Fragment的Activity管理,這個棧中的每一條都是一個Fragment的一次事務,有了這個棧去管理Fragment,就可以通過回退按鍵,反向回滾Fragment的事務。這一點很重要,因為Fragment無需在清單文件中配置,所以現在有些應用會使用Fragment來布局跳轉。

  下面通過一個示例,演示一下動態操作Fragment的例子。在示例中,會實現一個分欄的效果,在左邊點擊項會動態修改右邊的內容。

  布局文件,activity_fragmenttab.xml:

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:layout_width="match_parent" 
  4.     android:layout_height="match_parent" 
  5.     android:orientation="horizontal" > 
  6.  
  7.     <LinearLayout 
  8.         android:layout_width="wrap_content" 
  9.         android:layout_height="match_parent" 
  10.         android:orientation="vertical" > 
  11.  
  12.         <TextView 
  13.             android:id="@+id/tabfgt1" 
  14.             android:layout_width="wrap_content" 
  15.             android:layout_height="wrap_content" 
  16.             android:text="fragment1" /> 
  17.  
  18.         <TextView 
  19.             android:id="@+id/tabfgt2" 
  20.             android:layout_width="wrap_content" 
  21.             android:layout_height="wrap_content" 
  22.             android:text="fragment2" /> 
  23.  
  24.         <TextView 
  25.             android:id="@+id/tabfgt3" 
  26.             android:layout_width="wrap_content" 
  27.             android:layout_height="wrap_content" 
  28.             android:text="fragment3" /> 
  29.     </LinearLayout> 
  30.  
  31.     <LinearLayout 
  32.         android:id="@+id/content" 
  33.         android:layout_width="match_parent" 
  34.         android:layout_height="match_parent" 
  35.         android:orientation="vertical" > 
  36.     </LinearLayout> 
  37.  
  38. </LinearLayout> 

  FragmentTabActivity.java:

 

  1. package com.example.fragmentTab;  
  2.  
  3. import com.example.fragmentSimple.Fragment1;  
  4. import com.example.fragmentSimple.Fragment2;  
  5. import com.example.fragmentTurn.Fragment3;  
  6. import com.example.fragmentdemo.R;  
  7.  
  8. import android.app.Activity;  
  9. import android.app.FragmentManager;  
  10. import android.app.FragmentTransaction;  
  11. import android.graphics.Color;  
  12. import android.os.Bundle;  
  13. import android.view.View;  
  14. import android.widget.TextView;  
  15.  
  16. public class FragmentTabActivity extends Activity {  
  17.     private TextView tabfgt1, tabfgt2, tabfgt3;  
  18.  
  19.     @Override 
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         // TODO Auto-generated method stub  
  22.         super.onCreate(savedInstanceState);  
  23.         setContentView(R.layout.activity_fragmenttab);  
  24.  
  25.         tabfgt1 = (TextView) findViewById(R.id.tabfgt1);  
  26.         tabfgt2 = (TextView) findViewById(R.id.tabfgt2);  
  27.         tabfgt3 = (TextView) findViewById(R.id.tabfgt3);  
  28.  
  29.         tabfgt1.setOnClickListener(click);  
  30.         tabfgt2.setOnClickListener(click);  
  31.         tabfgt3.setOnClickListener(click);  
  32.  
  33.     }  
  34.  
  35.     private View.OnClickListener click = new View.OnClickListener() {  
  36.  
  37.         @Override 
  38.         public void onClick(View v) {  
  39.             tabfgt1.setBackgroundColor(Color.GRAY);  
  40.             tabfgt2.setBackgroundColor(Color.GRAY);  
  41.             tabfgt3.setBackgroundColor(Color.GRAY);  
  42.             // 獲取FragmentManager對象  
  43.             FragmentManager fm = getFragmentManager();  
  44.             // 開啟事務  
  45.             FragmentTransaction ft = fm.beginTransaction();  
  46.             switch (v.getId()) {  
  47.             case R.id.tabfgt1:  
  48.                 tabfgt1.setBackgroundColor(Color.GREEN);  
  49.                 // 替換R.id.content中的Fragment  
  50.                 ft.replace(R.id.content, new Fragment1());  
  51.                 break;  
  52.             case R.id.tabfgt2:  
  53.                 tabfgt2.setBackgroundColor(Color.YELLOW);  
  54.                 ft.replace(R.id.content, new Fragment2());  
  55.                 break;  
  56.             case R.id.tabfgt3:  
  57.                 tabfgt3.setBackgroundColor(Color.RED);  
  58.                 ft.replace(R.id.content, new Fragment3());  
  59.                 break;  
  60.             default:  
  61.                 break;  
  62.             }  
  63.             // 提交事務  
  64.             ft.commit();  
  65.         }  
  66.     };  

 效果展示:

在Fragment中,如何交互

  既然Fragment是嵌套在Activity中的,而在Fragment加載的布局文件中,又可以額外的布局,那麼出現了新的問題,如何操作兩個不同Fragment中的控件呢?回憶一下在Activity中,操作一個控件需要通過findViewById(int)方法通過控件的ID去找到控件,而使用Fragment其實到最後Fragment.onCreateActivity()的時候是把膨脹的View加載到Activity中了,所以可以直接在Activity中通過findViewById()方法找到控件,進而操作它,這一點和直接操作Activity的方式一致。但是如果需要在一個Fragment中操作另外一個Fragment的控件,就需要用到Fragment.getActivity()來獲取到當前Fragment承載的Activity對象,拿到這個Activity對象,獲取到其中的控件就不成問題了。

  下面通過一個示例來演示Fragment中的交互,在Activity中,有三個Fragment,從其中的一個Fragment的Button點擊的時候,修改其他Fragment的值。

  布局,activity_fragmentturn.xml

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:layout_width="match_parent" 
  4.     android:layout_height="match_parent" 
  5.     android:orientation="horizontal" > 
  6.  
  7.     <fragment 
  8.         android:id="@+id/fragment1" 
  9.         android:name="com.example.fragmentSimple.Fragment1" 
  10.         android:layout_width="0px" 
  11.         android:layout_height="match_parent" 
  12.         android:layout_weight="1" /> 
  13.     <!-- 加載了兩個Fragment1 --> 
  14.      <fragment 
  15.         android:id="@+id/fragment2" 
  16.         android:name="com.example.fragmentSimple.Fragment1" 
  17.         android:layout_width="0px" 
  18.         android:layout_height="match_parent" 
  19.         android:layout_weight="1" /> 
  20.     <fragment 
  21.         android:id="@+id/fragment3" 
  22.         android:name="com.example.fragmentTurn.Fragment3" 
  23.         android:layout_width="0px" 
  24.         android:layout_height="match_parent" 
  25.         android:layout_weight="1" /> 
  26.  
  27. </LinearLayout> 

 帶Button的Fragment:

  1. package com.example.fragmentTurn;  
  2.  
  3. import com.example.fragmentdemo.R;  
  4.  
  5. import android.app.Fragment;  
  6. import android.os.Bundle;  
  7. import android.view.LayoutInflater;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10. import android.widget.Button;  
  11. import android.widget.TextView;  
  12. import android.widget.Toast;  
  13. public class Fragment3 extends Fragment {  
  14.  
  15.       
  16.     @Override 
  17.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  18.             Bundle savedInstanceState) {  
  19.         // TODO Auto-generated method stub  
  20.         return inflater.inflate(R.layout.fragment3, container, false);          
  21.     }  
  22.     @Override 
  23.     public void onStart() {  
  24.         super.onStart();  
  25.         // 方法2: 在Fragment中獲取操作其他Fragment的控件  
  26. //        Button btnGetText=(Button)getActivity().findViewById(R.id.btnGetText);  
  27. //        btnGetText.setOnClickListener(new View.OnClickListener() {  
  28. //              
  29. //            @Override  
  30. //            public void onClick(View v) {  
  31. //                TextView tv=(TextView)getActivity().findViewById(R.id.tvFragment1);  
  32. //                Toast.makeText(getActivity(), tv.getText().toString()    ,Toast.LENGTH_SHORT).show();                  
  33. //            }  
  34. //        });  
  35.     }  

  FragmentTurnActivity.java: 

 

  1. package com.example.fragmentTurn;  
  2.  
  3.  
  4. import com.example.fragmentdemo.R;  
  5.  
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.TextView;  
  11. import android.widget.Toast;  
  12.  
  13. public class FragmentTurnActivity extends Activity {  
  14.     @Override 
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         // TODO Auto-generated method stub  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_fragmentturn);  
  19.         // 方法1:在Activity中操作旗下Fragment中的控件  
  20.         Button btn=(Button)findViewById(R.id.btnGetText);  
  21.         btn.setOnClickListener(new View.OnClickListener() {  
  22.               
  23.             @Override 
  24.             public void onClick(View v) {                  
  25.                 TextView tv=(TextView)findViewById(R.id.tvFragment1);  
  26.                 tv.setText("動態修改");  
  27.                 Toast.makeText(FragmentTurnActivity.this,tv.getText().toString() ,Toast.LENGTH_SHORT    ).show();  
  28.             }  
  29.         });  
  30.     }  

 效果展示:

 

   從上面的例子有一個問題,無論是在Activity中使用findViewById()還是在Fragment中使用getActivity().findViewById(),雖然可以獲取到控件,但是有個例外的情況。就是在Activity中,同時使用了兩個一樣的Fragment,這個時候僅僅使用上面介紹的方法,只能通過id獲取到第一個Fragment中的控件。因為,在布局文件中定義的控件,就算ID重復了,AndroidSDK維護的R.java類中,也只會聲明一次,也就是說,想在Activity中區分同一個Fragment類的兩個實例中的控件,是無法做到的。

  那麼就嘚換一個思路,我的解決方案:在Fragment中聲明一個View變量,然後在onCreateView中膨脹的View並不直接返回,而是把它引用到聲明的View變量上,然後在應用的任何地方,使用getFragmentManager().findFragmentById(int)通過Fragment的Id找到這個Fragment對象,然後獲取其中的View對象,使用View.findViewById(int)找到Fragment的對應Id的控件,進而操作它,這裡就不提供示例了。雖然這個方法可以解決問題,但是一般不推薦如此做,因為大部分場景沒必要在一個Activity中定義兩個相同的Fragment。

 

Fragement向下兼容

  上面已經提到,Fragment是Android3.0行增加的特性。 而對於低版本的Android設備,Google也沒有放棄。細心的朋友應該已經發現了,當對Fragment引用包的時候,會有兩個選項,android.app.Fragment和android.support.v4.app.Fragment,其中android.support.v4.app.Fragment就是為了兼容低版本而考慮的,只需要引用它即可。

  一般而言,如果考慮向下兼容的問題的話,推薦直接引用android.support.v4.app.Fragment包進行開發,就不會存在兼容性的問題。

 

  源碼下載

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved