Fragment全解析

平板電腦和手機(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)加載
  1. 啟動(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>
  1. 新建兩個(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庫就可以了。

  1. 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)加載
  1. 在原有的項(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)容基本相同,只是將背景色改成了黃色厨姚,并將顯示的文字改了改衅澈。

  1. 新建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;
    }
}
  1. 修改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>
  1. 向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。

  1. 從以上可以看出動(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ū)別凡桥。

  1. 運(yùn)行狀態(tài)
    當(dāng)一個(gè)碎片是可見的蟀伸,并且它所關(guān)聯(lián)的活動(dòng)正處于運(yùn)行狀態(tài),該碎片也處于運(yùn)行狀態(tài)缅刽。
  2. 暫停狀態(tài)
    活動(dòng)進(jìn)入暫停狀態(tài)時(shí)(由于另一個(gè)未占滿屏幕的活動(dòng)被添加到了棧頂)啊掏,與它相關(guān)聯(lián)的可見碎片就會(huì)進(jìn)入到暫停狀態(tài)。
  3. 停止?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)回收畦戒。
  4. 銷毀狀態(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. 限定符參考表

  • 屏幕大小方面
  1. 限定符:
    small 代表提供給小屏幕設(shè)備的資源
    normal 代表提供給中等屏幕設(shè)備的資源
    large 代表提供給大屏幕設(shè)備的資源
    xlarge 代表提供給超大屏幕設(shè)備的資源
  • 屏幕特征
  1. 分辨率:
    ldpi 代表提供給低分辨率設(shè)備的資源(120dpi以下)
    mdpi 代表提供給中等分辨率設(shè)備的資源(120dpi-160dpi)
    hdpi 代表提供給高分辨率設(shè)備的資源(160dpi-240dpi)
    xhdpi 代表提供給超高分辨率設(shè)備的資源(240dpi-320dpi)
    xxhdpi 代表提供給超超高分辨率設(shè)備的資源(320dpi-480dpi)
  2. 方向
    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 最小寬度限定符

步驟:

  1. 利用最小寬度限定符垫挨,創(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哲泊。
  1. 如果是手機(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即可温亲。
  1. 現(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ù)傳遞丐膝。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帅矗,更是在濱河造成了極大的恐慌偎肃,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑此,死亡現(xiàn)場離奇詭異累颂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凛俱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門紊馏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蒲犬,你說我怎么就攤上這事朱监。” “怎么了原叮?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵赌朋,是天一觀的道長。 經(jīng)常有香客問我篇裁,道長,這世上最難降的妖魔是什么赡若? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任达布,我火速辦了婚禮,結(jié)果婚禮上逾冬,老公的妹妹穿的比我還像新娘黍聂。我一直安慰自己,他們只是感情好身腻,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布产还。 她就那樣靜靜地躺著,像睡著了一般嘀趟。 火紅的嫁衣襯著肌膚如雪脐区。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天她按,我揣著相機(jī)與錄音牛隅,去河邊找鬼。 笑死酌泰,一個(gè)胖子當(dāng)著我的面吹牛媒佣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陵刹,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼默伍,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起也糊,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤炼蹦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后显设,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體框弛,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年捕捂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟枫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡指攒,死狀恐怖慷妙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情允悦,我是刑警寧澤膝擂,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站隙弛,受9級(jí)特大地震影響架馋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜全闷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一叉寂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧总珠,春花似錦屏鳍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淫奔,卻和暖如春山涡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背唆迁。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工佳鳖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人媒惕。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓系吩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妒蔚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子穿挨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容