第一行代码读书笔记 4 -- 探究碎片

本篇文章主要介绍以下几个知识点:

  • 碎片 fragment 的用法;
  • 碎片 fragment 的生命周期;
  • 动态加载布局的技巧,限定符的使用;
  • 实战:简易版的新闻应用。
图片来源于网络

4.1 碎片是什么

碎片(Fragment)是一种可以嵌入在活动当中的 UI 片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用的非常广泛。

如开发一个新闻应用,其中一个界面使用 RecyclerView 展示了一组新闻的标题,当点击了其中一个标题,就打开另一个界面显示新闻的详细内容。若是在手机中设计,可以将新闻标题列表放在一个活动中,将新闻的详细内容放在另一个活动中,如图所示:

手机的设计方案

但显示在平板上,那么新闻标题列表将会被拉长至填充满整个平板的屏幕,而新闻的标题一般都不会太长,这样将会导致界面上有大量的空白区域,如图所示:

平板的新闻列表

因此,更好的设计方案是将新闻标题列表界面和新闻详细内容界面分别放在两个碎片中,然后在同一个活动里引入这两个碎片,这样就可以将屏幕空间充分地利用起来了,如图所示:

平板的双页设计

4.2 碎片的使用方式

4.2.1 碎片的简单用法

在一个活动当中添加两个碎片,并让这两个碎片平分活动空间。

新建一个左侧碎片布局 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>

这个布局只放置了一个按钮,并让它水平居中显示。然后新建右侧碎片布局 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>

将这个布局的背景色设置成绿色,并放置了一个 TextView 用于显示一段文本。

接着新建一个 LeftFragment 类,继承自 Fragment。注意,这里可能会有两个不同包下的 Fragment 供你选择,一个是系统内置的 android.app.Fragment,一个是 support-v4 库中的 android.support.v4.app.Fragment

这里强烈建议使用 support-v4 库中的 Fragment,因为它可以让碎片在所有 Android 系统版本中保持功能一致性。

代码如下:

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() 方法,然后在这个方法中通过 LayoutInflaterinflate() 方法将刚定义的 fragment_left 布局动态加载进来,整个方法简单明了。

接着用同样的方法再新建一个 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> 标签在布局中添加碎片,通过 android:name 属性来显式指明要添加的碎片类名,注意一定要将类的包名也加上。

这样简单的碎片示例就已经写好了,运行程序,(平板上)效果如图:

碎片的简单运行效果

4.2.2 动态添加碎片

碎片真正的强大之处在于,它可以在程序运行时动态地添加到活动当中。根据具体情况来动态地添加碎片,你就可以将程序界面定制得更加多样化。

在上一节代码的基础上继续完善,新建 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="这是另外一个右边的fragment"/>

</LinearLayout>

然后新建 AnotherRightFragment 作为另一个右侧碎片如下:

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;
    }
}

接下来看一下如何将它动态地添加到活动当中。修改 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>

上面将右侧碎片放在了一个 FrameLayout 中,下面在代码中向 FrameLayout 里添加内容,从而实现动态添加碎片的功能。修改 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();
        // 开启事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 添加或替换碎片
        transaction.replace(R.id.right_layout,fragment);
        // 提交事务
        transaction.commit();
    }
}

上述代码,给左侧碎片中的按钮注册了一个点击事件,调用 replaceFragment() 方法动态添加碎片。结合代码可看出,动态添加碎片主要分为 5 步。

1. 创建待添加的碎片实例。

2. 获取 FragmentManager,在活动中可以直接调用 getSupportFragmentManager() 方法得到。

3. 开启一个事务,通过调用 beginTransaction() 方法开启。

4. 向容器内添加或替换碎片,一般使用 replace() 方法实现,需要传入容器的 id 和待添加的碎 片实例。

5. 提交事务,调用 commit() 方法来完成。

重新运行程序,效果如图:

动态添加碎片的效果

4.2.3 在碎片中模拟返回栈

在上一小节中,实现了向活动中动态添加碎片的功能,但通过点击按钮添加了一个碎片之后,按下 Back 键程序就会直接退出。该如何实现按下 Back 键可以回到上一个碎片呢?

FragmentTransaction 中提供了一个 addToBackStack() 方法,可以用于将一个事务添加到返回栈中,修改 Activity 中的代码如下:

public class FragmentActivity extends AppCompatActivity implements View.OnClickListener {

    . . .

    private void replaceFragment(Fragment fragment){
        // 获取FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 开启事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 添加或替换碎片
        transaction.replace(R.id.right_layout,fragment);
        // 用于描述返回栈的状态
        transaction.addToBackStack(null);
        // 提交事务
        transaction.commit();
    }
}

在事务提交之前调用了 FragmentTransactionaddToBackStack() 方法,它可以接收一个名字用于描述返回栈的状态,一般传入 null 即可。这样问题就解决了。

4.2.4 碎片和活动之间进行通信

为了方便碎片和活动之间进行通信,FragmentManager 提供了一个类似于 findViewById() 的方法,专门用于从布局文件中获取碎片的实例。在活动中调用碎片里的方法:

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

在碎片中调用活动里的方法:通过调用 getActivity()方法来得到和当前碎片相关联的活动实例,代码如下:

MainActivity activity = (MainActivity) getActivity();

4.3 碎片的生命周期

每个活动在其生命周期内可能会有四种状态:运行状态、暂停 状态、停止状态和销毁状态。类似地,每个碎片在其生命周期内也可能会经历这几种状态,只不过在一些细小的地方会有部分区别。

  • 运行状态:
    当一个碎片是可见的,并且它所关联的活动正处于运行状态时,该碎片也处于运行状态。

  • 暂停状态:
    当一个活动进入暂停状态时(由于另一个未占满屏幕的活动被添加到了栈顶),与它相关联的可见碎片就会进入到暂停状态。

  • 停止状态:
    当一个活动进入停止状态时,与它相关联的碎片就会进入到停止状态。或者通过调用 FragmentTransactionremove()replace() 方法将碎片从活动中移除,但若在事务提交之前调用 addToBackStack() 方法,这时的碎片也会进入到停止状态。总的来说,进入停止状态的碎片对用户来说是完全不可见的,有可能会被系统回收。

  • 销毁状态:
    碎片总是依附于活动而存在的,因此当活动被销毁时,与它相关联的碎片就会进入到销毁状态。或者通过调用 FragmentTransactionremove()replace() 方法将碎片从活动中移除,但在事务提交之前并没有调用 addToBackStack() 方法,这时的碎片也会进入 到销毁状态。

和活动 Acitvity 相似,Fragment 类中也提供了一系列的回调方法,以覆盖碎片生命周期的每个环节。其中,活动中有的回调方法,碎片中几乎都有,不过碎片还提供了一些附加的回调方法,重点来看下这几个回调:

  • onAttach() 当碎片和活动建立关联的时候调用。
  • onCreateView() 为碎片创建视图(加载布局)时调用。
  • onActivityCreated() 确保与碎片相关联的活动一定已经创建完毕的时候调用。
  • onDestroyView() 当与碎片关联的视图被移除的时候调用。
  • onDetach() 当碎片和活动解除关联的时候调用。

碎片完整的生命周期可参考源自 Android 官网图的示意图:

碎片的生命周期

另外,在碎片中你也可以通过 onSaveInstanceState() 方法来保存数据, 因为进入停止状态的碎片有可能在系统内存不足的时候被回收。保存下来的数据在 onCreate()onCreateView()onActivityCreated() 这三个方法中你都可以重新得到,它们都含有一个 Bundle 类型的 savedInstanceState 参数。

4.4 动态加载布局的技巧

4.4.1 使用限定符

现有个需求:在平板上使用双页模式,在手机上显示单页模式。那么怎样才能在运行时判断程序应该是使用双页模式还是单页模式呢?这就需要借助限定符(Qualifiers)来实现了。

通过一个例子来学习一下它的用法,修改项目中 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>

这里将多余的代码都删掉,只留下一个左侧碎片,并让它充满整个父布局。接着在 res 目录下新建 layout-large 文件夹,在这个文件夹下新建一个布局,也叫做 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 布局只包含了一个碎片,即单页模式,而 layout-large/activity_fragment 布局包含了两个碎片,即双页模式。其中 large 就是一个限定符,那些屏幕被认为是 large 的设备就会自动加载 layout-large 文件夹下的布局,而小屏幕的设备则还是会加载 layout 文件夹下的布局。

然后将 ActivityreplaceFragment() 方法注释掉,并在平板模拟器上重新运行程序, 效果如图:

双页模式运行效果

再启动一个手机模拟器,并在这个模拟器上重新运行程序,效果如图:

单页模式运行效果

Android 中一些常见的限定符可以参考下表:

Android 中一些常见的限定符

4.4.2 使用最小宽度限定符

有时候希望可以更加灵活地为不同设备加载布局,不管它们是不是被系统认定为 “ large ”,这时就可以使用最小宽度限定符(Smallest-width Qualifier)了。

最小宽度限定符允许我们对屏幕的宽度指定一个最小指(以 dp 为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局。

在 res 目录下新建 layout-sw600dp 文件夹,然后在这个文件夹下新建 activity_fragment .xml 布局,代码与上面 layout-large/activity_fragment 中的一样。

...

这就意味着,当程序运行在屏幕宽度大于 600dp 的设备上时,会加载 layout-sw600dp/ activity_fragment 布局,当程序运行在屏幕宽度小于 600dp 的设备上时,则仍然加载默认的 layout/activity_fragment 布局。

4.5 碎片的最佳实践——一个简易版的新闻应用

需求:一个简易的新闻应用,可以同时兼容手机和平板。

首先,在 app/build.gradle 中添加后面需要用到的 RecyclerView 依赖库:

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'
}

接下来,准备好一个新闻的实体类,新建类 News,代码如下:

/**
 * 新闻实体类
 * Created by KXwon on 2016/12/12.
 */

public class News {

    private String title;   // 新闻标题

    private String content; // 新闻内容

    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;
    }
}

接着新建一个 news_content_frag.xml 布局,作为新闻内容的布局:

<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>

新闻内容的布局主要分为两个部分,头部显示新闻标题,正文显示新闻内容,中间使用一条细线分隔开。

然后再新建一个 NewsContentFragment 类,如下:

/**
 * 新闻内容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;
    }

    /**
     * 将新闻标题和新闻内容显示在界面上
     * @param newsTitle   标题
     * @param newsContent 内容
     */
    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); // 刷新新闻的标题
        newsContentText.setText(newsContent); // 刷新新闻的内容
    }
}

这样就把新闻内容的碎片和布局创建好了,但它们都是在双页模式下使用的,若要在单页模式中使用,还需创建一个活动 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,相当于把 news_content_frag 布局的内容自动加了进来。

然后编写 NewsContentActivity 的代码如下:

public class NewsContentActivity extends AppCompatActivity {

    /**
     * 构建Intent,传递所需数据
     * @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);
        // 获取传入的新闻标题、新闻内容
        String newsTitle = getIntent().getStringExtra("news_title");
        String newsContent = getIntent().getStringExtra("news_content");
        // 获取 NewsContentFragment 实例
        NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager()
                .findFragmentById(R.id.news_content_fragment);
        // 刷新 NewsContentFragment 界面
        newsContentFragment.refresh(newsTitle, newsContent); 
    }
}

上述代码,在 onCreate() 方法中通过 Intent 获取传入的新闻标题和内容,然后调用 FragmentManagerfindFragmentById() 方法得到 NewsContentFragment 的实例,接着调用它的 refresh() 方法,并将新闻的标题和内容传入,显示数据。(关于 actionStart() 方法可以阅读前面的探究活动2.5.2相关笔记。)

接下来还需再创建显示新闻列表的布局 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 子项的布局:

<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"/>

子项的布局就只有一个 TextView

新闻列表和子项布局都创建好了,接下来就需要一个用于展示新闻列表的地方。这里新建 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 布局时,为双页模式
            isTowPane = true;
        }else {
            // 找不到 news_content_layout 布局时,为单页模式
            isTowPane = false;
        }
    }
}

为实现上述 onActivityCreated() 方法中判断当前时双页还是单页模式,需要借助限定符,首先修改主布局 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>

上述代码表示,在单页模式下只会加载一个新闻标题的碎片。

然后在 res 目录下新建 layout-sw600dp 文件夹,在这个文件夹下再新建一个 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>

上述代码表示,在双页模式下会同时加载两个碎片,并将新闻内容碎片放在 FrameLayout 布局下,这个布局 id 正是 news_content_layout。因此能找到这个 id 的时候就是双页模式,否则就是单页模式。

现在已经将绝大多数工作完成了,剩下至关重要的一点,就是在 NewsTitleFragemt 中通过 RecyclerView 将新闻列表展示出来。接下来在 NewsTitleFragemt 中新建一个内部类 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 中的内容
                        NewsContentFragment newsContentFragment = (NewsContentFragment)
                                getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(), news.getContent());
                    } else {
                        // 若是单页模式,则直接启动 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();
        }

    }

需要注意的是,这里把适配器写成内部类是为了直接访问 NewsTitleFragment 的变量,比如 isTowPane

现在还剩最后一步收尾工作,就是向 RecyclerView 中填充数据了。修改 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布局时,为双页模式
            isTwoPane = true;
        } else {
            // 找不到news_content_layout布局时,为单页模式
            isTwoPane = false;
        }
    }

    /**
     * 初始化50条模拟新闻数据
     * @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("新闻内容吼吼吼" + i + "!"));
            newsList.add(news);
        }
        return newsList;
    }

    /**
     * 随机生成不同长度的新闻内容
     * @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();
    }

    . . . 
}

到这里,所有的代码编写工作就完成了,运行程序,效果如下:

单页模式的新闻列表界面

点击一条新闻,会启动一个新的活动来显示新闻内容:

单页模式的新闻内容界面

接下来把程序在平板上运行,同样点击一条新闻,效果如下:

双页模式的新闻标题和内容界面

好了,关于碎片的内容学习到这,下篇文章将学习广播机制...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容