澳门新葡萄京官网注册 13

Android RecyclerView 简介与实例

RecyclerView是传统ListView的一个很好的替代,具有很好的拓展性,初次接触RecyclerView可能会被其复杂的逻辑搞晕,本文就以一个简单的实例带小伙伴们理清其中的关系。

澳门新葡萄京官网注册 1

本章主要讲述了 RecyclerView
的基础使用,单例设计模式以及通过抽象的统一的 activity 来托管
fragment(以减少重复代码量)。

GitHub 地址:
完成第九章

一 添加依赖包

本文所使用的IDE为AndroidStudio。

依次点击File—>Project
Structure—>左下角Module的app—>Dependencis—>点击左下方的+号,选择recycler
view即可。

我们采用RecyclerView来实现如上图红色线框内的列表视图。
RecyclerView的运作由三个组件协作完成:

1. 单例(SingleInstance)

单例是特殊的 JAVA
类,在创建实例的时候,一个单例类仅允许创建一个实例。应用能在内存里多久,单例就能存在多久,因此将对象列表保存在单例里的话,就能随时获取到数据,而不用管
activity 和 fragment
的生命周期怎么变化。不过当应用被从内存里移除的时候,单例对象就不复存在了。

要创建单例,需要创建一个带有私有构造方法及 get()
方法的类,如果实例已经存在了,get()
方法就直接返回它,如果还不存在,就需要调用构造方法创建它。书上的代码是这样的:

public class CrimeLab {
    //下面这个静态对象只会创建一次
    private static CrimeLab sCrimeLab;

    private List<Crime> mCrimes;

    //程序的其他部分需要使用时,调用下列方法,当第一次使用的时候创建这个对象,如果不是第一次使用的时候就直接返回静态对象。
    public static CrimeLab get(Context context) {
        if (sCrimeLab == null) {
            sCrimeLab = new CrimeLab(context);
        }
        return sCrimeLab;
    }

    //私有的构造方法,只在 get 方法中使用
    private CrimeLab(Context context) {
        mCrimes = new ArrayList<>();
        //初始化数据的语句
        ………………
    }

    //由于对象只创建了一次,故而数据只有一份
    public List<Crime> getCrimes() {
        return mCrimes;
    }

    public Crime getCrime(UUID id) {
        for (Crime crime : mCrimes) {
            if (crime.getId().equals(id)) {
                return crime;
            }
        }
        return null;
    }
}

单例能方便地控制模型层对象,由一个单例类来控制数据,所有的修改都由它处理,会使数据的一致性控制更加简便。

但是万事总有缺点,

  • 首先,单例无法做到持久的存储,应用的内存被回收时,单例就不复存在了。
  • 其次,单例还不利于单元测试。
  • 最后,单例还容易被滥用,需要注意的是有充足的理由时才使用单例模式存储共享数据。

二 准备工作

首先创建一个名为NotesListFragment的fragment,对应的布局文件名为fragment_notes_list。接着将该fragment加入到主Activity中(关于如何在Activity中操作fragment将另作文章说明,此处省略啦),接下来在NotesListFragment中定义一个私有字段

private RecyclerView noteRecycler;

我们将要实现的是一个显示笔记的RecyclerView,这里将笔记类定义如下:

package com.aristark.note;

import java.util.Date;
import java.util.UUID;
public class Note {

    private UUID uuid;
    private String title;
    private String content;
    private Date date;
    private String tag;

    public Note{
        uuid = UUID.randomUUID();
        date = new Date();
    }

    public UUID getUuid() {
        return uuid;
    }

    public Date getDate() {
        return date;
    }

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

    public String getTitle() {
        return title;
    }

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

    public String getContent() {
        return content;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public String getTag() {
        return tag;
    }
}

为了操作方便,我们再创建一个NoteLab类:

package com.aristark.note;

import android.content.Context;
import java.util.ArrayList;

public class NoteLab {
    private static NoteLab sNoteLab; //for the global use
    private ArrayList<Note> notes;

    private NoteLab(Context context){
        notes = new ArrayList<Note>();

        //generate 100 Note Objects
        for (int i=0;i<100;i++){
            Note note = new Note();
            note.setTitle("this is title "+i);
            note.setContent("this is content"+i+"balabalabalabalalabalabalabalabalala/nbalabalabalabalalabalabalabala    balala/nbalabalabalabalalabalabalabalabalala/nbalabalabalabalalabalabalab    alabalala/nbalabalabalabalalabalabalabalabalala/n");
            notes.add(note);
        }
    }

    public static NoteLab getNoteLab(Context context){
        if (sNoteLab == null){
            sNoteLab = new NoteLab(context);
        }

        return sNoteLab;
    }

    public ArrayList<Note> getNotes() {
        return notes;
    }
}

注意体会该类所使用的单例模式,sNoteLab以静态方式存在,既节省了内存,又可以让应用的各个部件方便的访问。在构造方法NoteLab中,我们生成100个Note对象以作后面的测试。

  • 视图组件:将数据以列表的形式展示,由RecyclerView类实现
  • 适配器:将一组数据绑定到RecyclerView,由RecyclerView.Adapter内部类实现
  • 列表项缓存:用来提高列表视图运转效率,由RecyclerView.ViewHolder内部类实现,可以看作是列表项中各视觉元素对象的容器。

2. 使用抽象 activity 托管 fragment

由于书中大部分 FragmentActivity
的是类似的,所以可以直接创建一个抽象的类用于被继承,简化代码。

回忆一下使用 fragment 的步骤:

  1. 在托管的 activity 的 onCreate() 方法中新建一个 FragmentManager
    对象(getSupportFragmentManager() 方法或者 getFragmentManager()
    方法)。
  2. 使用该对象的 findFragmentById() 方法找到放置 fragment 的位置。
  3. 如果 fragment 没有建立,就新建一个 fragment 对象,并使用
    FragmentManager 对象的 beginTransaction().add().commit()
    的连续方法将 fragment 事务提交到队列中

在这其中,只有新建 fragment 对象是与具体 fragment
有关的,那么我们可以将其写成一个抽象的函数:

protected abstract Fragment createFragment();

三 ViewHolder和Adapter

这两个类是实现列表的关键,其实从字面含义很容易猜测这两个类的作用,ViewHolder操作的是列表每个部分的布局,而Adapter则是用数据去填充View,虽然解释的不是很准确,但姑且这么理解是没问题的。那么下面我们就在NotesListFragment里创建这两个类:

1 首先创建NoteHolder

private class NoteHolder extends RecyclerView.ViewHolder{

    public NoteHolder(View root) {
        super(root);
    }       
}

这个类很简单,值得注意的是自动创建的构造方法所传入的参数名叫itemView,这里我将其改为root,因为接下来我们通过这个构造方法传进来的是一个完整的布局文件,而不仅仅是一个控件。

2 创建Adapter

private class NoteAdapter extends RecyclerView.Adapter<NoteHolder>{
    private List<Note> notes;

    public NoteAdapter(List<Note> notes){
        this.notes = notes;
    }

    public void setNotes(List<Note> notes) {
        this.notes = notes;
    }

    @Override
    public NoteHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(NoteHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }
}

前面说了Adapter是有关于数据的操作了,因此在类的内部定义notes字段也很容易理解,我们再来看看这里覆写的三个方法,onCreateViewHolder返回值是NoteHolder,因此它是用来创建ViewHolder,onBindViewHolder则可以直接操作NoteHolder,position指的是当前View处在整个List的位置(我们的目的是要创建类似于微信消息列表的一个列表,其每个部件的布局其实是一样的,只是填充的数据不一样而已),以便按照当前的位置填入相应的数据。getItemCount则是返回需要相应布局的总数。talk
is cheap,show me the
code。说再多恐怕也难以表达,下面看代码,多看几遍,自然而然就会用了。

private class NoteAdapter extends RecyclerView.Adapter<NoteHolder>{
    private List<Note> notes;

    public NoteAdapter(List<Note> notes){
        this.notes = notes;
    }

    public void setNotes(List<Note> notes) {
        this.notes = notes;
    }

    @Override
    public NoteHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
        View view = layoutInflater.inflate(R.layout.list_item_note,parent,false);
        return new NoteHolder(view);
    }

    @Override
    public void onBindViewHolder(NoteHolder holder, int position) {
        Note note = notes.get(position);
        holder.bindView(note);
    }

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

其中R.layout.list_item_note的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout     xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/list_item_note_title" />

</LinearLayout>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/list_item_note_content" />

</LinearLayout>

这里只简单的使用了Note类中title和content两个字段,其实就是这个view就是形成整个列表,只是依次填充类不同的数据而已。


3. RecyclerView, Adapter 和 ViewHolder

对于一个列表,之前有 ListView,网格有
GridView,但要实现更加复杂的布局和功能,比如瀑布流的时候,就有些力不从心了。RecyclerView
是 Google 推出 Android 5.0
时一并推出的控件,其具有强大的功能和高度的解耦,有助于开发者实现更加多变具有拓展能力的布局。

澳门新葡萄京官网注册 ,四 RecyclerView的使用

前面已经准备好了ViewHolder和Adapter,接下来要做的就是将这些部件组装在一起,最后将整个fragment贴出来,大家注意onCreateView里是
Ruhr操作的!

public class NotesListFragment extends Fragment {
    private RecyclerView noteRecycler;
    private NoteAdapter noteAdapter;

    public NotesListFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
        // Inflate the layout for this fragment
//return inflater.inflate(R.layout.fragment_notes_list, container, false);
        View root = inflater.inflate(R.layout.fragment_notes_list,container,false);

        noteRecycler = (RecyclerView) root.findViewById(R.id.note_recycler_view);
        noteRecycler.setLayoutManager(new LinearLayoutManager(getActivity()));
        NoteLab noteLab = NoteLab.getNoteLab(getActivity());
        ArrayList<Note> notes = noteLab.getNotes();
        noteAdapter = new NoteAdapter(notes);
        noteRecycler.setAdapter(noteAdapter);
        return root;
    }

    private class NoteHolder extends RecyclerView.ViewHolder{
        private TextView noteTitle;
        private TextView noteContent;

        public NoteHolder(View root) {
            super(root);
            noteTitle = (TextView) root.findViewById(R.id.list_item_note_title);
            noteContent = (TextView) root.findViewById(R.id.list_item_note_content);
        }

        public void bindView(Note note){
            noteTitle.setText(note.getTitle());
            noteContent.setText(note.getContent());
        }

    }

    private class NoteAdapter extends RecyclerView.Adapter<NoteHolder>{
        private List<Note> notes;

        public NoteAdapter(List<Note> notes){
            this.notes = notes;
        }

        public void setNotes(List<Note> notes) {
            this.notes = notes;
        }

        @Override
        public NoteHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
            View view = layoutInflater.inflate(R.layout.list_item_note,parent,false);
            return new NoteHolder(view);
        }

        @Override
        public void onBindViewHolder(NoteHolder holder, int position) {
            Note note = notes.get(position);
            holder.bindView(note);
        }

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

}

1. 添加RecyclerView组件

首先删除“阅读”按钮。
然后仿照上节中拖放漂浮按钮的操作,选择设计视图中的“Palette”栏下的“AppCompat”项,将其下的RecyclerView组件拖放到布局中:

澳门新葡萄京官网注册 2

然后切换到XML视图,为RecyclerView添加id为“note_list”

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

3.1 RecyclerView 简介及工作原理

要使用 RecyclerView 显示视图,需要三样东西,即RecyclerView,Adapter,
ViewHolder,它们的任务各不相同:

  • RecyclerView 是视图层对象,负责回收和定位屏幕上的 ViewHolder
  • ViewHolder 只负责容纳 View 视图
  • Adapter 是控制器对象,负责创建必要的
    ViewHolder,从模型层获取数据并与 ViewHolder 绑定,然后提供给
    RecyclerView 显示

RecyclerView 需要显示视图对象时,就会去找它的
Adapter,然后会有如下调用。

  1. 首先,调用 Adapter 的 getItemCount() 方法,RecyclerView
    询问数组列表中包含多少个对象。
  2. 接着,调用 Adapter 的 createViewHolder(ViewGroup, int) 方法创建
    ViewHolder 以及 ViewHolder 要显示的视图。
  3. 最后,RecyclerView 会传入 ViewHolder 及其位置,调用
    onBindViewHolder(ViewHolder, int) 方法。Adapter
    会找到目标位置的数据并用数据填充到 ViewHolder 的视图上。

过程图示如下:

澳门新葡萄京官网注册 3

这里写图片描述

需要注意的是,相对于 onBindViewHolder(ViewHolder, int)
方法,createViewHolder(ViewGroup, int)
方法的调用并不频繁。一旦创建了够用的 ViewHolder,RecyclerVIew
就会停止调用 createViewHolder() 方法,然后通过回收旧的 ViewHolder
来节约时间和内存。

后记

最后给大家贴出最后的效果图吧!

这是我第一次写博客,解释的不多,代码较多,一来是因为我没有很多表达的经验,二来我觉得很多时候类名,函数名足以说明它的用途,过多解释怕会误导大家。whatever,还是希望得到大家的支持,我会在坚持写代码的同时也坚持把博客写下去,是对自己学到的知识的总结,也希望确确实实可以帮到需要帮助的朋友。我的QQ891871898,大家有任何技术交流的问题都可以联系我,批评也可以!另外,求工作!

2. 定义列表项视图布局

在设计图中,列表项视图如下:

澳门新葡萄京官网注册 4

根据这个设计图,我们先分析应当怎样设计布局。以下是可行方案的一种,我们依据这个方案创建布局文件:

澳门新葡萄京官网注册 5

在layout目录下创建新的布局文件。右键点击layout目录,在弹出菜单中选择“New->Layout
resource
file”
。在弹出的对话框中输入文件名为“note_list_item”,修改“Root
element”
项为“LinearLayout”:

澳门新葡萄京官网注册 6

按照上图的布局文件方案的描述来创建布局中的各项元素。注意UI设计中使用恰当的外边距(margin)内边距(padding)使布局美观。我这里给出一个note_list_item.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="90dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:orientation="horizontal"
        android:gravity="center_vertical">

        <!--标题-->
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="Title"
            android:textColor="#000000"
            android:textSize="24sp"/>

        <!--时间-->
        <TextView
            android:id="@+id/tv_create_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="2018/03/03"
            android:textColor="#00CCFF"
            android:textSize="18sp" />

    </LinearLayout>

    <!--正文摘要-->
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:text="这里是正文部分的摘要"
        android:maxLines="2"
        android:textColor="#999999"
        android:textSize="18sp" />

    <!--分隔线-->
    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#999999"/>
</LinearLayout>

3.2 使用 RecyclerView

介绍了 RecyclerView 的各种细节,我们来看看它具体怎么使用吧。

3. 实现ViewHolder

前面提到了,ViewHolder用作列表项视图元素的缓存,用来提高列表视图显示的效率,使其滚动中尽量平滑。通常它是列表项中各个视觉元素的容器。
在我们的笔记列表视图的ViewHolder中,应当包含笔记标题正文创建时间对应的各个视图元素。这些元素已经在上一步骤所创建的布局文件中存在。
打开NoteListActivity.java文件,创建内部类NoteViewHolder,它是RecyclerView.ViewHolder的子类.
键入以下代码:

    private class NoteViewHolder extends RecyclerView.ViewHolder {

    }

此时,编辑器中会以红色波浪线的方式提示错误。显然,我们的类继承了RecyclerView.ViewHolder,却还缺少某些必要的要素。
将光标定位到错误区域,按快捷键“Alt+Enter”,可以弹出功参考的纠错方案列表:

澳门新葡萄京官网注册 7

原来是没有创建对应的构造方法。选择第一项,Android Studio帮我们自动创建:

        public NoteViewHolder(View itemView) {
            super(itemView);
        }

观察此构造方法,它接收一个View类型的参数itemView。这个itemView对象对应的就是我们在note_list_item.xml中定义的列表项布局。

接下来,为我们的NoteViewHolder类增加三个对应于要显示的视图元素的属性,并在构造方法中初始化:

    private class NoteViewHolder extends RecyclerView.ViewHolder {
        private TextView titleView;
        private TextView createTimeView;
        private TextView contentView;

        public NoteViewHolder(View itemView) {
            super(itemView);
            titleView = itemView.findViewById(R.id.tv_title);
            createTimeView = itemView.findViewById(R.id.tv_create_time);
            contentView = itemView.findViewById(R.id.tv_content);
        }

    }

3.2.1 添加 RecyclerView 依赖库

在 File – Project Structure 菜单项,选择 app 模块,然后单击 Dependencies
选项页,单击加号,找到并添加 recyclerview-v7 支持库。

4. 创建适配器

适配器将数据集与列表视图绑定起来。

3.2.2 在布局文件中使用 RecyclerView 并在 JAVA 代码中声明

示例 JAVA 代码如下:

mCrimeRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

RecyclerView 视图创建完成后,就立即转交给了 LayoutManager
对象。LayoutManager 实际上负责定位列表项和定义屏幕滚动行为,因此如果没有
LayoutManger 的支持,不仅 RecyclerView
无法工作,还会导致应用崩溃。在示例中使用的 LinearLayoutManager
是以竖直列表的方式展示列表项,内置的还有GridLayoutManager
,还有很多第三方的库可以使用。

第一步,在NoteListActivity.java中定义我们自己的适配器类NoteAdapter

它是RecyclerView.Adapter类的子类。这里需要使用上一步骤中定义的NoteViewHolder类作为范型参数,同时要根据出错提示创建需要重写的几个方法:

    private class NoteAdapter extends RecyclerView.Adapter<NoteViewHolder> {

        @Override
        public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return null;
        }

        @Override
        public void onBindViewHolder(NoteViewHolder holder, int position) {

        }

        @Override
        public int getItemCount() {
            return 0;
        }
    }

3.2.3 实现 Adapter 和 ViewHolder

ViewHolder 需要做的事情很简单,就是将自定义的 view
中的组件找出来并绑定在这个 ViewHolder 的成员变量上。

比如定义了一个有标题和图片的 item,那么这个 Holder 可以这么写:

class ItemHolder extends RecyclerView.ViewHolder {

    public TextView mTitle;
    public ImageView mImg;

    public ItemHolder(View itemView) {
        super(itemView);

        mTitle = (TextView) itemView.findViewById(R.id.tv_item_title);
        mImg = (ImageView) itemView.findViewById(R.id.iv_item_img);
    }
}

如果有监听器的话,也可以写在构造函数中

对于 Adapter 来说,要做的事就更多了,我来一一梳理:

  • 从模型层获取数据
    一般在 Adapter 内部声明一个数据模型的成员变量,在 Adapter
    的构造函数中进行初始化

  • 重写 ViewHolder 这个父类的三个方法

    • onCreateViewHolder(ViewGroup parent, int viewType)
      每当 RecyclerView 需要新的 View
      视图来显示列表项的时候就会调用这个方法。在这其中,我们创建 View
      视图,然后封装到 ViewHolder 中,此时并不需要向视图加载数据。

     //一个典型的 onCreateViewHolder 方法的内部
     LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
     View view = layoutInflater.inflate(R.layout.list_item, parent, false);
     return new ItemHolder(view);
    
  • onBindViewHolder(ItemHolder holder, int position)
    这个方法负责将 ViewHolder 的 View 视图和模型层的数据绑定起来。拿到
    ViewHolder
    和列表项在数据集中的索引位置后,我们通过索引位置找到要显示的数据进行绑定。绑定完毕后,刷新显示
    View 视图。

//典型的 onBindViewHolder 方法内部
Data data = mDataList.get(position); 
// 注意上面的 mDataList 就是在 Adatper 的构造函数中初始化的 Adapter 的成员变量
holder.mTitle.setText(data.getTitle(position));
holder.mImg.setImageResource(data.getImgRes(position));
  • getItemCount()
    返回要展示的数据的数量,一般是数据集的 size

到此一个基本的 Adapter 就创建完了,在主程序中声明并初始化 Adapter,调用
RecyclerView 的 setAdapter 方法即可显示出列表了~


GitHub Page:
kniost.github.io
简书:http://www.jianshu.com/u/723da691aa42

第二步,添加一个Note类对象的列表(List)作为属性。

因为我们的列表视图展示的是笔记列表。前面章节我们已经为笔记创建了数据模型类Note,在这里便可以定义如下:

        private ArrayList<Note> notes = new ArrayList<>();

        public void setNotes(ArrayList<Note> notes) {
            // 现将原列表清空,再将传入的列表元素全部加入
            this.notes.clear();
            if (notes != null) {
                this.notes.addAll(notes);
            }
        }

可以看到,我们同时创建了setNotes(),用来在数据集发生变化时重新设定笔记列表。

第三步,分别实现适配器类中继承的3个方法:

  • onCreateViewHolder():这里创建ViewHolder对象,代码如下:

        @Override
        public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // 根据列表项布局文件创建视图对象
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            View view = inflater.inflate(R.layout.note_list_item, parent, false);
            // 基于上面的视图对象创建ViewHolder对象并返回
            NoteViewHolder vh = new NoteViewHolder(view);
            return vh;
        }
  • onBindViewHolder():这里将实际的某一条笔记数据绑定到ViewHolder中对应的各个视图元素

        @Override
        public void onBindViewHolder(NoteViewHolder holder, int position) {
            // 取对应位置的笔记对象
            Note note = notes.get(position);
            // 设置对应ViewHolder对象中各视觉元素的值
            holder.titleView.setText(note.getTitle());
            holder.createTimeView.setText(formatTime(note.getCreateTime()));
            holder.contentView.setText(note.getContent());
        }

可以看到,在设置创建时间的时候,并没有之间用note.getCreateTime()的返回值作为文本视图的内容。因为这是一个毫秒值,并不直观,需要将其转换为我们能够读懂的格式。为此,单独定义工具函数formatTime()

    private String formatTime(long time) {
        // 按照给定的格式串("yyyy/MM/dd")转换
        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
        return format.format(new Date(time));
    }
  • getItemCount():统计当前数据集中对象数量。我们直接返回笔记列表长度即可:

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

这三个方法,我们并不会调用,而是供RecyclerView内部机制调用的“回调方法”,也是我们能够将数据集绑定到列表视图的入口。


5. 初始化RecyclerView

在第3步和第4步,我们分别创建并实现了RecyclerView三个要素中的ViewHolder适配器。接下来我们最终来完成RecyclerView的初始化和展示。
在这里,要完成以下几个步骤的工作:

  • Activity的布局中取得RecyclerView对象
  • 设定其为垂直方向列表的形式
  • 创建适配器对象并设置给RecyclerView对象
    NoteListActivityonCreate()回调方法的末尾添加如下代码以实现之:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_note_list);
        ...
        RecyclerView recyclerView = findViewById(R.id.note_list);
        // 设定为垂直列表
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
        // 创建并设置适配器
        NoteAdapter adapter = new NoteAdapter();
        recyclerView.setAdapter(adapter);
    }

运行程序,看到如下效果:

澳门新葡萄京官网注册 8

没错,还是空白,因为我们实际上并没有绑定任何笔记到列表视图中。由于我们目前并没有真正的为笔记的存储创建数据库,因此我们在下一节里制造一些测试用的笔记数据,用来查看笔记列表视图是否正常工作。


6. 创建数据仓库(repository)

Repository的意思是“仓库”。我们在这里进行以下几步工作:

  • 创建一个抽象的仓库接口(INoteRepository)来为应用程序的UI提供获取数据的入口。
  • 定义一个用于临时测试的仓库类(TestNoteRepository),它实现INoteRepository中定义的方法。
  • 利用面向对象思想中的“多态”机制,将TestNoteRepository的对象作为实际使用的仓库。
    将来,我们建立了数据库后,就可以定义基于数据库存储的仓库类来代替现在的测试仓库类。这样一套模式的优势是,将来不必大规模修改UI模块的代码;缺点是比较繁琐。

创建仓库接口(INoteRepository)

将来针对不同的实体模型,可能会增加不同的仓库接口,因此我们单独建立一个仓库对应的包,在这个包里面创建我们的接口(interface):

  • 创建于model子包并列的子包“repository”

澳门新葡萄京官网注册 9

com.jing.app.sn.repository

  • 在新建的包下创建接口INoteRepository
    选择包名,然后右键选择“New->Java
    Class”
    ,填写名称为“INoteRepository”,并将“Kind”项设置为“Interface”

澳门新葡萄京官网注册 10

点击“OK”,接口源文件(INoteRepository.java)即在包下生成。打开它,为其添加一个接口方法getAllNotes(),用来提供全部笔记的列表:

public interface INoteRepository {
    ArrayList<Note> getAllNotes();
}

目前我们仅用到获取全部笔记这一个操作,所以先不考虑其它操作。

定义一个用于临时的仓库类(TestNoteRepository)

  • 在包repository下新建Java类TestNoteRepository,令其实现(implement)INoteRepository接口:

澳门新葡萄京官网注册 11

  • 打开TestNoteRepository.java文件,根据错误提示产生必要的方法定义,结果如下:

public class TestNoteRepository implements INoteRepository {

    @Override
    public ArrayList<Note> getAllNotes() {
        return null;
    }
}
  • 修改getAllNotes()方法,使其返回一个含有多条笔记记录的列表,例如:

    @Override
    public ArrayList<Note> getAllNotes() {
        ArrayList<Note> notes = new ArrayList<>();
        notes.add(new Note(1, "笔记1", "笔记1正文", System.currentTimeMillis()));
        notes.add(new Note(2, "笔记2", "笔记2正文", System.currentTimeMillis()));
        notes.add(new Note(3, "笔记3", "笔记3正文", System.currentTimeMillis()));
        notes.add(new Note(4, "笔记4", "笔记4正文", System.currentTimeMillis()));
        notes.add(new Note(5, "笔记5", "笔记5正文", System.currentTimeMillis()));
        notes.add(new Note(6, "笔记6", "笔记6正文", System.currentTimeMillis()));
        notes.add(new Note(7, "笔记7", "笔记7正文", System.currentTimeMillis()));
        notes.add(new Note(8, "笔记8", "笔记8正文", System.currentTimeMillis()));
        notes.add(new Note(9, "笔记9", "笔记9正文", System.currentTimeMillis()));
        notes.add(new Note(10, "笔记10", "笔记10正文", System.currentTimeMillis()));
        return notes;
    }

为NoteListActivity提供数据

  • 回到NoteListActivity.java文件,增加INoteRepository接口类型的属性定义

public class NoteListActivity extends AppCompatActivity {
    // 笔记数据仓库接口,并绑定到测试仓库
    private INoteRepository noteRepository = new TestNoteRepository();
    ...
}
  • 定位到onCreate()回调方法,为适配器指定数据集:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        NoteAdapter adapter = new NoteAdapter();
        // 为适配器设定数据集
        adapter.setNotes(noteRepository.getAllNotes());

        recyclerView.setAdapter(adapter);
    }
  • 运行程序查看效果:

澳门新葡萄京官网注册 12


7. 实现列表项点击响应

  • 点击列表项,列表项在视觉上应当有一个变化使用户感知到自己的操作。为此我们为列表项布局设定随点击变化的背景色

第一步,先定义两种颜色

先打开res/values目录下的colors.xml文件,向里面增加两个颜色项目:

    <color name="note_list_item_bg_pressed">#D9D9D9</color>
    <color name="note_list_item_bg_released">#FFFFFF</color>

第二步,创建背景资源

选择res/drawable文件夹,点击右键后在弹出菜单中选择“New->Drawable
resource
file”
,在弹出对话框中输入文件名为“note_list_item_bg”

澳门新葡萄京官网注册 13

  • 点击“OK”完成创建并打开文件。在XML视图下,添加对“按下(pressed)”状态的处理,当按下时,设定背景色为浅灰色,松开后设为白色

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@color/note_list_item_bg_pressed"/>
    <item android:state_pressed="false" android:drawable="@color/note_list_item_bg_released"/>
</selector>
  • 打开列表项布局文件note_list_item.xml,为它的根元素设定背景色,即设置android:background属性为我们刚创建的xml文件;同时还要将android:clickable设为true,以使其可以接受点击事件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:clickable="true"
    android:background="@drawable/note_list_item_bg">
  • 编译运行,查看效果。

8. 实现点击列表项操作

点击列表项,将打开笔记进行阅读。此处我们仅仅将打开阅读页面与点击列表项操作进行对接,即代替任务1中“阅读”按钮。
打开NoteListAcitivity.java文件,执行以下修改:

  • 修改原“阅读”按钮响应方法onReadNote()为如下形式:

    private void onReadNote() {
        // 启动阅读页面
        Intent intent = new Intent(this, ReadNoteActivity.class);
        startActivity(intent);
    }
  • 修改内部类NoteViewHolder的构造方法,为itemView(对应于列表项布局的根元素)添加点击操作处理代码:

    public NoteViewHolder(View itemView) {
            super(itemView);
            titleView = itemView.findViewById(R.id.tv_title);
            createTimeView = itemView.findViewById(R.id.tv_create_time);
            contentView = itemView.findViewById(R.id.tv_content);

            // 响应点击列表项事件
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onReadNote();
                }
            });
        }

    }
  • 运行程序,效果如下:
![](https://upload-images.jianshu.io/upload_images/10901316-f666efe1657cbcc9.gif)

总结

RecyclerView(或者老旧的ListView)来展示列表数据是比较繁琐的过程。但是其中要领主要包括:

  • 正确的在布局中放置RecyclerView
  • 正确的创建列表项的布局文件
  • 正确的创建ViewHolder
  • 正确创建Adapter,并实现3个回调方法以完成数据绑定

发表评论

电子邮件地址不会被公开。 必填项已用*标注