平板電腦和手機(jī)最大的區(qū)別就是屏幕的大小不同,一般手機(jī)屏幕的大小會(huì)在3英寸到6英寸之間劲蜻,而一般平板電腦屏幕的大小會(huì)在7英寸到10英寸之間浸剩。屏幕大小差距過大有可能會(huì)讓同樣的界面在視覺效果上有較大的差異,比如一些界面在手機(jī)上看起來非常美觀,但在平板電腦上看起來就可能會(huì)有控件被過分拉長息裸、元素之間空隙過大等情況。
作為一名Android開發(fā)人員,能夠同時(shí)兼顧手機(jī)和平板的開發(fā)是我們必須做到的事情跃赚,Android 3.0版本開始引入了碎片的概念,它可以讓界面在平板上更好地展示报咳,下面我們就來學(xué)習(xí)一下宵膨。
1. 碎片是什么
碎片和活動(dòng)非常像,都能包含布局幻件,同樣都有自己的生命周期,是一種可以嵌入在活動(dòng)當(dāng)中的UI片段奥此,它能讓程序更加合理和充分地利用大屏幕的空間茴她,因而在平板上應(yīng)用得非常廣泛泛粹。那如何使用碎片充分利用平板屏幕的空間呢蒙挑?
想象我們正在開發(fā)一個(gè)新聞應(yīng)用欣鳖。其中一個(gè)界面使用recyclerview展示一組新聞標(biāo)題胰坟,當(dāng)點(diǎn)擊了其中一個(gè)標(biāo)題時(shí)厢塘,就打開另一個(gè)界面顯示新聞的詳細(xì)內(nèi)容懂诗。如果是在手機(jī)中設(shè)計(jì)泡躯,我們可以將新聞標(biāo)題列表放在一個(gè)活動(dòng)中,將新聞的詳細(xì)內(nèi)容放在另一個(gè)活動(dòng)中。但是平板屏幕太大,這樣設(shè)計(jì)肯定不合理,會(huì)導(dǎo)致界面上有大量的空白區(qū)域。
因此更好的設(shè)計(jì)方案是將新聞標(biāo)題列表界面和新聞詳細(xì)內(nèi)容界面分別放在兩個(gè)碎片中岂座,然后在同一個(gè)活動(dòng)中引入這兩個(gè)碎片拱礁,這樣就可以將屏幕空間充分地利用起來了细诸。
2. 在碎片中模擬返回棧
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentmanager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentmanager.beginTransaction();
transaction.replace(R.id.right_layout,fragment);
transaction.addToBackStack(null);
transaction.commit();
}
*** 重新運(yùn)行程序博敬,并點(diǎn)擊按鈕將AnotherRightFragment添加到活動(dòng)中,然后按下Back鍵,就會(huì)跳轉(zhuǎn)到RightFragment界面岩榆,繼續(xù)按下Back鍵,RightFragment界面也會(huì)消失齐苛。
3. 碎片的使用方式
- 靜態(tài)加載
- 啟動(dòng)平板模擬器官辽,新建FramentTest項(xiàng)目,創(chuàng)建兩個(gè)布局left_fragment和right_fragment
left_fragment
<?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="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button" />
</LinearLayout>
right_fragment
<?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:background="#00ff00"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="This is right fragment"
android:textSize="20sp" />
</LinearLayout>
- 新建兩個(gè)類LeftFragment和RightFragment:
LeftFragment
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.left_fragment,container,false);
return view;
}
}
RightFragment
public class RightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment,container,false);
return view;
}
}
注意:LeftFragment和RightFragment繼承Fragmnet忆某。這個(gè)Fragment可能會(huì)有兩個(gè)不同包下的Fragment供選擇状原,一個(gè)是系統(tǒng)內(nèi)置的android.app.Fragment,一個(gè)是support-v4庫中的android.support.v4.app.Fragment.這里強(qiáng)烈建議使用support-v4庫中的Fragment蛹稍,因?yàn)樗梢宰屗槠谒蠥ndrid系統(tǒng)版本中保持功能一致性朝卒。比如說Fragment中嵌套使用Fragment乐埠,這個(gè)功能是在Android 4.2系統(tǒng)中才開始支持的抗斤。如果你使用的是系統(tǒng)內(nèi)置的Fragment囚企,那么很遺憾,4.2系統(tǒng)之前的設(shè)備運(yùn)行你的程序就會(huì)崩潰瑞眼。而使用support-v4庫中的Fragment就不會(huì)出現(xiàn)這個(gè)問題龙宏,只要你保證使用 是最新的support-v4庫就可以了。
- activity_main中引入frament:
<?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/left_fragment"
android:name="com.beidou.fragmenttest.LeftFragment"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"></fragment>
<fragment
android:id="@+id/right_fragment"
android:name="com.beidou.fragmenttest.RightFragment"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"></fragment>
</LinearLayout>
注意:我們使用了<fragment>標(biāo)簽在布局中添加碎片伤疙,其中指定的大多數(shù)屬性都熟悉银酗,只不過還需要通過android:name屬性來顯示指明要添加的碎片類名,注意一定也要將類的包名也加上徒像。
- 動(dòng)態(tài)加載
- 在原有的項(xiàng)目的基礎(chǔ)上繼續(xù)完善黍特,新建another_right_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:background="#ffff00"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="This is another right fragment"
android:textSize="20sp" />
</LinearLayout>
**** 布局的內(nèi)容和right_fragment內(nèi)容基本相同,只是將背景色改成了黃色厨姚,并將顯示的文字改了改衅澈。
- 新建AnotherRightFragment,進(jìn)行填充布局:
public class AnotherRightFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.another_right_fragment,container,false);
return view;
}
}
- 修改activity_main.xml谬墙,添加FrameLayout布局
<?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/left_fragment"
android:name="com.beidou.fragmenttest.LeftFragment"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"></fragment>
<FrameLayout
android:id="@+id/right_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"></FrameLayout>
</LinearLayout>
- 向rameLayout布局中動(dòng)態(tài)添加碎片今布,修改Main_Activity中的代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
replaceFragment(new RightFragment());
}
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentmanager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentmanager.beginTransaction();
transaction.replace(R.id.right_layout,fragment);
transaction.commit();
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.button:
replaceFragment(new AnotherRightFragment());
break;
}
}
}
**** 首先我們給左側(cè)碎片中的按鈕注冊(cè)了一個(gè)點(diǎn)擊事件,然后調(diào)用replaceFragment方法動(dòng)態(tài)添加了RightFragment這個(gè)碎片拭抬。當(dāng)點(diǎn)擊左側(cè)碎片中的按鈕時(shí)部默,又會(huì)調(diào)用 replaceFragment方法將RightFragment碎片替換成AnotherRightFragment。
- 從以上可以看出動(dòng)態(tài)添加代碼主要分為5步:
① 創(chuàng)建待添加的碎片實(shí)例造虎。
② 獲取FragmentManager傅蹂,在活動(dòng)中可以直接調(diào)用getSupportFragmentManager方法得到。
③ 開啟一個(gè)事務(wù)算凿,通過調(diào)用beginTransaction方法開啟份蝴。
④ 向容器內(nèi)添加或者替換碎片,一般使用replace方法實(shí)現(xiàn)氓轰,需要傳入容器的id和待添加的碎片實(shí)例婚夫。
⑤ 提交事務(wù),調(diào)用commit方法來完成署鸡。
4. 碎片和活動(dòng)之間的通信
所謂通信案糙,無非即是在活動(dòng)中調(diào)用碎片里面的方法,或者在碎片中調(diào)用活動(dòng)里面的方法靴庆。
- 活動(dòng)中調(diào)用碎片的方法
LeftFragment leftFragment = (LeftFragment)
getSupportFragmentManager().findFragmentById(R.id.left_fragment);
*** 獲取相應(yīng)碎片實(shí)例时捌,然后就能調(diào)用碎片里面的方法了。
- 碎片中調(diào)用活動(dòng)的方法
MainActivity activity = (MainActivity)getActivity();
*** 有了活動(dòng)實(shí)例炉抒,就可以調(diào)用活動(dòng)里面的方法奢讨。
5. 碎片與碎片之間的通信原理
首先在一個(gè)碎片中可以得到與它關(guān)聯(lián)的活動(dòng),然后再通過這個(gè)活動(dòng)去獲取另外一個(gè)碎片的實(shí)例既可焰薄。
6. 碎片的狀態(tài)
活動(dòng)有四種狀態(tài):運(yùn)行狀態(tài)拿诸、暫停狀態(tài)入录、停止?fàn)顟B(tài)、銷毀狀態(tài)佳镜。碎片在其生命周期內(nèi)也可能會(huì)經(jīng)歷這幾種狀態(tài),只不過在一些細(xì)小的地方會(huì)有部分區(qū)別凡桥。
- 運(yùn)行狀態(tài)
當(dāng)一個(gè)碎片是可見的蟀伸,并且它所關(guān)聯(lián)的活動(dòng)正處于運(yùn)行狀態(tài),該碎片也處于運(yùn)行狀態(tài)缅刽。 - 暫停狀態(tài)
活動(dòng)進(jìn)入暫停狀態(tài)時(shí)(由于另一個(gè)未占滿屏幕的活動(dòng)被添加到了棧頂)啊掏,與它相關(guān)聯(lián)的可見碎片就會(huì)進(jìn)入到暫停狀態(tài)。 - 停止?fàn)顟B(tài)
① 當(dāng)一個(gè)活動(dòng)進(jìn)入停止?fàn)顟B(tài)時(shí)衰猛,與它相關(guān)聯(lián)的碎片就會(huì)進(jìn)入到停止?fàn)顟B(tài)迟蜜。
② 通過調(diào)用FragmentTransaction的remove()、replace()方法將碎片從活動(dòng)中移除啡省,但如果在事務(wù)提交之前調(diào)用addToBackStack()方法娜睛,這時(shí)的碎片也會(huì)進(jìn)入到停止?fàn)顟B(tài)。此時(shí)碎片不可見卦睹,可能被系統(tǒng)回收畦戒。 - 銷毀狀態(tài)
① 碎片總是依附于活動(dòng)而存在的,因此當(dāng)活動(dòng)被銷毀時(shí)结序,與它相關(guān)聯(lián)的碎片就會(huì)進(jìn)入到銷毀狀態(tài)障斋。
② 通過調(diào)用FragmentTransaction的remove()、replace()方法將碎片從活動(dòng)中移除徐鹤,但是在事務(wù)提交之前并沒有調(diào)用addToBackStack()方法垃环,這時(shí)的碎片會(huì)進(jìn)入到銷毀狀態(tài)。
7. 碎片的回調(diào)
活動(dòng)的回調(diào)返敬,碎片中都有遂庄,并且碎片還有附加的回調(diào)方法,我們著重看下救赐。
① onAttach()當(dāng)碎片和活動(dòng)建立關(guān)聯(lián)的時(shí)候調(diào)用涧团。
② onCreateView()當(dāng)碎片創(chuàng)建視圖(加載布局)的時(shí)候調(diào)用。
③ onActivityCreated()確保與碎片相關(guān)聯(lián)的活動(dòng)一定已經(jīng)創(chuàng)建完畢的時(shí)候調(diào)用经磅。
④ onDestoryView()當(dāng)與碎片關(guān)聯(lián)的視圖被移除的時(shí)候調(diào)用泌绣。
⑤ onDetach()當(dāng)碎片和活動(dòng)接觸關(guān)聯(lián)的時(shí)候調(diào)用。
8. 碎片的完整生命周期
添加一個(gè)碎片——onAttach()——onCreate()——onCreateView()——onActivityCreated()——onStart()——onResume()——碎片已激活
碎片被激活后预厌,有兩種情況:
第一種:用戶點(diǎn)擊返回鍵或者碎片被移除/替換——onPause()——onStop()——onDestoryView()——onDestory()——onDetach()——碎片被銷毀
第二種:當(dāng)碎片被添加到返回棧阿迈,然后被移除/替換——onPause()——onStop()——onDestoryView()——從返回棧中回到上一個(gè)碎片——重新執(zhí)行onCreateView()方法
9. 體驗(yàn)碎片的生命周期(依然是在FragmentTest項(xiàng)目上做改動(dòng))
- 修改RightFragment的代碼如下:
public class RightFragment extends Fragment {
private static final String TAG = "RightFragment";
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d(TAG,"onAttach");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG,"onCreateView");
View view = inflater.inflate(R.layout.right_fragment,container,false);
return view;
}
@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");
}
}
*** 第一次加載的時(shí)候打印出的日志:
2589-2589/com.beidou.fragmenttest D/RightFragment: onAttach
2589-2589/com.beidou.fragmenttest D/RightFragment: onCreate
2589-2589/com.beidou.fragmenttest D/RightFragment: onCreateView
2589-2589/com.beidou.fragmenttest D/RightFragment: onActivityCreated
2589-2589/com.beidou.fragmenttest D/RightFragment: onStart
2589-2589/com.beidou.fragmenttest D/RightFragment: onResume
*** 點(diǎn)擊LeftFragment的時(shí)候打印出的日志:
com.beidou.fragmenttest D/RightFragment: onPause
com.beidou.fragmenttest D/RightFragment: onStop
com.beidou.fragmenttest D/RightFragment: onDestroyView
com.beidou.fragmenttest D/RightFragment: onDestroy
com.beidou.fragmenttest D/RightFragment: onDetach
*** AnotherRightFragment替換了RightFragment,此時(shí)的RightFragment進(jìn)入了停止?fàn)顟B(tài)轧叽,因此執(zhí)行了onPause苗沧、onStop刊棕、onDestoryView、onDestory待逞、onDetach方法甥角。假如替換的時(shí)候調(diào)用了addToBackStack方法,此時(shí)的RightFragment就會(huì)進(jìn)入停止?fàn)顟B(tài)识樱,不會(huì)執(zhí)行onDestory和onDetach方法嗤无。
調(diào)用addToBackStack方法的時(shí)候
16600-16600/com.beidou.fragmenttest D/RightFragment: onPause
16600-16600/com.beidou.fragmenttest D/RightFragment: onStop
16600-16600/com.beidou.fragmenttest D/RightFragment: onDestroyView
*** 如果替換的時(shí)候調(diào)用了addToBackStack方法,點(diǎn)擊返回鍵怜庸,RightFragment重新回到了運(yùn)行狀態(tài)当犯,因此會(huì)執(zhí)行onCreateView、onActivityCreated割疾、onStart和onResume方法(一定要注意:郭霖在這里寫錯(cuò)了嚎卫,郭霖寫的是由于調(diào)用了addToBackStack方法,onCreate和onCreateView并不會(huì)執(zhí)行)
16600-16600/com.beidou.fragmenttest D/RightFragment: onCreateView
16600-16600/com.beidou.fragmenttest D/RightFragment: onActivityCreated
16600-16600/com.beidou.fragmenttest D/RightFragment: onStart
16600-16600/com.beidou.fragmenttest D/RightFragment: onResume
*** 再按下Back鍵宏榕,依次會(huì)執(zhí)行onPause拓诸、onStop、onDestroyView担扑、onDestory恰响、onDetach方法,最終將活動(dòng)和碎片一起銷毀涌献。
16600-16600/com.beidou.fragmenttest D/RightFragment: onPause
16600-16600/com.beidou.fragmenttest D/RightFragment: onStop
16600-16600/com.beidou.fragmenttest D/RightFragment: onDestroyView
16600-16600/com.beidou.fragmenttest D/RightFragment: onDestroy
16600-16600/com.beidou.fragmenttest D/RightFragment: onDetach
10. 動(dòng)態(tài)加載布局的技巧
所謂的動(dòng)態(tài)加載布局的技巧胚宦,就是程序能夠根據(jù)設(shè)備的分辨率或屏幕大小在運(yùn)行時(shí)來決定加載哪個(gè)布局。
- 使用限定符
平板電腦使用的雙頁模式燕垃,而手機(jī)采用的是單頁模式枢劝。那么如何在運(yùn)行時(shí)判斷程序應(yīng)該是使用前者還是后者呢?就需要借助限定符來實(shí)現(xiàn)了卜壕。 - 修改FragmentTest項(xiàng)目中的activity_main.xml:
只留下左側(cè)碎片您旁,并讓它充滿整個(gè)父布局
<?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/left_fragment"
android:name="com.beidou.fragmenttest.LeftFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"></fragment>
</LinearLayout>
- res項(xiàng)目下創(chuàng)建layout-large文件夾,里面創(chuàng)建文件activity_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
android:id="@+id/left_fragment"
android:name="com.beidou.fragmenttest.LeftFragment"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"></fragment>
<fragment
android:id="@+id/right_fragment"
android:name="com.beidou.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"></fragment>
</LinearLayout>
layout/activity_main布局只包含了一個(gè)碎片轴捎,即單頁模式鹤盒,而layout-large/activity_main布局包含了兩個(gè)碎片,即雙頁模式侦副。其中l(wèi)arge就是一個(gè)限定符侦锯,那些屏幕被認(rèn)為是large的設(shè)備就會(huì)自動(dòng)加載layout_layout文件夾下的布局,而小屏幕的設(shè)備則還是會(huì)加載layout文件夾下的布局秦驯。
將MainAcitivity中replaceFragment方法里面的代碼注釋掉尺碰,并在平板模擬器上重新運(yùn)行程序,會(huì)發(fā)現(xiàn)自動(dòng)匹配的是layout-large文件夾下的布局;再啟動(dòng)一個(gè)手機(jī)模擬器亲桥,并在模擬器上重新運(yùn)行程序洛心,會(huì)發(fā)現(xiàn)自動(dòng)匹配的是layout文件夾下的布局。
11. 限定符參考表
- 屏幕大小方面
- 限定符:
small 代表提供給小屏幕設(shè)備的資源
normal 代表提供給中等屏幕設(shè)備的資源
large 代表提供給大屏幕設(shè)備的資源
xlarge 代表提供給超大屏幕設(shè)備的資源
- 屏幕特征
- 分辨率:
ldpi 代表提供給低分辨率設(shè)備的資源(120dpi以下)
mdpi 代表提供給中等分辨率設(shè)備的資源(120dpi-160dpi)
hdpi 代表提供給高分辨率設(shè)備的資源(160dpi-240dpi)
xhdpi 代表提供給超高分辨率設(shè)備的資源(240dpi-320dpi)
xxhdpi 代表提供給超超高分辨率設(shè)備的資源(320dpi-480dpi) - 方向
land 提供給橫屏設(shè)備的
port 提供給豎屏設(shè)備的資源
12. 使用最小寬度設(shè)備限定符
雖然large限定符成功解決了單頁雙頁的判斷問題题篷,不過large究竟指多大呢词身?有的時(shí)候我們希望可以更加靈活地為不同設(shè)備加載布局,不管它們是不是被系統(tǒng)認(rèn)定為large番枚,這時(shí)就可以使用最小寬度限定符偿枕。
最小寬度限定符允許我們隊(duì)屏幕的寬度指定一個(gè)最小值(以dp為單位),然后以這個(gè)最小值為臨界點(diǎn)户辫,屏幕寬度大于這個(gè)值的設(shè)備就加載一個(gè)布局,屏幕寬度小于這個(gè)值的設(shè)備就加載另一個(gè)布局嗤锉。
- 在res目錄下新建layout-sw600dp文件夾渔欢,然后在這個(gè)文件夾下新建activity_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
android:id="@+id/left_fragment"
android:name="com.beidou.fragmenttest.LeftFragment"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"></fragment>
<fragment
android:id="@+id/right_fragment"
android:name="com.beidou.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"></fragment>
</LinearLayout>
*** 意味著當(dāng)程序運(yùn)行在屏幕寬度大于600dp的設(shè)備上時(shí),會(huì)加載layout-sw600dp/activity_main布局瘟忱,當(dāng)程序運(yùn)行在屏幕寬度小于600dp的設(shè)備上時(shí)奥额,則仍然會(huì)加載默認(rèn)的layout/activity_main布局。
13. 碎片的最佳實(shí)踐——一個(gè)簡易版的新聞應(yīng)用
需求:在一個(gè)項(xiàng)目里面利用一套代碼融合手機(jī)和平板访诱。
技術(shù):RecyclerView fragment 最小寬度限定符
步驟:
- 利用最小寬度限定符垫挨,創(chuàng)建兩個(gè)布局,如果是手機(jī)就加載layout文件夾下的布局触菜,如果是平板就加載layout-sw600dp文件夾下的布局九榔,代碼如下:
- layout文件夾下的activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/news_title_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.fkq.fragmentbestpractice.MainActivity">
<fragment
android:id="@+id/news_title_fragment"
android:name="com.fkq.fragmentbestpractice.NewsTitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
- layout-sw600dp文件夾下的布局:
<?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/news_title_fragment"
android:name="com.fkq.fragmentbestpractice.NewsTitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"></fragment>
<FrameLayout
android:id="@+id/news_content_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<fragment
android:id="@+id/news_content_fragment"
android:name="com.fkq.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"></fragment>
</FrameLayout>
</LinearLayout>
說明:
- 手機(jī)的話加載NewsTitleFragment內(nèi)容。
- 平板的話左邊加載NewsTitleFragment涡相,右邊加載NewsContentFragment哲泊。
- 如果是手機(jī)的話,只會(huì)加載NewsTitleFragment催蝗,NewsTitleFragment是一個(gè)RecyclerView切威,RecyclerView的item布局就是一個(gè)Textview,顯示標(biāo)題即可丙号,點(diǎn)擊標(biāo)題進(jìn)入Activity先朦,在Activity里面顯示內(nèi)容。如果是平板的話犬缨,除了加載NewsTitleFragment喳魏,還會(huì)加載NewsContentFragment,NewsTitleFragmen展示的仍然是RecyclerView遍尺,里面的item是TextView截酷,點(diǎn)擊標(biāo)題,展示NewsContentFragment,內(nèi)容是標(biāo)題+內(nèi)容迂苛。所以這個(gè)時(shí)候NewsTitleFragment中RecyclerView的點(diǎn)擊事件需要判斷當(dāng)前是手機(jī)還是平板三热,如果是手機(jī)進(jìn)入Activity,如果是平板三幻,NewsContentFragment直接展示標(biāo)題和內(nèi)容就漾。
- 先創(chuàng)建手機(jī)跳轉(zhuǎn)后的Activity,命名為NewsContentActivity念搬,內(nèi)容是新聞的標(biāo)題+新聞的內(nèi)容抑堡;新聞標(biāo)題和新聞內(nèi)容我們是從NewsTitleFragment中的RecyclerView傳遞進(jìn)來;Activity的布局就是一個(gè)fragment朗徊,這個(gè)fragment也就是NewsContentFragment首妖,也就是說Activity頁面里面的內(nèi)容和平板新聞內(nèi)容一模一樣,都是NewsContentFragment爷恳,代碼如下:
*** 1. NewsContentActivity:
public class NewsContentActivity extends AppCompatActivity {
public static void actionStart(Context context,String newsTitle,String newsContent){
Intent intent = new Intent(context,NewsContentActivity.class);
intent.putExtra("news_title",newsTitle);
intent.putExtra("news_content",newsContent);
context.startActivity(intent);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_content);
String newsTitle = getIntent().getStringExtra("news_title");//獲取傳入的新聞標(biāo)題
String newsContent = getIntent().getStringExtra("news_content"); //獲取傳入的新聞內(nèi)容
NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(newsTitle,newsContent); //刷新NewsContentFragment
}
}
actionStart:用于數(shù)據(jù)傳遞使用有缆。
*** 2. NewsContentActivity的布局news_content.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="vertical">
<fragment
android:id="@+id/news_content_fragment"
android:name="com.fkq.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"></fragment>
</LinearLayout>
*** 3. NewsContentFragment:
public class NewsContentFragment extends Fragment {
private View view;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.news_content_frag,container,false);
return view;
}
public void refresh(String newsTitle,String newsContent){
View visibilityLayout = view.findViewById(R.id.visibility_layout);
visibilityLayout.setVisibility(View.VISIBLE);
TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);
TextView newsContentText = (TextView) view.findViewById(R.id.news_content);
newsTitleText.setText(newsTitle); //刷新新聞的標(biāo)題
newsContentText.setText(newsContent);//刷新新聞的內(nèi)容
}
}
refresh 用于展示新聞標(biāo)題和內(nèi)容。
*** 4. NewsContentFragment填充的布局news_content_frag:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/visibility_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="invisible">
<TextView
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:textSize="20sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000"></View>
<TextView
android:id="@+id/news_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="15dp"
android:textSize="18sp" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="#ff0000"></View>
</RelativeLayout>
- 平板頁面activity_main.xml中的新聞內(nèi)容展示使用NewsContentFragment即可温亲。
- 現(xiàn)在要在NewsTitleFragment的RecyclerView中做判斷棚壁,判斷是手機(jī)還是平板,以便決定是跳轉(zhuǎn)Activity栈虚,還是讓NewsContentFragment直接顯示內(nèi)容袖外。
*** 1. 先創(chuàng)建RecyclerView和Adapter:
這是Adapter:
class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{
private List<News> mNewsList;
class ViewHolder extends RecyclerView.ViewHolder{
TextView newsTitleText;
public ViewHolder(View itemView) {
super(itemView);
newsTitleText = (TextView) itemView.findViewById(R.id.news_title);
}
}
public NewsAdapter(List<News> newsList){
mNewsList = newsList;
}
@Override
public NewsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item,parent,false);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
News news = mNewsList.get(holder.getAdapterPosition());
if (isTwoPane){
//如果是雙頁模式,則刷新NewsContentFragment中的內(nèi)容
NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(news.getTitle(),news.getContent());
}else{
//如果是單頁模式魂务,則直接啟動(dòng)NewsContentActivity
NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent());
}
}
});
return holder;
}
@Override
public void onBindViewHolder(NewsAdapter.ViewHolder holder, int position) {
News news = mNewsList.get(position);
holder.newsTitleText.setText(news.getTitle());
}
@Override
public int getItemCount() {
return mNewsList.size();
}
}
這是Adapter的item布局:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:orientation="vertical"
android:paddingBottom="15dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:singleLine="true"
android:textSize="18sp">
</TextView>
這是數(shù)據(jù)來源:
/**
* 初始化新聞數(shù)據(jù)
* @return
*/
private List<News> getNews() {
List<News> newsList = new ArrayList<>();
for (int i = 1 ;i <= 50 ; i++){
News news = new News();
news.setTitle("This is news title"+i);
news.setContent(getRandomLengthContent("This is news content"+ i + "."));
newsList.add(news);
}
return newsList;
}
/**
* 把新聞內(nèi)容隨機(jī)變數(shù)
* @param content
* @return
*/
private String getRandomLengthContent(String content) {
Random random = new Random();
int length = random.nextInt(20)+1;
StringBuilder builder = new StringBuilder();
for (int i = 0;i<length;i++){
builder.append(content);
}
return builder.toString();
}
這是加載NewsTitleFragment的布局和生成RecyclerView:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag,container,false);
RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
LinearLayoutManager layoutmanager = new LinearLayoutManager(getActivity());
newsTitleRecyclerView.setLayoutManager(layoutmanager);
NewsAdapter adapter = new NewsAdapter(getNews());
newsTitleRecyclerView.setAdapter(adapter);
return view;
}
這是NewsTitleFragment的布局news_title_frag:
<?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="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/news_title_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>
</LinearLayout>
*** 2.判斷是手機(jī)還是平板曼验,就看看NewsTitleFragment所在的MainActivity的布局加載的是手機(jī)的還是平板,區(qū)別就是平板里面多一個(gè)NewsContentFragment粘姜,而NewsContentFragment是放在FrameLayout中的蚣驼,而FrameLayout的id是news_content_layout,所以我們判斷在這個(gè)Activity中能否找到這個(gè)id即可相艇,代碼如下:
private boolean isTwoPane;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout)!=null){
isTwoPane = true; //可以找到news_content_layout布局時(shí)颖杏,為雙頁模式
}else{
isTwoPane = false; //找不到news_content_layout布局時(shí),為單頁模式
}
}
*** 3.如果是雙頁模式坛芽,則刷新NewsContentFragment中的內(nèi)容留储;如果是單頁模式,則直接啟動(dòng)NewsContentActivity咙轩,代碼從上面Adapter中拿出來:
if (isTwoPane){
//如果是雙頁模式获讳,則刷新NewsContentFragment中的內(nèi)容
NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(news.getTitle(),news.getContent());
}else{
//如果是單頁模式,則直接啟動(dòng)NewsContentActivity
NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent());
}
refresh直接顯示數(shù)據(jù)活喊;actionStart數(shù)據(jù)傳遞丐膝。