編輯:Android開發實例
前言
本文將講解一下Android中Fragment的內容,必要的地方會提供相應的演示代碼,並且會在最後給出源碼下載。
本文主要有以下內容:
什麼是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)
onCreateView()返回一個View,用於Fragment的顯示,這裡使用inflater.inflate()方法動態膨脹一個View對象做返回值,inflate()的簽名如下:
public View inflate(int resource,ViewGroup root,boolean attachToRoot)
inflate()的resource是一個普通的布局資源,和之前的布局沒有什麼特殊性。而在布局中使用Fragment使用<fragment/>標簽來在XML文件中布局,大多數屬性與UI控件一樣,但是其中有兩個屬性需要特別注意:
下面通過一個示例,來演示一下Fragment在Activity中的應用。示例中在一個Activity中,添加了兩個Fragment。
activity_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="horizontal" >
- <fragment
- android:id="@+id/fragment1"
- android:name="com.example.fragmentSimple.Fragment1"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="2" />
- <fragment
- android:id="@+id/fragment2"
- android:name="com.example.fragmentSimple.Fragment2"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="1" />
- </LinearLayout>
Fragment1:
- package com.example.fragmentSimple;
- import com.example.fragmentdemo.R;
- import android.app.Fragment;
- import android.os.Bundle;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- public class Fragment1 extends Fragment {
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // 填充一個布局View到ViewGrope中
- return inflater.inflate(R.layout.fragment1, container, false);
- }
- }
Fragment2:
- package com.example.fragmentSimple;
- import com.example.fragmentdemo.R;
- import android.os.Bundle;
- import android.app.Fragment;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- public class Fragment2 extends Fragment {
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment2, container, false);
- }
- }
啟動後顯示效果:
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、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:
- <?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="horizontal" >
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/tabfgt1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="fragment1" />
- <TextView
- android:id="@+id/tabfgt2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="fragment2" />
- <TextView
- android:id="@+id/tabfgt3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="fragment3" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- </LinearLayout>
- </LinearLayout>
FragmentTabActivity.java:
- package com.example.fragmentTab;
- import com.example.fragmentSimple.Fragment1;
- import com.example.fragmentSimple.Fragment2;
- import com.example.fragmentTurn.Fragment3;
- import com.example.fragmentdemo.R;
- import android.app.Activity;
- import android.app.FragmentManager;
- import android.app.FragmentTransaction;
- import android.graphics.Color;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.TextView;
- public class FragmentTabActivity extends Activity {
- private TextView tabfgt1, tabfgt2, tabfgt3;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_fragmenttab);
- tabfgt1 = (TextView) findViewById(R.id.tabfgt1);
- tabfgt2 = (TextView) findViewById(R.id.tabfgt2);
- tabfgt3 = (TextView) findViewById(R.id.tabfgt3);
- tabfgt1.setOnClickListener(click);
- tabfgt2.setOnClickListener(click);
- tabfgt3.setOnClickListener(click);
- }
- private View.OnClickListener click = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- tabfgt1.setBackgroundColor(Color.GRAY);
- tabfgt2.setBackgroundColor(Color.GRAY);
- tabfgt3.setBackgroundColor(Color.GRAY);
- // 獲取FragmentManager對象
- FragmentManager fm = getFragmentManager();
- // 開啟事務
- FragmentTransaction ft = fm.beginTransaction();
- switch (v.getId()) {
- case R.id.tabfgt1:
- tabfgt1.setBackgroundColor(Color.GREEN);
- // 替換R.id.content中的Fragment
- ft.replace(R.id.content, new Fragment1());
- break;
- case R.id.tabfgt2:
- tabfgt2.setBackgroundColor(Color.YELLOW);
- ft.replace(R.id.content, new Fragment2());
- break;
- case R.id.tabfgt3:
- tabfgt3.setBackgroundColor(Color.RED);
- ft.replace(R.id.content, new Fragment3());
- break;
- default:
- break;
- }
- // 提交事務
- ft.commit();
- }
- };
- }
效果展示:
在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
- <?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="horizontal" >
- <fragment
- android:id="@+id/fragment1"
- android:name="com.example.fragmentSimple.Fragment1"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="1" />
- <!-- 加載了兩個Fragment1 -->
- <fragment
- android:id="@+id/fragment2"
- android:name="com.example.fragmentSimple.Fragment1"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="1" />
- <fragment
- android:id="@+id/fragment3"
- android:name="com.example.fragmentTurn.Fragment3"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="1" />
- </LinearLayout>
帶Button的Fragment:
- package com.example.fragmentTurn;
- import com.example.fragmentdemo.R;
- import android.app.Fragment;
- import android.os.Bundle;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.Button;
- import android.widget.TextView;
- import android.widget.Toast;
- public class Fragment3 extends Fragment {
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- return inflater.inflate(R.layout.fragment3, container, false);
- }
- @Override
- public void onStart() {
- super.onStart();
- // 方法2: 在Fragment中獲取操作其他Fragment的控件
- // Button btnGetText=(Button)getActivity().findViewById(R.id.btnGetText);
- // btnGetText.setOnClickListener(new View.OnClickListener() {
- //
- // @Override
- // public void onClick(View v) {
- // TextView tv=(TextView)getActivity().findViewById(R.id.tvFragment1);
- // Toast.makeText(getActivity(), tv.getText().toString() ,Toast.LENGTH_SHORT).show();
- // }
- // });
- }
- }
FragmentTurnActivity.java:
- package com.example.fragmentTurn;
- import com.example.fragmentdemo.R;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
- import android.widget.TextView;
- import android.widget.Toast;
- public class FragmentTurnActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_fragmentturn);
- // 方法1:在Activity中操作旗下Fragment中的控件
- Button btn=(Button)findViewById(R.id.btnGetText);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- TextView tv=(TextView)findViewById(R.id.tvFragment1);
- tv.setText("動態修改");
- Toast.makeText(FragmentTurnActivity.this,tv.getText().toString() ,Toast.LENGTH_SHORT ).show();
- }
- });
- }
- }
效果展示:
從上面的例子有一個問題,無論是在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包進行開發,就不會存在兼容性的問題。
源碼下載
如圖所示為程序效果動畫圖 地圖滾動的原理 在本人之前博客的文章中介紹過人物在屏幕中的移動方式,因為之前拼的游戲地圖是完全填充整個手機屏幕的,所以無需處理地圖的平
在《Android 手機衛士(六):打包生成apk維護到服務器》一文中,實現了新版本的apk到服務器,當打開客戶端apk的時候,發現有新版本,提示更
中國科學院開源協會鏡像站地址: IPV4/IPV6: http://mirrors.opencas.cn 端口:80 IPV4/IPV6: http://mirr
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我