第十三章 强大的滚动控件——RecyclerView

1. 引言

  上两章简单讲了下ListView的一些基本用法。ListView由于其强大的功能,在Android 开发的过程中贡献卓越。但是由于它自身的部分缺点,不使用一些技巧来提高它的运行效率的话,它的性能体验会非常的差。还有就是ListView的扩展性不够完美,我们在实现数据纵向排列的同时,还需要数据的横向排列。
 RecyclerView可以说是ListView的增强版,不仅可以轻松实现和ListView同样的效果,还会优化ListView存在的各种问题。目前官方更推荐RecyclerView,相信未来也会有更多的程序逐渐从ListView转向Recyclerview。

2. RecyclerView的基本用法

  RecyclerView也属于新增的控件,所以为了能够使用RecyclerView,首先得在项目的build.gradle的dependencies中添加相应的依赖库。


dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:recyclerview-v7:25.3.1'
}

添加完依赖库之后,Sync Now来进行同步,修改activity_main.xml文件中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.recyclerviewdemo.MainActivity">

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

</LinearLayout>

在你添加完了RecyclerView的依赖包之后,添加依赖控件的,记得显示完整的包名。为了和之前的ListView的项目有一个对比,这里用同样的图和布局模式。
1.item_list.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="100dp"
    android:gravity="center_vertical"
    android:padding="10dp">

    <ImageView
        android:id="@+id/iv_head"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher"
        android:scaleType="centerCrop"/>
    <TextView
        android:id="@+id/tv_student_name"
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        android:text="学生姓名"
        android:layout_gravity="center"
        android:gravity="center"/>

</LinearLayout>

2.Student.java 学生的实体类


public class Student {
    private  String name;
    private  int  imageId;

    public Student(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }
}


  1. 自定义一个适配器,让这个适配器继承RecyclerView.Adapter ,并将泛型指定为StudentAdapter.ViewHolder,其中ViewHolder是我们自己定义的一个内部类。

public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {

    private List<Student> mStudentList;

    public StudentAdapter(List<Student> mStudentList) {
        this.mStudentList = mStudentList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
        ViewHolder holder=new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Student student=mStudentList.get(position);
        holder.studentImage.setImageResource(student.getImageId());
        holder.studentname.setText(student.getName());

    }

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

    static  class  ViewHolder extends  RecyclerView.ViewHolder{

        private ImageView studentImage;
        private TextView  studentname;
        public ViewHolder(View itemView) {
            super(itemView);
            studentImage= (ImageView) itemView.findViewById(R.id.iv_head);
            studentname= (TextView) itemView.findViewById(R.id.tv_student_name);
        }
    }
}

  可能刚开始看的时候有点晕,哪里好理解了?我发四没有骗你,你等我慢慢给你分析下。首先我们定义了一个内部类 ViewHolder 继承RecyclerView.ViewHolder,然后ViewHolder下面会有一个构造函数ViewHolder(itemView)需要让你实现,你发现没有,你在构造函数传的这个View参数,其实就是RecyclerView中的子项的最外层布局。所以我们就可以通过findviewByid的方法来获取布局中实例。
  然后我们接下来继续分析,StudentAdapter 中也有一个构造函数,这个方法是用于将要展示的数据源传进来,并赋值给全局变量,后续的操作都是围着这个操作进行的。
  继续往下看,由于StudentAdapter 是继承RecyclerView.Adapter的,所以必须重写 onCreateViewHolder(), onBindViewHolder(),getItemCount()三个方法。 onCreateViewHolder()这个方法是创建ViewHolder 实例,然后将item_list.xml这个子布局也装载进去,并把加载出来的布局传入到构造函数当中,最后返回ViewHolder的实例。onBindViewHolder()这个方法是对RecyclerView的子项的数据进行赋值,会在子项被滚动到屏幕中的时候被执行。我们通过get(position)的方法得到当前项的Student的实例。getItemCount(),告诉RecyclerView,这个集合有多少个子项。直接返回数据的长度。

4.开始使用RecyclerView了。


public class MainActivity extends AppCompatActivity {
    private List<Student> studentList=new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化学生数据
        RecyclerView  recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);


    }

    private void initStudent() {
        for (int i = 0; i < 2; i++) {
            Student student1=new Student("学生一",R.mipmap.a1);
            studentList.add(student1);
            Student student2=new Student("学生二",R.mipmap.a2);
            studentList.add(student2);
            Student student3=new Student("学生三",R.mipmap.a3);
            studentList.add(student3);
            Student student4=new Student("学生四",R.mipmap.a4);
            studentList.add(student4);
            Student student5=new Student("学生五",R.mipmap.a5);
            studentList.add(student5);
            Student student6=new Student("学生六",R.mipmap.a6);
            studentList.add(student6);
            Student student7=new Student("学生七",R.mipmap.a7);
            studentList.add(student7);
            Student student8=new Student("学生八",R.mipmap.a8);
            studentList.add(student8);
            Student student9=new Student("学生九",R.mipmap.a9);
            studentList.add(student9);
            Student student10=new Student("学生十",R.mipmap.a10);
            studentList.add(student10);
            Student student11=new Student("学生十一",R.mipmap.a11);
            studentList.add(student11);
            Student student12=new Student("学生十二",R.mipmap.a12);
            studentList.add(student12);
            Student student13=new Student("学生十三",R.mipmap.a13);
            studentList.add(student13);

        }
    }

}

 可以看到我们使用同样的办法initStudent()初始化所有的学生对象,接着在OnCreate()方法中线获取到RecyclerView的实例。然后创建一个LinearLayoutManager对象,将它设置到RecyclerView当中,这里使用LinearLayoutManager是线性布局的意思。可以实现和ListView类似的效果。然后是创建Studentadapter的实例,将studentList实例数据传到StudentAdapter的构造函数当中。最后调用setAdapter()方法完成适配器的设置。这样RecyclerView和数据之间的关联就建立完成了。效果如下图所示:

recyclerviewdemo

3.实现横向滚动

 之前说了ListView不能设置横向排列,RecyclerView可以很好的弥补这个不足,使用方法很简单,在MainActivity加入一行代码就可以了。

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化学生数据
        RecyclerView  recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);

        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

        recyclerView.setLayoutManager(linearLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);
    }

发现没有,就多了一行代码 linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);设置布局的横向排列。默认是纵向排列的。显示效果如图所示:

横向排列

为啥子ListView不能横向滑动,而RecyclerView可以很轻松的实现呢?这主要的得益于RecyclerView出色的设计。ListView的布局排列是由自身去管理的,而RecyclerView将这个工作给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类按照这个借口的规范来实现,这样就可以制定出各种不同的排列的方式的布局。

4.瀑布流布局

RecyclerView除了提供了LinearLayoutManager线性布局排列以外,还提供了GridLayoutManager(网格布局)和StaggeredGridLayoutManager(瀑布流布局)。
这里稍稍修改了下item_list的布局:

<?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="wrap_content"
    android:orientation="vertical"
    android:padding="5dp"
    >

    <ImageView
        android:id="@+id/iv_head"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        android:scaleType="centerCrop"
        android:layout_gravity="center_horizontal"/>
    <TextView
        android:id="@+id/tv_student_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:text="学生姓名"
        android:layout_marginTop="10dp"/>

</LinearLayout>

代码也更改了一下,仔细看:

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化学生数据
        RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);

        StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);

        recyclerView.setLayoutManager(staggeredGridLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);


    }

对比下前面的发现就改变了一句是不是:
StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager (3, StaggeredGridLayoutManager.VERTICAL);
StaggeredGridLayoutManager 的参数:

  1. 第一个参数,是设置多少列,我这里设为3列
  2. 第二个参数 ,是指定布局的排列方向
    实现的效果如下图所示:
瀑布流布局

5.网格布局

在经过了这个多的布局之后,下面的的网格布局应该也猜到了,也是修改其中的LayoutManager。

  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initStudent();//初始化学生数据
        RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager gridLayoutManager=new GridLayoutManager(this,4);
        gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(linearLayoutManager);
        StudentAdapter adapter=new StudentAdapter(studentList);
        recyclerView.setAdapter(adapter);
    }

GridLayoutManager的两个参数:
1、第一个参数,是设置上下文对象
2、第二个参数是设置排列的行数
还有最后设置好排列的方向


网格布局

6.RecyclerView的点击事件

  正如ListView那样,RecyclerView也必须满足点击这个硬性要求的。但是与ListView不同的是,RecyclerView并没有提供类似setOnItemClickLisenter()这样的注册监听器的方法,而是需要我们自己给子项具体的view去注册点击事件。相比较实现起来,可能会稍微复杂一点。
  大兄弟,看到这里你是不是又有点晕了啊?都说了是比ListView更优秀,那搞个点击事件怎么实现起来还复杂了呢?...... 大哥,我真的没骗你,拳头放下来。因为ListView的点击处理,是点击一整个子项目,然后出发点击事件的是不,哪如果我只想子项目里面的某一个控件出发自己的点击事件呢?ListView并没有给出人性化的处理,处理起来比较麻烦。所以RecyclerView就放弃了子项的点击监听事件的监听器。所有的点击事件都由具体的View来处理,这样是不是没有这个烦恼了啊。
  这里讲一下它的点击事件,需要修改StudentAdapter的代码。我们先是修改了ViewHolder,在ViewHolder 添加studentView 变量来保存子项最外层的布局的实例。然后在onCreateViewHolder()方法中注册点击事件就可以了。这里分别为最外层的布局和ImageViewd都注册了点击事件。(意思是,它不仅给每个item都设置了点击事件,连item里面的imageview也设置了点击事件)。通过holder 获取子项的view,然后设置点击事件。通过holder获取子项的view的position。这样就拿到了子项的Student的数据。设置一个点击事件,来显示具体的数据值。在点击文字的时候,我们没有给文字设置点击事件,但是给studentView设置了点击事件,所以还是被最外层的布局捕获到,弹出Toast。

ublic class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {

    private List<Student> mStudentList;

    public StudentAdapter(List<Student> mStudentList) {
        this.mStudentList = mStudentList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
        final ViewHolder holder=new ViewHolder(view);

        holder.studentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Student student = mStudentList.get(position);
                Toast.makeText(v.getContext(), "你点击了"+student.getName(), Toast.LENGTH_SHORT).show();

            }
        });
        holder.studentImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Student student = mStudentList.get(position);
                Toast.makeText(v.getContext(), "你点击了"+student.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Student student=mStudentList.get(position);
        holder.studentImage.setImageResource(student.getImageId());
        holder.studentname.setText(student.getName());

    }

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

    static   class  ViewHolder extends  RecyclerView.ViewHolder{

        private ImageView studentImage;
        private TextView  studentname;
          View  studentView;

          public ViewHolder(View itemView) {
            super(itemView);
            studentView=itemView;
            studentImage= (ImageView) itemView.findViewById(R.id.iv_head);
            studentname= (TextView) itemView.findViewById(R.id.tv_student_name);
        }
    }


}

效果如图:

recyclerview的点击事件

项目代码github地址:https://github.com/wangxin3119/recyclerview

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

推荐阅读更多精彩内容