當(dāng)今是移動(dòng)設(shè)備發(fā)展非常迅速的時(shí)代,不僅手機(jī)已經(jīng)成為了生活必需品,就連平板電腦也變得越來越普及。平板電腦和手機(jī)最大的區(qū)別就在于屏幕的大小,一般手機(jī)屏幕的犬小會在3英寸到 6英寸之間,而一般平板電腦屏幕的大小會在 7 英寸到 10英寸之間。屏幕犬小差距過大有可能會讓同樣的界面在視覺效果上有較大的差異,比如一些界面在手機(jī)上看起來非常美觀,但在平板電腦上看起來就可能會有控件被過分拉長、 元素之間空隙過大等情況。作為Android開發(fā)人員,能夠同時(shí)兼顧手機(jī)和平板的開發(fā)是必須做到的事情。
Android 自 3.0版本開始引人了Fragment的概念, 它可以讓界面在平板上更好地展示,下面我們就來一起學(xué)習(xí)一下。
1、什么是Fragment
碎片 (Fragment) 是一種可以嵌人在活動(dòng)當(dāng)中的 UI 片段,它能讓程序更加合理和充分地利用大屏幕的空間,因而在平板上應(yīng)用得非常廣泛。雖然碎片對我們來說應(yīng)該是個(gè)全新的概念, 但相信學(xué)習(xí)起來應(yīng)該亳不費(fèi)力,因?yàn)樗虯ctivity實(shí)在是太像了,同祥都能包含布局,同樣都有自己的生命周期。你甚至可以將碎片理解成一個(gè)迷你型的Activity,雖然這個(gè)迷你型的Activity有可能和普通的活動(dòng)是一樣大的。
那么究竟如何使用Fragment才能充分的利用平板的空間呢?官網(wǎng)給出了一個(gè)很好的例子,一個(gè)新聞客戶端:

在平板電腦尺寸的設(shè)備上運(yùn)行時(shí),該應(yīng)用可以在 Activity A 中嵌入兩個(gè)Fragment。 不過,在手機(jī)尺寸的屏幕上,沒有足以儲存兩個(gè)Fragment的空間,因此Activity A 只包括用于顯示文章列表的Fragment,當(dāng)用戶選擇文章時(shí),它會啟動(dòng)Activity B,其中包括用于閱讀文章的第二個(gè)Fragment。 因此,應(yīng)用可通過重復(fù)使用不同組合的Fragment來同時(shí)支持平板電腦和手機(jī)。
2、Fragment的使用方式
先寫一個(gè)最簡單的Fragment事例練手,在一個(gè)Activity中添加兩個(gè)Fragment,并讓這兩個(gè)Fragment平分Activity空間。
新建左側(cè)Fragment的布局fragment_left.xml:
<LinearLayout 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"
android:background="#ff00ff"
android:layout_gravity="center_horizontal"
tools:context="com.johnhao.listviewdemo.Fragment.LeftFragment">
<Button
android:id="@+id/btn_left_frag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Button"
android:textColor="#ff00ff"/>
</LinearLayout>
布局很簡單,放置一個(gè)Button并讓它水平居中。然后創(chuàng)建右側(cè)Fragment的布局文件fragment_right.xml:
<LinearLayout 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"
android:background="#00ff00"
tools:context="com.johnhao.listviewdemo.Fragment.RightFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/hello_blank_fragment" />
</LinearLayout>
接著創(chuàng)建一個(gè)LeftFragment類,并讓它繼承Fragment。這里我們建議使用support-v4庫中的android.support.v4.app.Fragment。因?yàn)檫@可以使Fragment在所有的Android系統(tǒng)版本中保持功能一致性。另外,我們不需要在build.gradle中添加support-v4庫的依賴,因?yàn)閎uild.gradle文件中已經(jīng)添加了appcpmat-v7庫的依賴,而這個(gè)庫會將support-v4庫一起引入進(jìn)來。
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_left, container, false);
}
}
這里僅僅是重寫了Fragment的onCreateView()方法,然后方法中通過LayoutInflater的inflate()方法將剛才定義的fragment_left布局動(dòng)態(tài)加載進(jìn)來。緊接著,我們用同樣的方法創(chuàng)建一個(gè)RightFragment:
public class RightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_right, container, false);
}
}
接下來,修改Activity的布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.johnhao.listviewdemo.Activity.FragmentSimActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.johnhao.listviewdemo.Fragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/right_fragment"
android:name="com.johnhao.listviewdemo.Fragment.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
可以看到,我們使用<fragment>標(biāo)簽在布局中添加Fragment,其中指定的屬性大多都學(xué)習(xí)過了。只不過這里還需要童工android:name屬性來顯式指明要添加的Fragment,這里一定要將類的包名也加上。
重新運(yùn)行一下程序:

2、動(dòng)態(tài)添加Fragment
剛才的例子過于簡單,不能體現(xiàn)出Fragment的強(qiáng)大之處,現(xiàn)在我們學(xué)習(xí)如何在程序運(yùn)行時(shí)動(dòng)態(tài)的添加Fragment。
我們新建一個(gè)Fragment的布局文件,fragment_another.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#ffff00">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="This is an another Fragment"
android:textSize="30sp"
android:textColor="#000000"/>
</LinearLayout>
這里我們設(shè)置了不同顏色的背景、文字和文案,用來和之前的Fragment做視覺上的區(qū)分。然后我們新建一個(gè)AnotherFragment類:
public class AnotherFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_another, container, false);
}
}
代碼內(nèi)容基本和上面的一致,接下來就是如何將它動(dòng)態(tài)的添加到Activity中。修改布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.johnhao.listviewdemo.Activity.FragmentAddActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.johnhao.listviewdemo.Fragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/right_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
可以看到和之前不同的是,我們把右側(cè)Fragment替換成了FrameLayout布局。這種布局默認(rèn)會把所有控件放在布局的左上角。由于這里僅需要在布局中加入一個(gè)Fragment,不需要任何定位,因此非常適合用FrameLayout。
接下來修改Activity代碼,向FrameLayout中動(dòng)態(tài)的添加Fragment:
public class FragmentAddActivity extends BaseActivity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_add);
btn = findViewById(R.id.btn_left_frag);
replaceFragment(new RightFragment());
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
replaceFragment(new AnotherFragment());
}
});
}
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.right_layout, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
}
首先我們給左側(cè)布局中Button注冊一個(gè)監(jiān)聽器,然后調(diào)用了replaceFragment()方法動(dòng)態(tài)添加了RightFragment。當(dāng)點(diǎn)擊左側(cè)Fragment中的Button時(shí)候,又會調(diào)用replaceFragment()方法將右側(cè)Fragment替換成AnotherFragment。從代碼中可以看出,動(dòng)態(tài)添加Fragment主要分為5步:
1)創(chuàng)建待添加的Fragment實(shí)例
2)獲取FragmentManager,在support-v4中是通過調(diào)用getSupportFragmentManager()方法獲得。
3)開啟一個(gè)事務(wù),通過調(diào)用beginTransaction()方法開啟
4)向容器內(nèi)添加或者替換Fragment,一般通過調(diào)用replace()方法實(shí)現(xiàn),需要傳入容器的id和待添加的Fragment
5)提交事務(wù),調(diào)用commit()方法來完成。
除此之外,我們還額外添加了一行代碼addToBackStack,這個(gè)方法是FragmentTransaction中提供的用于將一個(gè)事務(wù)添加到返回棧中的方法。如果沒有這行,當(dāng)我們點(diǎn)擊back的時(shí)候,直接就會退出Activity。而調(diào)用addToBackStack()方法后,按下back鍵之后,Activity并不會退出,而是退回到了RightFragment。然后按一下back鍵,RightFragment也會消失。最后再按一下back鍵,Activity才會退出。
重新運(yùn)行下程序:

3、Fragment和Activity之間進(jìn)行通信
雖然Fragment是嵌入在Activity中顯示的,可實(shí)際上他們的關(guān)系并沒有那么緊密。Fragment和Activity都是各自存在于一個(gè)獨(dú)立的類中,為了方便Fragment和Activity進(jìn)行通信,F(xiàn)ragmentManager提供一個(gè)類似于findViewById()的方法,專門用于從布局文件中獲取Fragment的實(shí)例:
RightFragment rightFragment = (RightFragment)getSupportFragmentManager().findFragmentById(R.id.right_fragment);
調(diào)用FragmentManager的findFragmentById()方法,可以在Activity中得到相應(yīng)Fragment的實(shí)例,然后就能輕松的調(diào)用Fragment里的方法了。
相反的,在Fragment中可以通過調(diào)用getActivity()方法來得到和當(dāng)前Fragment相關(guān)聯(lián)的Activity的實(shí)例:
FragmentAddActivity activity = (FragmentAddActivity) getActivity();
有了活動(dòng)實(shí)例,在Fragment中調(diào)用Activity里的方法也就變得容易了。當(dāng)然Fragment和Activity之間的通信還有其他幾種方式實(shí)現(xiàn),這些我們?nèi)蘸笤俾龑W(xué)習(xí),這里只做個(gè)拋磚引玉。
4、Fragment的生命周期
管理Fragment生命周期與管理 Activity 生命周期很相似。和 Activity 一樣,F(xiàn)ragment也以三種狀態(tài)存在:
□ 運(yùn)行狀態(tài):
當(dāng)一個(gè)Fragment是可見的,并且他所關(guān)聯(lián)的Activity正處于運(yùn)行狀態(tài)是,該Fragment也處于運(yùn)行狀態(tài)。
□ 暫停狀態(tài):
當(dāng)一個(gè)Activity進(jìn)入暫停狀態(tài)是,與它關(guān)聯(lián)的Fragment就會進(jìn)入到暫停狀態(tài)。
□ 停止?fàn)顟B(tài):
當(dāng)一個(gè)Activity進(jìn)入停止?fàn)顟B(tài)是,與它關(guān)聯(lián)的Fragment就會進(jìn)入停止?fàn)顟B(tài),或者通過調(diào)用FragmentTransaction的remove()、replace()方法將Fragment從Activity中移除,但如果在提交事物之前調(diào)用addToBackStack()方法,這時(shí)的Fragment也會進(jìn)入到停止?fàn)顟B(tài)。

圖片為Activity生命周期對Fragment生命周期的影響。那么接下來就了解下Fragment的生命周期:

還是從代碼入手,通過打印Log的方式,更加直觀的體驗(yàn)Fragment的生命周期。我們在動(dòng)態(tài)添加Fragment的練習(xí)中修改,修改RightFragment類:
public class RightFragment extends Fragment{
private static final String TAG = "RightFragment";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.d(TAG, "onCreateView: ");
return inflater.inflate(R.layout.fragment_right, container, false);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG, "onAttach: ");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: ");
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated: ");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView: ");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach: ");
}
}
我們在Fragment的每一個(gè)回調(diào)方法里都添加了LOG,重新運(yùn)行程序,觀察logcat的日志:

可以看到,當(dāng)RightFragment第一次被加載到屏幕上時(shí),會依次執(zhí)行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onStart()、onResume()方法。

然后點(diǎn)擊左側(cè)LeftFragment中的按鈕,加載AnotherFragment,由于AnotherFragment替換掉了RightFragment,此時(shí)RightFragment進(jìn)入了暫停狀態(tài),因此onPause()、onStop()、onDestroyView()方法得到了執(zhí)行。當(dāng)然,如果在提交事物之前沒有調(diào)用addTBackStack()方法的話,RightFragment就會進(jìn)入到destroy狀態(tài)。

由于代碼中我們調(diào)用了addTBackStack()方法,點(diǎn)擊back鍵從AnotherFragment回到RightFragment。由于RightFragment重新回到了運(yùn)行狀態(tài),此時(shí)onAttach()、onCreate()方法不會被執(zhí)行,因?yàn)槲覀兘柚薬ddTBackStack()方法使得RightFragment沒有被銷毀。

再次按下back鍵,我們會看到RightFragment進(jìn)入了銷毀階段,依次執(zhí)行了onPause()、onStop()、onDestroyView()、onDestroy()、onDetach()方法。
結(jié)合前面的圖,是不是對Fragment的生命周期理解的更深刻了呢。

另外值得一提的是,在Fragment中也可以通過onSavedInstanceState()方法來保存數(shù)據(jù),因?yàn)檫M(jìn)入stop狀態(tài)的Fragment可能會在內(nèi)部不足時(shí)被系統(tǒng)回收。保存下來的數(shù)據(jù)在onCreate()、onActivityCreated()、onCreateView()方法中可以重新得到,它們都包含有Bundle類型的savedInstanceState參數(shù)。