《Android Fragment完全解析,關(guān)于碎片你所需知道的一切》
我們都知道,Android上的界面展示都是通過Activity實現(xiàn)的,
但是Activity也有它的局限性,同樣的界面在手機上顯示可能很好看,在平板上就未必了,因為平板的屏幕非常大,手機的界面放在平板上可能會有過分被拉大、控件間距過大等情況。這個時候更好的體驗效果是在Activity中嵌入“小Activity”,然后每個“小Activity”又可以擁有自己的布局。因此有了Fragment。
Fragment初探
為了讓界面在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能的,它非常類似于Activity,可以像Activity一樣包含布局。Fragment通常是嵌套在Activity中使用的,現(xiàn)在想象這種場景:有兩個Fragment,F(xiàn)ragment1包含了一個ListView,每行顯示一本書的標(biāo)題。Fragment2包含了TextView和ImageView,來顯示書的詳細(xì)內(nèi)容和圖片。
如果現(xiàn)在程序運行豎屏模式的平板或手機上,Fragment 1可能嵌入在一個Activity中,而Fragment 2可能嵌入在另一個Activity中,如下圖所示:

而如果現(xiàn)在程序運行在橫屏模式的平板上,兩個Fragment就可以嵌入在同一個Activity中了,如下圖所示:
由此可以看出,使用Fragment可以讓我們更加充分地利用平板的屏幕空間。
需要注意,Fragment是在3.0版本引入的,如果你使用的是3.0之前的系統(tǒng),需要先導(dǎo)入android-support-v4的jar包才能使用Fragment功能。
靜態(tài)使用Fragment
把Fragment當(dāng)成普通的控件,直接寫在Activity的布局文件中。步驟:
1、繼承Fragment,重寫onCreateView決定Fragemnt的布局
2、在Activity中聲明此Fragment,就當(dāng)和普通的View一樣
新建兩個分別名為fragment1和fragment2的xml布局文件
然后新建一個類Fragment1,這個類是繼承自Fragment的:
同樣建立Fragment2.
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}
然后打開或新建activity_main.xml作為主Activity的布局文件,在里面加入兩個Fragment的引用,使用android:name前綴來引用具體的Fragment。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false" >
<fragment
android:id="@+id/fragment1"
android:name="com.example.fragmentdemo.Fragment1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragment2"
android:name="com.example.fragmentdemo.Fragment2"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
動態(tài)添加Fragment
在XML中使用Fragment,這僅僅是Fragment最簡單的功能而已。Fragment的真正強大之處在于可以動態(tài)地添加到Activity當(dāng)中。
在上面的基礎(chǔ)上修改。打開activity_main.xml,將其中對Fragment的引用都刪除,只保最外層的LinearLayout,并給它添加一個id.因為我們要動態(tài)添加Fragment,不用在XML里添加了。
在MainActivity,修改其中代碼
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Display display = getWindowManager().getDefaultDisplay();
if (display.getWidth() > display.getHeight()) {
Fragment1 fragment1 = new Fragment1();
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();
} else {
Fragment2 fragment2 = new Fragment2();
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();
}
}
}
首先,我們要獲取屏幕的寬度和高度,然后進(jìn)行判斷,如果屏幕寬度大于高度就添加fragment1,如果高度大于寬度就添加fragment2.動態(tài)添加fragment主要分為4步。
- 獲取到FragmentManager,在Activity中可以直接通過getFragmentManager得到。
- 開啟一個事務(wù),通過調(diào)用beginTransaction方法開啟。
- 向容器內(nèi)加入Fragment,一般使用replace方法實現(xiàn),需要傳入容器的id和Fragment的實例。
- 提交事務(wù)。
Fragment的生命周期
因為Fragment必須嵌入在Activity中使用,所以Fragment的生命周期和它所處的Activity是密切相關(guān)的。
如果Activity是暫停狀態(tài),其中所有的Fragment都是暫停狀態(tài);如果Activity是stopped狀態(tài),這個Activity中所有的Fragment都不能被啟動;如果Activity被銷毀,那么它其中的所有Fragment都會被銷毀。
但是,當(dāng)Activity在活動狀態(tài),可以獨立控制Fragment的狀態(tài),比如加上或者移除Fragment。
使用Fragment時,需要繼承Fragment或者Fragment的子類(DialogFragment, ListFragment, PreferenceFragment, WebViewFragment),所以Fragment的代碼看起來和Activity的類似。
必須實現(xiàn)的三個回調(diào)函數(shù):
onCreate()
系統(tǒng)在創(chuàng)建Fragment的時候調(diào)用這個方法,這里應(yīng)該初始化相關(guān)的組件,一些即便是被暫?;蛘弑煌V箷r依然需要保留的東西。
onCreateView()
當(dāng)?shù)谝淮卫L制Fragment的UI時系統(tǒng)調(diào)用這個方法,必須返回一個View,如果Fragment不提供UI也可以返回null。
注意,如果繼承自ListFragment,onCreateView()默認(rèn)的實現(xiàn)會返回一個ListView,所以不用自己實現(xiàn)。
onPause()
當(dāng)用戶離開Fragment時第一個調(diào)用這個方法,需要提交一些變化,因為用戶很可能不再返回來。
Fragment和Activity的生命周期非常相似,只有幾個Activity中沒有的新方法,如下:
onAttach方法:Fragment和Activity建立關(guān)聯(lián)的時候調(diào)用。
onCreateView方法:為Fragment加載布局時調(diào)用。
onActivityCreated方法:當(dāng)Activity中的onCreate方法執(zhí)行完后調(diào)用。
onDestroyView方法:Fragment中的布局被移除時調(diào)用。
onDetach方法:Fragment和Activity解除關(guān)聯(lián)的時候調(diào)用。
注意:除了onCreateView,其他的所有方法如果你重寫了,必須調(diào)用父類對于該方法的實現(xiàn),
Fragment之間進(jìn)行通信
通常情況下,Activity中都會包含多個Fragment,這時多個Fragment之間如何進(jìn)行通信就是個非常重要的問題了。
主要都是通過getActivity這個方法實現(xiàn)的。getActivity方法可以讓Fragment獲取到關(guān)聯(lián)的Activity,然后再調(diào)用Activity的findViewById方法,就可以獲取到和這個Activity關(guān)聯(lián)的其它Fragment的視圖了。
Fragment是什么,生命周期、靜態(tài)和動態(tài)使用,
Fragment如何與Activity交互?Fragment如何創(chuàng)建對話框?Fragment如何與ActionBar集成等等。
Fragment家族常用的API
android.app.Fragment主要用于定義Fragment
android.app.FragmentManager 主要用于在Activity中操作Fragment
android.app.FragmentTransaction 保證一系列Fragment操作的原子性
a.獲取FragmentManager的方式
getFragmentManager(); //v4中,getSupportFragmentManager
b. 主要的操作都是FragmentTransaction
FragmentTransaction transaction = fm.beginTransaction();//開啟一個事務(wù)
- transaction.add()
往Activity中添加一個Fragment
- transaction.remove()
從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧,這個Fragment實例將會被銷毀。 - transaction.replace()
使用另一個Fragment替換當(dāng)前的,實際上就是remove()然后add()的合體 - transaction.hide()
隱藏當(dāng)前的Fragment(),僅僅是設(shè)為不可見,并不會銷毀。 - transaction.show()
顯示之前隱藏的Fragment - detach()
會將view從UI中移除,和remove()不同,此時的Fragment的狀態(tài)依然由FragmentManager維護(hù) - attach()
重建view視圖,附加到UI上并顯示。 - transatcion.commit()//提交一個事務(wù)
注意:常用Fragment的哥們,可能會經(jīng)常遇到這樣Activity狀態(tài)不一致:State loss這樣的錯誤。主要是因為:commit方法一定要在Activity.onSaveInstance()之前調(diào)用。
上述,基本是操作Fragment的所有的方式了,在一個事務(wù)開啟到提交可以進(jìn)行多個的添加、移除、替換等操作。
值得注意的是:如果你喜歡使用Fragment,一定要清楚這些方法,哪個會銷毀視圖,哪個會銷毀實例,哪個僅僅只是隱藏,這樣才能更好的使用它們。
a、比如:我在FragmentA中的EditText填了一些數(shù)據(jù),當(dāng)切換到FragmentB時,如果希望會到A還能看到數(shù)據(jù),則適合你的就是hide和show;也就是說,希望保留用戶操作的面板,你可以使用hide和show,當(dāng)然了不要使勁在那new實例,進(jìn)行下非null判斷。
b、再比如:我不希望保留用戶操作,你可以使用remove(),然后add();或者使用replace()這個和remove,add是相同的效果。
c、remove和detach有一點細(xì)微的區(qū)別,在不考慮回退棧的情況下,remove會銷毀整個Fragment實例,而detach則只是銷毀其視圖結(jié)構(gòu),實例并不會被銷毀。那么二者怎么取舍使用呢?如果你的當(dāng)前Activity一直存在,那么在不希望保留用戶操作的時候,你可以優(yōu)先使用detach。
管理Fragment回退棧
類似于Android系統(tǒng)為Activity維護(hù)一個任務(wù)棧,我們也可以通過Activity維護(hù)一個回退棧來保存每次Fragment事務(wù)發(fā)生的變化。如果你將Fragment任務(wù)添加到回退棧,當(dāng)用戶點擊后退按鈕時,將看到上一次的保存的Fragment。一旦Fragment完全從后退棧中彈出,用戶再次點擊后退鍵,則退出當(dāng)前Activity。
添加一個Fragment事務(wù)到回退棧
FragmentTransaction.addToBackStack(String)
eg. Activity的布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/id_content"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</FrameLayout>
</RelativeLayout>
不同的Fragment就在這個FrameLayout中顯示。
MainActivity
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, new FragmentOne(),"ONE");
tx.commit();
}
}
Fragment與Activity通信
因為所有的Fragment都是依附于Activity的,所以通信起來并不復(fù)雜,大概歸納為:
a、如果你Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法
b、如果Activity中未保存任何Fragment的引用,那么沒關(guān)系,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然后進(jìn)行操作。
c、在Fragment中可以通過getActivity得到當(dāng)前綁定的Activity的實例,然后進(jìn)行操作。
注:如果在Fragment中需要Context,可以通過調(diào)用getActivity(),如果該Context需要在Activity被銷毀后還存在,則使用getActivity().getApplicationContext()。
Fragment與Activity通信的最佳實踐
因為要考慮Fragment的重復(fù)使用,所以必須降低Fragment與Activity的耦合,而且Fragment更不應(yīng)該直接操作別的Fragment,畢竟Fragment操作應(yīng)該由它的管理者Activity來決定
首先看FragmentOne
public class FragmentOne extends Fragment implements OnClickListener{
private Button mBtn;
/**
* 設(shè)置按鈕點擊的回調(diào)
* @author zhy
*
*/
public interface FOneBtnClickListener {
void onFOneBtnClick();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
View view =inflater.inflate(R.layout.fragment_one, container, false);
mBtn = (Button)view.findViewById(R.id.id_**fragment**_one_btn);
mBtn.setOnClickListener(this);
return view;
}
/**
* 交給宿主Activity處理,如果它希望處理
*/
@Override
public void onClick(View v)
{
if (getActivity() instanceof FOneBtnClickListener)
{
((FOneBtnClickListener)getActivity()).onFOneBtnClick();
}
}
}
FragmentTwo代碼
public class FragmentTwo extends Fragment implements OnClickListener{
private Button mBtn ;
private FTwoBtnClickListener fTwoBtnClickListener ;
public interface FTwoBtnClickListener
{
void onFTwoBtnClick();
}
//設(shè)置回調(diào)接口
public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)
{
this.fTwoBtnClickListener = fTwoBtnClickListener;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.**fragment**_two, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
mBtn.setOnClickListener(this);
return view ;
}
@Override
public void onClick(View v)
{
if(fTwoBtnClickListener != null)
{
fTwoBtnClickListener.onFTwoBtnClick();
}
}
}
與FragmentOne極其類似,但是我們提供了setListener這樣的方法,意味著Activity不僅需要實現(xiàn)該接口,還必須顯示調(diào)用mFTwo.setfTwoBtnClickListener(this)。
可以看到現(xiàn)在的FragmentOne不和任何Activity耦合,任何Activity都可以使用;并且我們聲明了一個接口,來回調(diào)其點擊事件,想要管理其點擊事件的Activity實現(xiàn)此接口就即可??梢钥吹轿覀冊趏nClick中首先判斷了當(dāng)前綁定的Activity是否實現(xiàn)了該接口,如果實現(xiàn)了則調(diào)用
如何處理運行時配置發(fā)生變化
Android 屏幕旋轉(zhuǎn) 處理 AsyncTask 和 ProgressDialog 的最佳方案
當(dāng)屏幕發(fā)生旋轉(zhuǎn),Activity發(fā)生重新啟動,默認(rèn)的Activity中的Fragment也會跟著Activity重新創(chuàng)建;這樣造成當(dāng)旋轉(zhuǎn)的時候,本身存在的Fragment會重新啟動,然后當(dāng)執(zhí)行Activity的onCreate時,又會再次實例化一個新的Fragment。
如何解決呢?
其實通過檢查onCreate的參數(shù)Bundle savedInstanceState就可以判斷,當(dāng)前是否發(fā)生Activity的重新創(chuàng)建:
默認(rèn)的savedInstanceState會存儲一些數(shù)據(jù),包括Fragment的實例。所以,我們簡單改一下代碼,只有在savedInstanceState==null時,才進(jìn)行創(chuàng)建Fragment實例:
使用Fragment創(chuàng)建對話框
Android 官方推薦 : DialogFragment 創(chuàng)建對話框
Android Fragment 你應(yīng)該知道的一切
1. 概述
一般情況下,我們在Activity里面會這么添加Fragment:
public class MainActivity extends FragmentActivity{
private ContentFragment mContentFragment ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
mContentFragment = (ContentFragment)
fm.findFragmentById(R.id.id_fragment_container);
if(mContentFragment == null )
{
mContentFragment = new ContentFragment();
fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
}
}
}
針對上面代碼,問兩個問題:
1、為什么需要判null呢?
主要是因為,當(dāng)Activity因為配置發(fā)生改變(屏幕旋轉(zhuǎn))或者內(nèi)存不足被系統(tǒng)殺死,造成重新創(chuàng)建時,我們的fragment會被保存下來,但是會創(chuàng)建新的FragmentManager,新的FragmentManager會首先會去獲取保存下來的fragment隊列,重建fragment隊列,從而恢復(fù)之前的狀態(tài)。
2、add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用?
一方面呢,是告知FragmentManager,此fragment的位置;另一方面是此fragment的唯一標(biāo)識;就像我們上面通過fm.findFragmentById(R.id.id_fragment_container)查找
2. Fragment Arguments
需要通過Intent傳遞參數(shù)到目標(biāo)Activity的Fragment中,那么此Fragment如何獲取當(dāng)前的Intent的值呢(考慮解耦的情況下)?
public class ContentFragment extends Fragment{
private String mArgument;
public static final String ARGUMENT = "argument";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); // mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
Bundle bundle = getArguments();
if (bundle != null)
mArgument = bundle.getString(ARGUMENT);
}
/**
* 傳入需要的參數(shù),設(shè)置給arguments
* @param argument
* @return
*/
public static ContentFragment newInstance(String argument)
{
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, argument);
ContentFragment contentFragment = new ContentFragment();
contentFragment.setArguments(bundle);
return contentFragment;
}
給Fragment添加newInstance方法,將需要的參數(shù)傳入,設(shè)置到bundle中,然后setArguments(bundle),最后在onCreate中進(jìn)行獲取;
這樣就完成了Fragment和Activity間的解耦。當(dāng)然了這里需要注意:
setArguments方法必須在fragment創(chuàng)建以后,添加給Activity前完成。千萬不要,首先調(diào)用了add,然后設(shè)置arguments。
3. Fragment的startActivityForResult
我們點擊跳轉(zhuǎn)到對應(yīng)Activity的Fragment中,并且希望它能夠返回參數(shù)。
在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是,沒有setResult()方法,用于設(shè)置返回的intent,這樣我們就需要通過調(diào)用getActivity.setResult(ListTitleFrament.REQUEST_DETAIL, intent);
fragment能夠從Activity中接收返回結(jié)果,但是其自設(shè)無法產(chǎn)生返回結(jié)果,只有Activity擁有返回結(jié)果。
5. FragmentPaperAdapter與FragmentStatePagerAdapter
相信這兩個PagerAdapter的子類,大家都不陌生吧~~自從Fragment問世,使用ViewPager再結(jié)合上面任何一個實例的制作APP主頁的案例特別多
那么這兩個類有何區(qū)別呢?
主要區(qū)別就在與對于fragment是否銷毀,下面細(xì)
說:
FragmentPagerAdapter:對于不再需要的fragment,選擇調(diào)用detach方法,僅銷毀視圖,并不會銷毀fragment實例。
FragmentStatePagerAdapter:會銷毀不再需要的fragment,當(dāng)當(dāng)前事務(wù)提交以后,會徹底的將fragmeng從當(dāng)前Activity的FragmentManager中移除,state標(biāo)明,銷毀時,會將其onSaveInstanceState(Bundle outState)中的bundle信息保存下來,當(dāng)用戶切換回來,可以通過該bundle恢復(fù)生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中保存一些數(shù)據(jù),在onCreate中進(jìn)行恢復(fù)創(chuàng)建。
如上所說,使用FragmentStatePagerAdapter當(dāng)然更省內(nèi)存,但是銷毀新建也是需要時間的。一般情況下,如果你是制作主頁面,就3、4個Tab,那么可以選擇使用FragmentPagerAdapter,如果你是用于ViewPager展示數(shù)量特別多的條目時,那么建議使用FragmentStatePagerAdapter。
參考: