第一行代碼讀書筆記 4 -- 探究碎片

本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):

  • 碎片 fragment 的用法;
  • 碎片 fragment 的生命周期隶糕;
  • 動(dòng)態(tài)加載布局的技巧冈在,限定符的使用府框;
  • 實(shí)戰(zhàn):簡(jiǎn)易版的新聞應(yīng)用。
圖片來源于網(wǎng)絡(luò)

4.1 碎片是什么

碎片(Fragment)是一種可以嵌入在活動(dòng)當(dāng)中的 UI 片段讥邻,它能讓程序更加合理和充分地利用大屏幕的空間,因而在平板上應(yīng)用的非常廣泛院峡。

如開發(fā)一個(gè)新聞應(yīng)用兴使,其中一個(gè)界面使用 RecyclerView 展示了一組新聞的標(biāo)題,當(dāng)點(diǎn)擊了其中一個(gè)標(biāo)題照激,就打開另一個(gè)界面顯示新聞的詳細(xì)內(nèi)容发魄。若是在手機(jī)中設(shè)計(jì),可以將新聞標(biāo)題列表放在一個(gè)活動(dòng)中俩垃,將新聞的詳細(xì)內(nèi)容放在另一個(gè)活動(dòng)中励幼,如圖所示:

手機(jī)的設(shè)計(jì)方案

但顯示在平板上,那么新聞標(biāo)題列表將會(huì)被拉長(zhǎng)至填充滿整個(gè)平板的屏幕口柳,而新聞的標(biāo)題一般都不會(huì)太長(zhǎng)苹粟,這樣將會(huì)導(dǎo)致界面上有大量的空白區(qū)域,如圖所示:

平板的新聞列表

因此跃闹,更好的設(shè)計(jì)方案是將新聞標(biāo)題列表界面和新聞詳細(xì)內(nèi)容界面分別放在兩個(gè)碎片中嵌削,然后在同一個(gè)活動(dòng)里引入這兩個(gè)碎片,這樣就可以將屏幕空間充分地利用起來了望艺,如圖所示:

平板的雙頁設(shè)計(jì)

4.2 碎片的使用方式

4.2.1 碎片的簡(jiǎn)單用法

在一個(gè)活動(dòng)當(dāng)中添加兩個(gè)碎片苛秕,并讓這兩個(gè)碎片平分活動(dòng)空間。

新建一個(gè)左側(cè)碎片布局 fragment_left.xml 如下:

<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="按鈕"/>

</LinearLayout>

這個(gè)布局只放置了一個(gè)按鈕找默,并讓它水平居中顯示艇劫。然后新建右側(cè)碎片布局 fragment_right.xml如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#00ff00">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="這是右邊的fragment"/>

</LinearLayout>

將這個(gè)布局的背景色設(shè)置成綠色,并放置了一個(gè) TextView 用于顯示一段文本惩激。

接著新建一個(gè) LeftFragment 類店煞,繼承自 Fragment。注意咧欣,這里可能會(huì)有兩個(gè)不同包下的 Fragment 供你選擇,一個(gè)是系統(tǒng)內(nèi)置的 android.app.Fragment魄咕,一個(gè)是 support-v4 庫(kù)中的 android.support.v4.app.Fragment 衩椒。

這里強(qiáng)烈建議使用 support-v4 庫(kù)中的 Fragment,因?yàn)樗梢宰屗槠谒?Android 系統(tǒng)版本中保持功能一致性。

代碼如下:

public class LeftFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left, container, false);
        return view;
    }
}

這里僅僅是重寫了 FragmentonCreateView() 方法毛萌,然后在這個(gè)方法中通過 LayoutInflaterinflate() 方法將剛定義的 fragment_left 布局動(dòng)態(tài)加載進(jìn)來苟弛,整個(gè)方法簡(jiǎn)單明了。

接著用同樣的方法再新建一個(gè) RightFragment

public class RightFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                          Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_right, container, false);
        return view;
    }
}

接下來修改 activity_fragment.xml 中的代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id="@+id/fragment_left"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <fragment
        android:id="@+id/fragment_right"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>
    
</LinearLayout>

上面使用了 <fragment> 標(biāo)簽在布局中添加碎片阁将,通過 android:name 屬性來顯式指明要添加的碎片類名膏秫,注意一定要將類的包名也加上。

這樣簡(jiǎn)單的碎片示例就已經(jīng)寫好了做盅,運(yùn)行程序缤削,(平板上)效果如圖:

碎片的簡(jiǎn)單運(yùn)行效果

4.2.2 動(dòng)態(tài)添加碎片

碎片真正的強(qiáng)大之處在于,它可以在程序運(yùn)行時(shí)動(dòng)態(tài)地添加到活動(dòng)當(dāng)中吹榴。根據(jù)具體情況來動(dòng)態(tài)地添加碎片亭敢,你就可以將程序界面定制得更加多樣化。

在上一節(jié)代碼的基礎(chǔ)上繼續(xù)完善图筹,新建 fragment_another_right.xml 如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ffff00">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="這是另外一個(gè)右邊的fragment"/>

</LinearLayout>

然后新建 AnotherRightFragment 作為另一個(gè)右側(cè)碎片如下:

public class AnotherRightFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_another_right, container, false);
        return view;
    }
}

接下來看一下如何將它動(dòng)態(tài)地添加到活動(dòng)當(dāng)中帅刀。修改 activity_fragment.xml 如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id="@+id/fragment_left"
        android:name="com.wonderful.myfirstcode.inquiry_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"/>
    
    <!--
    <fragment
        android:id="@+id/fragment_right"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>
         -->

</LinearLayout>

上面將右側(cè)碎片放在了一個(gè) FrameLayout 中,下面在代碼中向 FrameLayout 里添加內(nèi)容远剩,從而實(shí)現(xiàn)動(dòng)態(tài)添加碎片的功能扣溺。修改 Activity 中的代碼如下:

public class FragmentActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RightFragment());
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                replaceFragment(new AnotherRightFragment());
                break;

            default:
                break;
        }
    }

    private void replaceFragment(Fragment fragment){
        // 獲取FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 開啟事務(wù)
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 添加或替換碎片
        transaction.replace(R.id.right_layout,fragment);
        // 提交事務(wù)
        transaction.commit();
    }
}

上述代碼,給左側(cè)碎片中的按鈕注冊(cè)了一個(gè)點(diǎn)擊事件瓜晤,調(diào)用 replaceFragment() 方法動(dòng)態(tài)添加碎片锥余。結(jié)合代碼可看出,動(dòng)態(tài)添加碎片主要分為 5 步活鹰。

1. 創(chuàng)建待添加的碎片實(shí)例哈恰。

2. 獲取 FragmentManager,在活動(dòng)中可以直接調(diào)用 getSupportFragmentManager() 方法得到志群。

3. 開啟一個(gè)事務(wù)着绷,通過調(diào)用 beginTransaction() 方法開啟。

4. 向容器內(nèi)添加或替換碎片锌云,一般使用 replace() 方法實(shí)現(xiàn)荠医,需要傳入容器的 id 和待添加的碎 片實(shí)例。

5. 提交事務(wù)桑涎,調(diào)用 commit() 方法來完成彬向。

重新運(yùn)行程序,效果如圖:

動(dòng)態(tài)添加碎片的效果

4.2.3 在碎片中模擬返回棧

在上一小節(jié)中攻冷,實(shí)現(xiàn)了向活動(dòng)中動(dòng)態(tài)添加碎片的功能娃胆,但通過點(diǎn)擊按鈕添加了一個(gè)碎片之后,按下 Back 鍵程序就會(huì)直接退出等曼。該如何實(shí)現(xiàn)按下 Back 鍵可以回到上一個(gè)碎片呢里烦?

FragmentTransaction 中提供了一個(gè) addToBackStack() 方法凿蒜,可以用于將一個(gè)事務(wù)添加到返回棧中,修改 Activity 中的代碼如下:

public class FragmentActivity extends AppCompatActivity implements View.OnClickListener {

    . . .

    private void replaceFragment(Fragment fragment){
        // 獲取FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 開啟事務(wù)
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 添加或替換碎片
        transaction.replace(R.id.right_layout,fragment);
        // 用于描述返回棧的狀態(tài)
        transaction.addToBackStack(null);
        // 提交事務(wù)
        transaction.commit();
    }
}

在事務(wù)提交之前調(diào)用了 FragmentTransactionaddToBackStack() 方法胁黑,它可以接收一個(gè)名字用于描述返回棧的狀態(tài)废封,一般傳入 null 即可。這樣問題就解決了丧蘸。

4.2.4 碎片和活動(dòng)之間進(jìn)行通信

為了方便碎片和活動(dòng)之間進(jìn)行通信漂洋,FragmentManager 提供了一個(gè)類似于 findViewById() 的方法,專門用于從布局文件中獲取碎片的實(shí)例力喷。在活動(dòng)中調(diào)用碎片里的方法:

RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);

在碎片中調(diào)用活動(dòng)里的方法:通過調(diào)用 getActivity()方法來得到和當(dāng)前碎片相關(guān)聯(lián)的活動(dòng)實(shí)例刽漂,代碼如下:

MainActivity activity = (MainActivity) getActivity();

4.3 碎片的生命周期

每個(gè)活動(dòng)在其生命周期內(nèi)可能會(huì)有四種狀態(tài):運(yùn)行狀態(tài)、暫停 狀態(tài)弟孟、停止?fàn)顟B(tài)和銷毀狀態(tài)爽冕。類似地,每個(gè)碎片在其生命周期內(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)時(shí)没讲,該碎片也處于運(yùn)行狀態(tài)应役。

  • 暫停狀態(tài):
    當(dāng)一個(gè)活動(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)用 FragmentTransactionremove()replace() 方法將碎片從活動(dòng)中移除疏叨,但若在事務(wù)提交之前調(diào)用 addToBackStack() 方法潘靖,這時(shí)的碎片也會(huì)進(jìn)入到停止?fàn)顟B(tài)≡槁總的來說卦溢,進(jìn)入停止?fàn)顟B(tài)的碎片對(duì)用戶來說是完全不可見的,有可能會(huì)被系統(tǒng)回收秀又。

  • 銷毀狀態(tài):
    碎片總是依附于活動(dòng)而存在的单寂,因此當(dāng)活動(dòng)被銷毀時(shí),與它相關(guān)聯(lián)的碎片就會(huì)進(jìn)入到銷毀狀態(tài)吐辙⌒觯或者通過調(diào)用 FragmentTransactionremove()replace() 方法將碎片從活動(dòng)中移除昏苏,但在事務(wù)提交之前并沒有調(diào)用 addToBackStack() 方法尊沸,這時(shí)的碎片也會(huì)進(jìn)入 到銷毀狀態(tài)威沫。

和活動(dòng) Acitvity 相似,Fragment 類中也提供了一系列的回調(diào)方法椒丧,以覆蓋碎片生命周期的每個(gè)環(huán)節(jié)壹甥。其中,活動(dòng)中有的回調(diào)方法壶熏,碎片中幾乎都有句柠,不過碎片還提供了一些附加的回調(diào)方法,重點(diǎn)來看下這幾個(gè)回調(diào):

  • onAttach() 當(dāng)碎片和活動(dòng)建立關(guān)聯(lián)的時(shí)候調(diào)用棒假。
  • onCreateView() 為碎片創(chuàng)建視圖(加載布局)時(shí)調(diào)用溯职。
  • onActivityCreated() 確保與碎片相關(guān)聯(lián)的活動(dòng)一定已經(jīng)創(chuàng)建完畢的時(shí)候調(diào)用。
  • onDestroyView() 當(dāng)與碎片關(guān)聯(lián)的視圖被移除的時(shí)候調(diào)用帽哑。
  • onDetach() 當(dāng)碎片和活動(dòng)解除關(guān)聯(lián)的時(shí)候調(diào)用谜酒。

碎片完整的生命周期可參考源自 Android 官網(wǎng)圖的示意圖:

碎片的生命周期

另外,在碎片中你也可以通過 onSaveInstanceState() 方法來保存數(shù)據(jù)妻枕, 因?yàn)檫M(jìn)入停止?fàn)顟B(tài)的碎片有可能在系統(tǒng)內(nèi)存不足的時(shí)候被回收僻族。保存下來的數(shù)據(jù)在 onCreate()onCreateView()onActivityCreated() 這三個(gè)方法中你都可以重新得到屡谐,它們都含有一個(gè) Bundle 類型的 savedInstanceState 參數(shù)述么。

4.4 動(dòng)態(tài)加載布局的技巧

4.4.1 使用限定符

現(xiàn)有個(gè)需求:在平板上使用雙頁模式,在手機(jī)上顯示單頁模式愕掏。那么怎樣才能在運(yùn)行時(shí)判斷程序應(yīng)該是使用雙頁模式還是單頁模式呢度秘?這就需要借助限定符(Qualifiers)來實(shí)現(xiàn)了。

通過一個(gè)例子來學(xué)習(xí)一下它的用法饵撑,修改項(xiàng)目中 activity_fragment.xml 的代碼:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id="@+id/fragment_left"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.LeftFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

這里將多余的代碼都刪掉剑梳,只留下一個(gè)左側(cè)碎片,并讓它充滿整個(gè)父布局滑潘。接著在 res 目錄下新建 layout-large 文件夾垢乙,在這個(gè)文件夾下新建一個(gè)布局,也叫做 activity_fragment.xml语卤, 代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id="@+id/fragment_left"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent" 
        android:layout_weight="1"/>
    
    <fragment
        android:id="@+id/fragment_right"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"/>
    
</LinearLayout>

上面 layout/activity_fragment 布局只包含了一個(gè)碎片侨赡,即單頁模式,而 layout-large/activity_fragment 布局包含了兩個(gè)碎片粱侣,即雙頁模式羊壹。其中 large 就是一個(gè)限定符,那些屏幕被認(rèn)為是 large 的設(shè)備就會(huì)自動(dòng)加載 layout-large 文件夾下的布局齐婴,而小屏幕的設(shè)備則還是會(huì)加載 layout 文件夾下的布局油猫。

然后將 ActivityreplaceFragment() 方法注釋掉,并在平板模擬器上重新運(yùn)行程序柠偶, 效果如圖:

雙頁模式運(yùn)行效果

再啟動(dòng)一個(gè)手機(jī)模擬器情妖,并在這個(gè)模擬器上重新運(yùn)行程序睬关,效果如圖:

單頁模式運(yùn)行效果

Android 中一些常見的限定符可以參考下表:

Android 中一些常見的限定符

4.4.2 使用最小寬度限定符

有時(shí)候希望可以更加靈活地為不同設(shè)備加載布局,不管它們是不是被系統(tǒng)認(rèn)定為 “ large ”毡证,這時(shí)就可以使用最小寬度限定符(Smallest-width Qualifier)了电爹。

最小寬度限定符允許我們對(duì)屏幕的寬度指定一個(gè)最小指(以 dp 為單位),然后以這個(gè)最小值為臨界點(diǎn)料睛,屏幕寬度大于這個(gè)值的設(shè)備就加載一個(gè)布局丐箩,屏幕寬度小于這個(gè)值的設(shè)備就加載另一個(gè)布局。

在 res 目錄下新建 layout-sw600dp 文件夾恤煞,然后在這個(gè)文件夾下新建 activity_fragment .xml 布局屎勘,代碼與上面 layout-large/activity_fragment 中的一樣。

...

這就意味著居扒,當(dāng)程序運(yùn)行在屏幕寬度大于 600dp 的設(shè)備上時(shí)概漱,會(huì)加載 layout-sw600dp/ activity_fragment 布局,當(dāng)程序運(yùn)行在屏幕寬度小于 600dp 的設(shè)備上時(shí)喜喂,則仍然加載默認(rèn)的 layout/activity_fragment 布局瓤摧。

4.5 碎片的最佳實(shí)踐——一個(gè)簡(jiǎn)易版的新聞應(yīng)用

需求:一個(gè)簡(jiǎn)易的新聞應(yīng)用,可以同時(shí)兼容手機(jī)和平板玉吁。

首先姻灶,在 app/build.gradle 中添加后面需要用到的 RecyclerView 依賴庫(kù):

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support:recyclerview-v7:24.2.1'
    testCompile 'junit:junit:4.12'
}

接下來,準(zhǔn)備好一個(gè)新聞的實(shí)體類诈茧,新建類 News,代碼如下:

/**
 * 新聞實(shí)體類
 * Created by KXwon on 2016/12/12.
 */

public class News {

    private String title;   // 新聞標(biāo)題

    private String content; // 新聞內(nèi)容

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

接著新建一個(gè) news_content_frag.xml 布局捂掰,作為新聞內(nèi)容的布局:

<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:textSize="20sp"
            android:padding="10dp"/>
        
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#000"/>

        <TextView
            android:id="@+id/news_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textSize="18sp"
            android:padding="15dp"/>
        
    </LinearLayout>

    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="#000"/>

</RelativeLayout>

新聞內(nèi)容的布局主要分為兩個(gè)部分敢会,頭部顯示新聞標(biāo)題,正文顯示新聞內(nèi)容这嚣,中間使用一條細(xì)線分隔開鸥昏。

然后再新建一個(gè) NewsContentFragment 類,如下:

/**
 * 新聞內(nèi)容fragment
 * Created by KXwon on 2016/12/12.
 */

public class NewsContentFragment extends Fragment {

    private View view;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.news_content_frag, container, false);
        return view;
    }

    /**
     * 將新聞標(biāo)題和新聞內(nèi)容顯示在界面上
     * @param newsTitle   標(biāo)題
     * @param newsContent 內(nèi)容
     */
    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)容
    }
}

這樣就把新聞內(nèi)容的碎片和布局創(chuàng)建好了姐帚,但它們都是在雙頁模式下使用的吏垮,若要在單頁模式中使用,還需創(chuàng)建一個(gè)活動(dòng) NewsContentActivity罐旗,其布局 news_content.xml 中的代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/news_content_fragment"
        android:name="com.wonderful.myfirstcode.chapter4.simple_news.NewsContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</LinearLayout>

這里直接在布局中引入了 NewsContentFragment膳汪,相當(dāng)于把 news_content_frag 布局的內(nèi)容自動(dòng)加了進(jìn)來。

然后編寫 NewsContentActivity 的代碼如下:

public class NewsContentActivity extends AppCompatActivity {

    /**
     * 構(gòu)建Intent九秀,傳遞所需數(shù)據(jù)
     * @param context
     * @param newsTitle
     * @param newsContent
     */
    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(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_content);
        // 獲取傳入的新聞標(biāo)題遗嗽、新聞內(nèi)容
        String newsTitle = getIntent().getStringExtra("news_title");
        String newsContent = getIntent().getStringExtra("news_content");
        // 獲取 NewsContentFragment 實(shí)例
        NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager()
                .findFragmentById(R.id.news_content_fragment);
        // 刷新 NewsContentFragment 界面
        newsContentFragment.refresh(newsTitle, newsContent); 
    }
}

上述代碼,在 onCreate() 方法中通過 Intent 獲取傳入的新聞標(biāo)題和內(nèi)容鼓蜒,然后調(diào)用 FragmentManagerfindFragmentById() 方法得到 NewsContentFragment 的實(shí)例痹换,接著調(diào)用它的 refresh() 方法征字,并將新聞的標(biāo)題和內(nèi)容傳入,顯示數(shù)據(jù)娇豫。(關(guān)于 actionStart() 方法可以閱讀前面的探究活動(dòng)2.5.2相關(guān)筆記匙姜。)

接下來還需再創(chuàng)建顯示新聞列表的布局 news_title_frag.xml,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_title_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

新建 news_item.xml 作為 上述 RecyclerView 子項(xiàng)的布局:

<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:singleLine="true"
    android:ellipsize="end"
    android:textSize="18sp"
    android:padding="10dp"/>

子項(xiàng)的布局就只有一個(gè) TextView冯痢。

新聞列表和子項(xiàng)布局都創(chuàng)建好了氮昧,接下來就需要一個(gè)用于展示新聞列表的地方。這里新建 NewsTitleFragment 作為展示新聞列表的碎片:

/**
 * 新聞列表fragment
 * Created by KXwon on 2016/12/12.
 */

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;

    @Override
    public View onCreateView(LayoutInflater inflater,  ViewGroup container,  Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_content_frag, container, false);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout)!= null){
            // 可以找到 news_content_layout 布局時(shí)系羞,為雙頁模式
            isTowPane = true;
        }else {
            // 找不到 news_content_layout 布局時(shí)郭计,為單頁模式
            isTowPane = false;
        }
    }
}

為實(shí)現(xiàn)上述 onActivityCreated() 方法中判斷當(dāng)前時(shí)雙頁還是單頁模式,需要借助限定符椒振,首先修改主布局 activity_news.xml 中的代碼:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_title_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.wonderful.myfirstcode.chapter4.simple_news.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

上述代碼表示昭伸,在單頁模式下只會(huì)加載一個(gè)新聞標(biāo)題的碎片。

然后在 res 目錄下新建 layout-sw600dp 文件夾澎迎,在這個(gè)文件夾下再新建一個(gè) activity_news.xml 文件庐杨,代碼如下:

<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:id="@+id/news_title_fragment"
        android:name="com.wonderful.myfirstcode.chapter4.simple_news.NewsTitleFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <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.wonderful.myfirstcode.chapter4.simple_news.NewsContentFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>

</LinearLayout>

上述代碼表示,在雙頁模式下會(huì)同時(shí)加載兩個(gè)碎片夹供,并將新聞內(nèi)容碎片放在 FrameLayout 布局下灵份,這個(gè)布局 id 正是 news_content_layout。因此能找到這個(gè) id 的時(shí)候就是雙頁模式哮洽,否則就是單頁模式填渠。

現(xiàn)在已經(jīng)將絕大多數(shù)工作完成了,剩下至關(guān)重要的一點(diǎn)鸟辅,就是在 NewsTitleFragemt 中通過 RecyclerView 將新聞列表展示出來氛什。接下來在 NewsTitleFragemt 中新建一個(gè)內(nèi)部類 NewsAdapter 來作為 RecyclerView 的適配器,如下:

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;

    . . .
    
    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {

        private List<News> mNewsList;

        class ViewHolder extends RecyclerView.ViewHolder {

            TextView newsTitleText;

            public ViewHolder(View view) {
                super(view);
                newsTitleText = (TextView) view.findViewById(R.id.news_title);
            }
        }

        public NewsAdapter(List<News> newsList) {
            mNewsList = newsList;
        }

        @Override
        public 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 v) {
                    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(ViewHolder holder, int position) {
            News news = mNewsList.get(position);
            holder.newsTitleText.setText(news.getTitle());
        }

        @Override
        public int getItemCount() {
            return mNewsList.size();
        }

    }

需要注意的是,這里把適配器寫成內(nèi)部類是為了直接訪問 NewsTitleFragment 的變量再层,比如 isTowPane贸铜。

現(xiàn)在還剩最后一步收尾工作,就是向 RecyclerView 中填充數(shù)據(jù)了聂受。修改 NewsTitleFragment 中的代碼如下:

public class NewsTitleFragment extends Fragment{

    . . .

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 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;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout) != null) {
            // 可以找到news_content_layout布局時(shí)蒿秦,為雙頁模式
            isTwoPane = true;
        } else {
            // 找不到news_content_layout布局時(shí),為單頁模式
            isTwoPane = false;
        }
    }

    /**
     * 初始化50條模擬新聞數(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("新聞內(nèi)容吼吼吼" + i + "!"));
            newsList.add(news);
        }
        return newsList;
    }

    /**
     * 隨機(jī)生成不同長(zhǎng)度的新聞內(nèi)容
     * @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();
    }

    . . . 
}

到這里蛋济,所有的代碼編寫工作就完成了渤早,運(yùn)行程序,效果如下:

單頁模式的新聞列表界面

點(diǎn)擊一條新聞瘫俊,會(huì)啟動(dòng)一個(gè)新的活動(dòng)來顯示新聞內(nèi)容:

單頁模式的新聞內(nèi)容界面

接下來把程序在平板上運(yùn)行鹊杖,同樣點(diǎn)擊一條新聞悴灵,效果如下:

雙頁模式的新聞標(biāo)題和內(nèi)容界面

好了,關(guān)于碎片的內(nèi)容學(xué)習(xí)到這骂蓖,下篇文章將學(xué)習(xí)廣播機(jī)制...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末积瞒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子登下,更是在濱河造成了極大的恐慌茫孔,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件被芳,死亡現(xiàn)場(chǎng)離奇詭異缰贝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)畔濒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門剩晴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侵状,你說我怎么就攤上這事赞弥。” “怎么了趣兄?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵绽左,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我艇潭,道長(zhǎng)拼窥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任蹋凝,我火速辦了婚禮鲁纠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仙粱。我一直安慰自己,他們只是感情好彻舰,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布伐割。 她就那樣靜靜地躺著,像睡著了一般刃唤。 火紅的嫁衣襯著肌膚如雪隔心。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天尚胞,我揣著相機(jī)與錄音硬霍,去河邊找鬼。 笑死笼裳,一個(gè)胖子當(dāng)著我的面吹牛唯卖,可吹牛的內(nèi)容都是我干的粱玲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拜轨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼抽减!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橄碾,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤卵沉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后法牲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體史汗,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年拒垃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了停撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恶复,死狀恐怖怜森,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谤牡,我是刑警寧澤副硅,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站翅萤,受9級(jí)特大地震影響恐疲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜套么,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一培己、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胚泌,春花似錦省咨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至穷缤,卻和暖如春敌蜂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背津肛。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工章喉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓秸脱,卻偏偏與公主長(zhǎng)得像落包,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撞反,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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