前言java
開門見山開篇名義,本篇博客將講解一下Android中Fragment的內容,必要的地方會提供相應的演示代碼,而且會在最後給出源碼下載。android
本文主要有如下內容:app
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清單文件中註冊。this
上面已經介紹了Fragment,再來說講如何使用Fragment。使用Fragment必須繼承這個類或其子類,而且重寫其的onCreateView()方法,這個方法是用於指定Fragment在初次加載的時候,顯示的View。下面是這個方法的簽名:設計
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState)3d
onCreateView()返回一個View,用於Fragment的顯示,這裏使用inflater.inflate()方法動態膨脹一個View對象作返回值,inflate()的簽名以下:xml
public View inflate(int resource,ViewGroup root,boolean attachToRoot)
inflate()的resource是一個普通的佈局資源,和以前的佈局沒有什麼特殊性。而在佈局中使用Fragment使用<fragment/>標籤來在XML文件中佈局,大多數屬性與UI控件同樣,可是其中有兩個屬性須要特別注意:
下面經過一個示例,來演示一下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 } 18 }
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 } 18 }
啓動後顯示效果:
Fragment有本身獨立的生命週期,可是它有是依託於Activity的,因此Fragment的生命週期直接受Activity的影響。下圖很直觀的描述了Activity的生命週期:
從上圖中能夠看出Fragment的生命週期大致上和Activity同樣,有兩個生命週期方法須要注意,onAttach()附加、onDetach()剝離,從這兩個方法的位置能夠看出,Fragment在建立的時候,是先附加到Activity中,而後纔開始從onCreateView()中加載View的,記住這一點很重要。而且在生命週期結束的時候,也是先銷燬onDestroy()而後纔回調onDetach()從Activity中剝離這個Fragment。
在代碼中管理一個Fragment很是簡單,須要用到一個FragmentTransaction對象,這個對象經過getFragmentManager().beginTransaction()獲取,它將開啓一個事務,用於操做一個ViewGroup中的Fragment。
FragmentTransaction的經常使用方法:
其中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 }; 67 }
效果展現:
既然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 } 36 }
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 } 31 }
效果展現:
從上面的例子有一個問題,不管是在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。
上面已經提到,Fragment是Android3.0行增長的特性。 而對於低版本的Android設備,Google也沒有放棄。細心的朋友應該已經發現了,當對Fragment引用包的時候,會有兩個選項,android.app.Fragment和android.support.v4.app.Fragment,其中android.support.v4.app.Fragment就是爲了兼容低版本而考慮的,只須要引用它便可。
通常而言,若是考慮向下兼容的問題的話,推薦直接引用android.support.v4.app.Fragment包進行開發,就不會存在兼容性的問題。