什么是Fragment:
Android是在Android 3.0 (API level 11)開始引入Fragment的。
可以把Fragment想成Activity中的模塊,這個模塊有自己的布局,有自己的生命周期,單獨處理自己的輸入,在Activity運行的時候可以加載或者移除Fragment模塊。
可以把Fragment設計成可以在多個Activity中復用的模塊。
當開發的應用程序同時適用于平板電腦和手機時,可以利用Fragment實現靈活的布局,改善用戶體驗。
?
?
Fragment的意義:
?Android在3.0中引入了fragments的概念,主要目的是用在大屏幕設備上--例如平板電腦上,支持更加動態和靈活的UI設計。平板電腦的屏幕要比手機的大得多,有更多的空間來放更多的UI組件,并且這些組件之間會產生更多的交互。Fragment允許這樣的一種設計,而不需要你親自來管理 viewhierarchy的復雜變化。 通過將activity的布局分散到fragment中, 你可以在運行時修改activity的外觀,并在由activity管理的back stack中保存那些變化.??
例如, 一個新聞應用可以在屏幕左側使用一個fragment來展示一個文章的列表,然后在屏幕右側使用另一個fragment來展示一篇文章--2個fragment并排顯示在相同的一個activity中,并且每一個fragment擁有它自己的一套生命周期回調方法,并且處理它們自己的用戶輸入事件。 因此, 取代使用一個activity來選擇一篇文章而另一個activity來閱讀文章的方式,用戶可以在同一個activity中選擇一篇文章并且閱讀, 如圖所示:
?
fragment在你的應用中應當是一個模塊化和可重用的組件.即,因為fragment定義了它自己的布局, 以及通過使用它自己的生命周期回調方法定義了它自己的行為,你可以將fragment包含到多個activity中. 這點特別重要, 因為這允許你將你的用戶體驗適配到不同的屏幕尺寸.舉個例子,你可能會僅當在屏幕尺寸足夠大時,在一個activity中包含多個fragment,并且,當不屬于這種情況時,會啟動另一個單獨的,使用不同fragment的activity.
繼續之前那個新聞的例子 -- 當運行在一個特別大的屏幕時(例如平板電腦),應用可以在Activity A中嵌入2個fragment。然而,在一個正常尺寸的屏幕(例如手機)上,沒有足夠的空間同時供2個fragment用, 因此, Activity A會僅包含文章列表的fragment, 而當用戶選擇一篇文章時, 它會啟動ActivityB,它包含閱讀文章的fragment.因此, 應用可以同時支持上圖中的2種設計模式。
?
?
Fragment的生命周期:
因為Fragment必須嵌入在Acitivity中使用,所以Fragment的生命周期和它所在的Activity是密切相關的。
如果Activity是暫停狀態,其中所有的Fragment都是暫停狀態;如果Activity是stopped狀態,這個Activity中所有的Fragment都不能被啟動;如果Activity被銷毀,那么它其中的所有Fragment都會被銷毀。(對Activity不熟悉的話,請看另一篇文章《Android四大組件之Activity》)
但是,當Activity在活動狀態,可以獨立控制Fragment的狀態,比如加上或者移除Fragment。
當這樣進行fragment transaction(轉換)的時候,可以把fragment放入Activity的back stack中,這樣用戶就可以進行返回操作。
?
?
Fragment的使用:
創建Fragment
? ????通常, 應當至少實現如下的生命周期方法:
- onCreate()
當創建fragment時, 系統調用該方法.?
在實現代碼中,應當初始化想要在fragment中保持的必要組件, 當fragment被暫停或者停止后可以恢復. - onCreateView()
fragment第一次繪制它的用戶界面的時候, 系統會調用此方法. 為了繪制fragment的UI,此方法必須返回一個View, 這個view是你的fragment布局的根view. 如果fragment不提供UI, 可以返回null. - onPause()
用戶將要離開fragment時,系統調用這個方法作為第一個指示(然而它不總是意味著fragment將被銷毀.) 在當前用戶會話結束之前,通常應當在這里提交任何應該持久化的變化(因為用戶有可能不會返回).
?
大多數應用應當為每一個fragment實現至少這3個方法,但是還有一些其他回調方法你也應當用來去處理fragment生命周期的各種階段.全部的生命周期回調方法將會在后面章節 Handlingthe Fragment Lifecycle 中討論.
? ????除了繼承基類 Fragment , 還有一些子類你可能會繼承:
?
- DialogFragment
顯示一個浮動的對話框.??
用這個類來創建一個對話框,是使用在Activity類的對話框工具方法之外的一個好的選擇,
因為你可以將一個fragment對話框合并到activity管理的fragment back stack中,允許用戶返回到一個之前曾被摒棄的fragment. - ListFragment
顯示一個由一個adapter(例如 SimpleCursorAdapter)管理的項目的列表, 類似于ListActivity.
它提供一些方法來管理一個list view, 例如 onListItemClick()回調來處理點擊事件. - PreferenceFragment
顯示一個 Preference對象的層次結構的列表, 類似于PreferenceActivity.?
這在為你的應用創建一個"設置"activity時有用處.
- DialogFragment
?
實現Fragment的UI
提供Fragment的UI,必須實現onCreateView()方法。
假設Fragment的布局設置寫在example_fragment.xml資源文件中,那么onCreateView()方法可以如下寫:
public static class ExampleFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState){// Inflate the layout for this fragmentreturn inflater.inflate(R.layout.example_fragment, container, false);} }
onCreateView()中container參數代表該Fragment在Activity中的父控件;savedInstanceState提供了上一個實例的數據。
inflate()方法的三個參數:
第一個是resource ID,指明了當前的Fragment對應的資源文件;
第二個參數是父容器控件;
第三個布爾值參數表明是否連接該布局和其父容器控件,在這里的情況設置為false,因為系統已經插入了這個布局到父控件,設置為true將會產生多余的一個View Group。
?
把Fragment加入Activity
?
當Fragment被加入Activity中時,它會處在對應的View Group中。
?
Fragment有兩種加載方式:一種是在Activity的layout中使用標簽<fragment>聲明;另一種方法是在代碼中把它加入到一個指定的ViewGroup中。
?
另外,Fragment它可以并不是Activity布局中的任何一部分,它可以是一個不可見的部分。這部分內容先略過。
?
?
?
加載方式1:通過Activity的布局文件將Fragment加入Activity
?
在Activity的布局文件中,將Fragment作為一個子標簽加入即可。
?
如:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="match_parent"><fragment android:name="com.example.news.ArticleListFragment"android:id="@+id/list"android:layout_weight="1"android:layout_width="0dp"android:layout_height="match_parent" /><fragment android:name="com.example.news.ArticleReaderFragment"android:id="@+id/viewer"android:layout_weight="2"android:layout_width="0dp"android:layout_height="match_parent" /> </LinearLayout>
其中android:name屬性填上你自己創建的fragment的完整類名。
當系統創建這個Activity的布局文件時,系統會實例化每一個fragment,并且調用它們的onCreateView()方法,來獲得相應fragment的布局,并將返回值插入fragment標簽所在的地方。
有三種方法為Fragment提供ID:
android:id屬性:唯一的id
android:tag屬性:唯一的字符串
如果上面兩個都沒提供,系統使用容器view的ID。
?
加載方式2:通過編程的方式將Fragment加入到一個ViewGroup中
當Activity處于Running狀態下的時候,可以在Activity的布局中動態地加入Fragment,只需要指定加入這個Fragment的父View Group即可。
首先,需要一個FragmentTransaction實例:
FragmentManager fragmentManager = getFragmentManager()FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
(注,如果import android.support.v4.app.FragmentManager;那么使用的是:FragmentManager fragmentManager = getSupportFragmentManager();)
之后,用add()方法加上Fragment的對象:
ExampleFragment fragment = new ExampleFragment();fragmentTransaction.add(R.id.fragment_container, fragment);fragmentTransaction.commit();
其中第一個參數是這個fragment的容器,即父控件組。
最后需要調用commit()方法使得FragmentTransaction實例的改變生效。
?
Fragment對menu菜單的操作
android4.0之后引入了fragment的概念,它的生命周期函數和activity幾乎一樣。對菜單的操作也是通過onCreateOptionMenu()實現的。
例子:
onCreate() 期間調用 setHasOptionsMenu() 來指出fragment愿意添加item到選項菜單
public static class DetailsFragment extends Fragment {@Overridepublic void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setHasOptionsMenu(true);} }
操作菜單
@Overridepublic void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {// TODO Auto-generated method stubsuper.onCreateOptionsMenu(menu, inflater);menu.add("Menu 1a").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);menu.add("Menu 1b").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// TODO Auto-generated method stubToast.makeText(getActivity(), "index is"+getShownIndex()+" && menu text is "+item.getTitle(), 1000).show();return super.onOptionsItemSelected(item);}
?
?
還記得文章上面提到過的閱讀新聞的例子嗎?(講Fragment意義那里)下面,我就來實現這個功能:
我們先貼出效果圖:
(豎屏)
???????點擊第二條新聞后,如下圖:
?
(橫屏狀態)
?
下面貼上代碼:
?先要創建兩個布局文件,一個用于橫屏、一個用于豎屏:
?其代碼分別為:
layout/main.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 class="com.topcsa.test_fragment.ListFragment"android:id="@+id/titles"android:layout_weight="1"android:layout_width="0px"android:layout_height="match_parent"/></LinearLayout>
layout-land/main.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 class="com.topcsa.test_fragment.ListFragment"android:id="@+id/titles"android:layout_weight="1"android:layout_width="0px"android:layout_height="match_parent"/><FrameLayout android:id="@+id/detail"android:layout_weight="2"android:layout_width="0px"android:layout_height="match_parent"android:background="?android:attr/detailsElementBackground"></FrameLayout></LinearLayout>
?
包下的文件如下:
下面依次貼上代碼(代碼有詳細注釋):
?


package com.topcsa.test_fragment;public final class Data {public static final String[] titles = { "日本買兩棲艦欲圓航母夢 最后得雞肋","中將:中國坦克兵素質優異 96A凸顯四大質量問題", "臺灣政壇又炸鍋:與大陸首席談判代表是“共諜”" };public static final String[] DETAIL = {"日本防衛相小野寺五典8月4日在東京都發表演講又一次強調了兩棲攻擊艦的重要性,指出日本將從美國購買黃蜂級兩棲攻擊艦。若發展順利,新型兩棲攻擊艦將于2019年服役日本海上自衛隊,成為其最大艦艇。","首先,這主要是一場坦克乘員素質的比賽,比技能、比體能、比心理素質。應該說中國坦克兵表現堪稱完美。射擊比賽第一,除了裝備因素外,嫻熟的操作技能和全車乘員協調一致的動作,是獲勝的關鍵。裝備性能可以提供高命中率的客觀條件,但在高速行進中(在視頻中看,96A坦克行進間射擊的時速應在20-25千米/小時)能發發命中目標,則主要取決于人的因素。而T-72坦克行進間射擊時速都不超過10千米/小時,甚至是短停射擊,差距就大了。96A坦克上反式穩像火控的反應速度、精度和在復雜工況條件的穩定性,大大超過了T-72下反式火控。96坦克初期型號也是下反式穩像火控,遠不至于在這次比賽中T-72坦克表現得這么差,這就是坦克乘員的素質在起作用了。只能說中國坦克兵的素質高于國外同行。另外,我軍坦克兵射擊訓練的難度大大超過了這次競賽條件。譬如射擊跑道是起伏的、彎曲的,目標間夾角不小于17密位(這次比賽也就1-2密位),打完一個目標后需要大角度、高速度調炮瞄向下一個目標;目標不僅是隱顯的,還是隱蔽的,周圍不能有明顯方位物(這次比賽在靶標附近設立了一個獨立家屋,便于搜索和指示目標);96A坦克在訓練中以打運動目標為主,目標時速不低于12千米/小時(這次目標是固定的,目標色彩與背景反差也較大),如果換成運動目標,估計T-72坦克脫靶的更多。裝備也是重要因素。在視頻中看到,T-72坦克彈跡和彈著點都能看到,說明它的炮口初速不大于1000米/秒,而96A坦克根本看不到彈跡,彈著點煙塵也小很多,貫穿布靶時形成一個小洞,說明我炮口初速和鎢芯脫殼穿甲彈的侵徹力遠高于T-72坦克。我命中部位大多進入目標9區(井字格,四周8個區,中心為9區),說明我坦克炮千米立靶密集度集中,精度高、彈道穩定。","兩岸發展關系,臺灣的政治穩定至關重要。臺灣社會很多人或許沒有意識到,臺灣政治的一些深層無序已經相當嚴重。大陸與世界很多經濟體談自貿區或類似協定,但唯有同臺灣的這一份冒出遭學生抗議并擱置的離奇周折,張顯耀也是第一位被疑遭大陸“策反”的首席談判代表。", }; }
?


package com.topcsa.test_fragment;import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ScrollView; import android.widget.TextView;public class DetailFragment extends Fragment {public static DetailFragment newInstance(int index){DetailFragment f=new DetailFragment();Bundle bundle=new Bundle();bundle.putInt("index", index);f.setArguments(bundle);//將bundle對象作為Fragment的參數保存return f;}public int getShownIndex(){//獲取要顯示的列表項索引return getArguments().getInt("index",0);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {if(container==null){return null;}//創建一個滾動視圖ScrollView sl=new ScrollView(getActivity());TextView text=new TextView(getActivity());text.setPadding(10, 10, 10, 10);sl.addView(text);//設置文本框中要顯示的文本 text.setText(Data.DETAIL[getShownIndex()]);return sl;} }
?


package com.topcsa.test_fragment;import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView;public class ListFragment extends android.app.ListFragment {boolean dualPane;// 是否在同一界面上顯示列表和內容int curCheckPosition = 0;// 當前選擇的索引位置 @Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);//為列表設置適配器setListAdapter(new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_checked, Data.titles));//獲取布局文件中添加的幀布局管理器View detailFrame=getActivity().findViewById(R.id.detail);//判斷是否在一屏上同時顯示列表和詳細內容dualPane=detailFrame!=null&&detailFrame.getVisibility()==View.VISIBLE;if(savedInstanceState!=null){//更新當前的索引位置curCheckPosition=savedInstanceState.getInt("curChoice",0);}if(dualPane){//設置列表為單選模式 getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);showDetails(curCheckPosition);//顯示詳細內容 }}//該方法在STOP()之前執行,用于保存當前選中項的列表項的索引值 @Overridepublic void onSaveInstanceState(Bundle outState) {// TODO Auto-generated method stubsuper.onSaveInstanceState(outState);outState.putInt("curChoice", curCheckPosition);}@Overridepublic void onListItemClick(ListView l, View v, int position, long id) {// TODO Auto-generated method stubshowDetails(position);//顯示詳細內容 }private void showDetails(int index) {curCheckPosition=index;if(dualPane){getListView().setItemChecked(index, true);//設置選中狀態//獲取用于顯示詳細信息的FragmentDetailFragment df=(DetailFragment) getFragmentManager().findFragmentById(R.id.detail);if(df==null||df.getShownIndex()!=index){//創建一個新的DetailFragment實例,用于顯示當前選項對應的詳細內容df=DetailFragment.newInstance(index);//在Activity中管理fragment,需要使用FragmentManager//獲得一個FragmentTransaction實例FragmentTransaction ft=getFragmentManager().beginTransaction();//替換原來顯示的詳細內容 ft.replace(R.id.detail, df);//設置轉換效果 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);ft.commit();//提交事務 }}else{//豎屏Intent intent=new Intent(getActivity(),MainActivity.DetailActivity.class);intent.putExtra("index", index);startActivity(intent);}}}
?


package com.topcsa.test_fragment;import android.app.Activity; import android.app.ActionBar; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.res.Configuration; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.os.Build;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);}public static class DetailActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);// 判斷是否為橫屏if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {finish();return;}if (savedInstanceState == null) {//在初始化時,插入一個顯示詳細內容的Fragment//實例化DetailFragment對象DetailFragment detail = new DetailFragment();//設置傳遞的參數 detail.setArguments(getIntent().getExtras());//添加一個顯示詳細內容的Fragment getFragmentManager().beginTransaction().add(android.R.id.content, detail).commit();}}}}
?
最后,別忘了清單文件的配置:(內部Activity的注冊)
<activity android:name="com.topcsa.test_fragment.MainActivity$DetailActivity"android:label="詳細內容"></activity>
?
?
?新聞閱讀Demo下載:http://download.csdn.net/detail/af74776/7806353
?
?
本文重點參考了的文章(基本上算是大匯總吧):http://blog.csdn.net/lilu_leo/article/details/7671533
http://www.cnblogs.com/mengdd/archive/2013/01/08/2851368.html
http://www.cnblogs.com/yydcdut/p/3921297.html