Android--UI之Fragment

前言java

  開門見山開篇名義,本篇博客將講解一下Android中Fragment的內容,必要的地方會提供相應的演示代碼,而且會在最後給出源碼下載。android

  本文主要有如下內容:app

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

 

什麼是Fragmentide

  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.net

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

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

  • inflater:當前佈局的填充者,能夠用inflater.inflate()方法去填充一個佈局文件。
  • container:爲包裹當前Fragment的容器,通常是一個佈局控件。
  • savedInstanceState:當前實例化的狀態,通常用不上。

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

    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     }
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的生命週期

  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     };
67 }
複製代碼

  效果展現:

 


在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     }
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。

 

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包進行開發,就不會存在兼容性的問題。

 

  源碼下載

相關文章
相關標籤/搜索