ღ⊙□⊙╱ 一个列子,搞懂Android RecyclerView!

学安卓的小伙伴们,今天咱们通过一个列子,来学学Android RecyclerView的用法。这个教程面向的读者定位是刚接触Android开发不久的新人,所以写的比较详细。Android开发大佬看这个教程,可能会有点啰嗦,尽量憋喷。
下图就是我们今天要实现的列子。_(•̀ω•́ 」∠)_



先来点吐槽:ღ⊙□⊙╱
之前窝在学这块的时候,真是费了不少精。当初搜教程的时候,我发现很多写教程的大佬基本有一个共同的尿性,那就是他们往往会说:“废话少说,直接上代码!”......
每当看到这句话,当时作为Android开发小透明的我心里总是很慌的,面对着一坨坨我撸不透搞不懂的代码,心里有一万句mmp油燃而生...... (;´༎ຶД༎ຶ`)


烟鬼正传...... 在开始之前,我得先硬塞给你一张图,先给你简单科普下RecyclerView的实现原理,让你心里有点数。我知道你可能还没接触过RecyclerView这东西,读下面这一段可能会犯晕。没事,先尽力尝试理解下就好,也不用刻意记忆。下面这段话和下面的图,我会贯穿本教程始终,慢慢的你就从了....... ←_← 

下面这里敲黑板了啊!要划重点了啊!ღ⊙□⊙╱
=================================================================
  1. RecyclerView在屏幕上显示的各个条目,加载的是同一个xml布局文件,只不过是往里面塞了不同的数据而已。(这个不难理解吧,你结合下面的图看看,每个条目都是一样的布局,只是数据不一样。)
  2. RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
  3. RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
  4. 有一个叫LayoutManager的家伙负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
看完上面那一段话,我估计很多萌萌小白们,已经开始犯晕了,没事,犯晕是对学习RecyclerView最起码的尊重,挺好的...... (●´ϖ`●) 

==【第一部分】======================================================

好,接下来咱们需要着手实现咱们的列子了。但在咱们实现上面那个相对比较复杂的列子之前,咱们先通过用最少,最简单的代码,先把最简单的RecyclerView运行起来。然后咱们一步一步的,增添代码,实现咱们最终的列子。好吧?
首先咱们要实现的Demo是这样子的:


交代一下我的开发环境:我写这篇教程的时候,我用的Android Studio版本是3.1,本地的JDK是1.8。

这部分的任务清单(To-Do List)
----------------------------------------------------------------------
  1. 新建Project
  2. 添加RecyclerView库依赖
  3. 修改下App主题颜色
  4. 在activity_main.xml中添加RecyclerView组件
  5. 创建RecyclerView条目的布局文件
  6. 实现RecyclerView需要的ViewHolder和Adapter两个辅助类
  7. 在MainActivity.java中启动RecyclerView
----------------------------------------------------------------------

那咱们就按照上面的任务清单走,清晰明朗。

(1)首先建个新Project,咱们App的名称就叫:Playlist。剩下的,按照你平常的设置,一带一路默认就好,进入Project。

(2)添加RecyclerView库依赖:咱们今天要学的RecyclerView这个组件,存在于Android的一个支持库中:'com.android.support:recyclerview-v7:27.1.0',需要添加这个库的依赖。不过现在添加这个库很简单,在activity_main.xml页面视图中,直接点击图中的下载按钮就好。或者直接在build.gradle(Mocule:app)中,添加下面的库依赖是一样的。

implementation 'com.android.support:recyclerview-v7:27.1.0'



(3)修改下App主题颜色:接下来咱们先修改一下rec/values/colors.xml中的颜色值,并添加一个黑色(后面的字体颜色用),对应的代码如下:

rec/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#d32f2f</color>
    <color name="colorPrimaryDark">#d32f2f</color>
    <color name="colorAccent">#d32f2f</color>
    <color name="black">#000000</color>
</resources>

这里我把App标题栏和Android通知栏的颜色都设置成了红色,添加了一个纯黑色用于后面的字体显示,不多解释。

(4)在activity_main.xml中添加RecyclerView组件:接下来把activity_main.xml中的“Hello World”删掉,并添加一个RecyclerView组件。相关设置如图,对于代码如下。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

(5)创建RecyclerView条目的布局文件:在创建条目布局文件之前,咱们先需要把下面这张16:9的妹子图片素材 放到res/drawbale目录中。有情操作提示:在电脑上拷贝你的图片,然后鼠标点击Project中的res/drawable目录,Ctrl+V粘贴就好。


然后在res/layout目录下新建一个Layout resource file,起名为list_item.xml,其他都默认就好。这个list_item.xml就是咱们的RecyclerView中的每个条目的布局文件。上面两步操作完后,显示如下图:

咱们的这个条目布局文件很简单:父容器用ConstraintLayout ,里面就放一张照片。设置属性如下图,代码在下面。
嘱咐一句:父容器ConstraintLayout的layout_height,要设置成wrap_content。
至于为什么用ConstraintLayout ,小伙伴就自己搜搜它的优点以及高级用法吧,这货也是Android官方主推的组件。其他的,我相信你照着我的图,对比下我的代码都能搞定。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="160dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/image01"
        tools:ignore="ContentDescription" />

</android.support.constraint.ConstraintLayout>

好,基本的布局啥的,就这么多,接下来要进入重头戏了。

(6)实现RecyclerView需要的ViewHolder和Adapter两个辅助类。

下面的要注意啦啊!这里我需要发布下小高能预警!"(ºДº*)  有皇家晕码血统的小可爱们请先撤离下现场......(ノ°ο°)ノ 因为接下来咱们要实现RecyclerView的核心了。这里我有必要再贴出来RecyclerView的实现原理,铺垫一下:
  1. RecyclerView中的各个条目,加载的是同一个xml布局文件,只不过是往里面塞了不同的数据而已。
  2. RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
  3. RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
  4. 有一个叫LayoutManager的家伙负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
好,按照上面的步骤,第一项的那个xml布局文件已经完成了,接下来需要实现传说中的ViewHolder和Adapter了。
接下来憋说话,先看我操作,伴随头晕目眩属正常生理现象。///•A•///

在java/com.nidebao.playlist包下,新建一个名为ListAdapter.java类,这个就是咱们的Adapter,让这个类继承RecyclerView.Adapter,并且需要在RecyleView.Adpter后面加上泛型<ListAdapter.ViewHolder>。我知道这个时候Android Studio已经出现了姨妈红,先憋管,先别按Alt+Enter修复。
紧接着,在class ListAdapter类里面创建内部类ViewHolder,让这个ViewHolder继承RecyclerView.ViewHolder。上面这两波操作完后应该显示如下:


这两行红代码我偷偷解释下:(°∀°)ノ
咱们的ViewHolder继承RecyclerView.ViewHolder,在RecyclerView的实现原理中也提到了,这是必须的,ViewHolder把控着RecyclerView里面控件。这里咱们把ViewHolder写成了ListAdapter的内部类。咱们的ListAdapter也必须继承RecyclerView.Adapter,它的功能就是为RecyclerView适配数据。并且需要在RecyclerView.Adapter的后面加上ViewHolder泛型。为什么这样写?Andriod组件设计人员就是这样的设计的,你点开一下那个RecyclerView.Adapter源码,你看它的类的定义就是下面这样子。

public abstract static class Adapter<VH extends ViewHolder> {
    ......

抛开这两行代码的内容,如果连这两句代码的逻辑都看不懂的小伙贼,你课下要偷偷补补Java基础了啊。
好,接下来咱们处理下姨妈红。先在内部类ViewHolder上按Alt+Enter键,选择"Create constructor matching super",建立匹配父类构造器。
然后在ListAdapter类上按Alt+Enter建,选择"Implement methods",实现那三个抽象方法。
操作完后,对比下图:

ListAdapter.java
package com.steve.playlist;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

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

    class ViewHolder extends RecyclerView.ViewHolder{
        public ViewHolder(View itemView) {
            super(itemView);
        }
    }

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

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

    }

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

估计这一波小操作,晕倒了不少骚年...... 可以的。ಥ_ಥ
咱的Adapter和ViewHolder两个类已经定义好了,下面修改onCreateViewHolder(......)方法,在里面增添3行代码如下:

@NonNull @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View ItemView = inflater.inflate(R.layout.list_item, parent, false);
    return new ViewHolder(ItemView);
}

听我解释:ღ⊙□⊙╱
首先这个onCreateViewHolder(...)方法就是用来创建ViewHolder的。咱们的代码,分三步实现这一目的:
  1. 通过LayoutInflater.from(......)方法拿到inflater实例,如果翻译的话,我想把它翻译为”控件吹鼓器“比较合适,inflate的意思就是吹鼓,吹气球的意思。它的作用就是解析指定的xml,并把它像气球一样”吹起来“,用于显示在屏幕上。
  2. 接下来通过inflater.inflat(......)方法,把咱们之前定义的list_item.xml条目布局文件”吹鼓“为View实例。
  3. 把上面拿到的list_item View实例交给ViewHolder的构造方法,创建ViewHolder对象并返回。
好,然后把下面的getItemCount()方法中的return 0,改为return 20。这个从方法名字上也能看出,这个货肯定是决定RecyclerView中条目数量的,没错。


到此咱的ViewHolder和Adapter就暂时写好了。

(7)最后一步,在MainActivity.java中启动RecyclerView:切换到MainActivity.java,在其onCreate方法中,增加下面5行代码。

MainActivity.java
public class MainActivity extends AppCompatActivity {

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

        ListAdapter adapter = new ListAdapter();
        LayoutManager layoutManager = new LinearLayoutManager(this);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(layoutManager);
    }
}

听我解释:ღ⊙□⊙╱
当MainActivity启动时,首先咱们创建了一个咱们自己的ListAdapter实例,等待传给RecyclerView。然后new了一个LayoutManager实例,上面也提到了,这个LayoutManager的作用就是决定RecyclerView中条目的显示方式(垂直,水平,卡片式......) 这里的LineraLayoutManager,就是垂直显示条目。

如果你想水平显示条目,可以这样写:
LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
如果你想卡片式显示条目,可以这样写:
LayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
小伙伴可以自己试试并看看效果,这里暂时不做过多扩展。

接下来通过findViewById(......),拿到RecyclerView,之后通过它的set...方法分别为其设置了Adpter和LayoutManager。
对了,有的小伙贼可能用的比较老的Android Studio,用findViewById(...)时,前面可能需要加上转型。

好啦,好啦!咱们的一小小部分就完成了!这就是能运行起来RecyclerView最少的代码!赶紧的,打开你的模拟器,运行一下!如果没有什么问题,你是按照我说的一步一步写过来的,你的安卓模拟器上显示的应该是这个样子:

如果你看到了这样的界面,我觉得这个时候咱有必要小兴奋一下,奖励给自己一根烟...... 不抽烟的可以叼根筷子意思一下,仪式感很重要...... 233333(ಡωಡ)

==【第二部分】======================================================

接下来,咱们修改下条目布局文件list_item.xml,让咱们的RecyclerView更高端大佬一些。咱们即将实现的样式如下:



这部分的任务清单(To-Do List)
-----------------------------------------
  1. 为Project添加几个Icon图标素材
  2. 重新布局list_item.xml
-----------------------------------------

这部分很简单,就是添加了几个Icon,修改list_item.xml,把上面列子的图片替换为了一个更复杂点的布局而已,没动其他任何Java代码,来一起实现这两步。
首先咱们需要为咱们的项目添加几个icon,如图中的小人icon,小电视icon,评论icon,看我操作。


在res/drawable目录上,右击,依次选择New > Vector Asset,然后双击666!.................. 额,你若真双击了,麻烦退回去,单击就好,不好意思,节目效果,必须有的...... 说下你可能知道的快捷操作:点击下drawable目录,按Alt+Insert,然后选择Vector Asset,可以达到同样的疗效。

下面就来到下面这个视图:


先把上面图中的Color选项设置为666666,额,是真666666!然后点击上面的小人图标,进入下面的界面:


这里面所有的Icon都是Google粑粑提供的官方Material Icons,左上角的搜索框可以搜索,选择你喜欢的就好。重复这样的操作,咱们需要3个icon,最后你的icon应该出现在左侧的drawable目录里,如下图:


好啦,准备工作就这么多!下面开始重新编写list_item.xml文件。我这里实在不能一步一步的给你演示ConstraintLayout怎么布局各个小控件了啊,要不然这教程就太臃肿了。如果有不会Android ConstraintLayout(约束性布局),自己费点心,先补补这块儿,很简单的。下面我把设计好后的视图,以及代码留下。视图中,各个控件的关系,以及距离都能看清楚,自己比葫芦画瓢按照我的做就好。


这里说明下视图中我定义的几个控件的Id名称,到后面咱们会用到,省的到时候它们不认识你...... →_→
左侧图片:iv_listitem_picture (ImageView类型的,我简称iv。)
右上标题:tv_listitem_title (TextView类型的,我简称tv。)
小人名字:tv_listitem_name
播放次数:tv_listitem_playcount
评论数目:tv_listitem_comments
其他的那些icon图标,都用不到id,随意就好。

代码清单:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/iv_listitem_picture"
        android:layout_width="160dp"
        android:layout_height="90dp"
        android:layout_marginBottom="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/image01"
        tools:ignore="ContentDescription" />

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_marginBottom="8dp"
        android:layout_marginStart="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/iv_listitem_picture"
        app:srcCompat="@drawable/ic_play" />

    <TextView
        android:id="@+id/tv_listitem_playcount"
        android:layout_width="wrap_content"
        android:layout_height="18dp"
        android:layout_marginBottom="8dp"
        android:layout_marginStart="8dp"
        android:text="8938"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView3" />

    <ImageView
        android:id="@+id/imageView5"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_marginBottom="8dp"
        android:layout_marginStart="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/tv_listitem_playcount"
        app:srcCompat="@drawable/ic_comment" />

    <TextView
        android:id="@+id/tv_listitem_comments"
        android:layout_width="0dp"
        android:layout_height="18dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="1138"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView5" />

    <ImageView
        android:id="@+id/imageView7"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_marginBottom="5dp"
        android:layout_marginStart="8dp"
        app:layout_constraintBottom_toTopOf="@+id/imageView3"
        app:layout_constraintStart_toEndOf="@+id/iv_listitem_picture"
        app:srcCompat="@drawable/ic_person" />

    <TextView
        android:id="@+id/tv_listitem_name"
        android:layout_width="0dp"
        android:layout_height="18dp"
        android:layout_marginBottom="5dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="Super Steve"
        app:layout_constraintBottom_toTopOf="@+id/tv_listitem_playcount"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView7" />

    <TextView
        android:id="@+id/tv_listitem_title"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:maxLines="2"
        android:text="Steve is the most handsome man in the world!"
        android:textColor="@color/black"
        android:textSize="16sp"
        app:layout_constraintBottom_toTopOf="@+id/tv_listitem_name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/iv_listitem_picture"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

好啦,这一块儿就这么多,大家应该能搞定吧!搞定后,赶紧再运行一下,模拟器上的显示视图应该是这个样子:

好,如果你实现到了这一步,赶紧给自己点小奖励,再来跟事后烟?(ಡωಡ) ...... 完事后咱们进入下面更高级的部分!晕码的兄得坐稳。


==【第三部分】======================================================

接下来咱们实现用Java代码的方式,让RecyclerView动态加载数据。

这部分的任务清单(To-Do List)
-----------------------------------------------------------------------------
  1. 为Project添加两张图片素材。
  2. 修改ViewHolder内部类,让其持有RecyclerView条目中的各个控件。
  3. 修改ListAdapter类,让其给RecyclerView条目中的控件适配数据。
-----------------------------------------------------------------------------

(1)为Project添加两张图片素材 :向项目中的res/drawable添加另外两张16::9的图片,我的用的是下面这两张。



添加后如图,咱们现在有3张图片了哈。

好,下面咱们需要开始写Java代码了!通过代码的方式,动态的向RecyclerView中的条目中添加数据,首先咱们面临的问题是,应该怎么获取到条目中的各个控件(TextView, ImageView, Button等)?再者,在哪里获取?难道还是像以前一样,在Activity中,通过findViewById(......)的方式获取?...... 小伙贼,你的想法很好,但不是这样的...... (<_<)
如果你记性好,咱们上面提到了,RecyclerView中的条目,是通过ViewHolder控制的,由他来提供访问权限。
好,小乖乖们下面看我在哪里写,以及怎么写。

(2)修改ViewHolder内部类,让其持有RecyclerView条目中的各个控件: 回到咱们的ListAdapter.java文件,在咱们的内部类ViewHolder中,添加如下代码。

ViewHolder.class
class ViewHolder extends RecyclerView.ViewHolder {
    ImageView itemPicture;
    TextView itemTitle;
    TextView itemName;
    TextView itemPlayCount;
    TextView itemCommentsCount;
    public ViewHolder(View itemView) {
        super(itemView);
        itemPicture = itemView.findViewById(R.id.iv_listitem_picture);
        itemTitle = itemView.findViewById(R.id.tv_listitem_title);
        itemName = itemView.findViewById(R.id.tv_listitem_name);
        itemPlayCount = itemView.findViewById(R.id.tv_listitem_playcount);
        itemCommentsCount = itemView.findViewById(R.id.tv_listitem_comments);
    }
}

听我解释:ღ⊙□⊙╱
这些代码应该不难理解,就是在ViewHolder类中,添加了咱们需要访问的ImageView,TextView控件的成员变量。然后紧接着在ViewHolder的构造器中,通过findViewById(......)的方式,初始化,或者说获取到那些控件的对象。只不过这里的findViewById(......)和Activity中的不一样,前面需要有view.findViewById(......),其实Activity中的那个findViewById(......)只是这个的语法糖而已,性质上是一样的。
既然拿到了一个个控件的对象,这些对象是ViewHolder的成员,咱们接来下就可以通过viewHolder.xxx的语法操控这些控件了,岂不美滋滋。

(3)修改ListAdapter类,让其给RecyclerView条目中的控件适配数据: 编写ListAdapter中的onBindViewHolder(.......)方法,这个方法从名字上就能看出,它是绑定数据用的。你看这个方法需要的参数:(ViewHolder holder, int positon),里面的holder肯定是上面的public ViewHolder onCreateViewHolder(......)方法返回的holder,position肯定是RecyclerView条目的位置。好,添加如下代码:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    holder.itemPicture.setImageResource(R.drawable.image02);
    holder.itemTitle.setText("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
    holder.itemName.setText("xxxxxxxxx");
    holder.itemPlayCount.setText("666666");
    holder.itemCommentsCount.setText("666666");
}

还用听我解释吗?代码中的xxxxxx数据,我只是为了测试,用脚丫子写的...... (ಠ .̫.̫ ಠ)
holder.itemPicture.setImageResource(......)就是给那个ImageView设置图片。
holder.itemTitle.setText(......)就是设置文字。
...................

好,不罗嗦,现在赶紧准备好下一根事后烟,从新编译运行下,看看是不是像我下面的样子......

=================================================================

好,在这个时间点,咱们再来回顾下RecyclerView的实现原理,效果最佳。你再反复阅读下你刚才写的代码,再看看下面这4条,参照下图片,是不不是有点儿酸爽的感觉?
  1. RecyclerView在屏幕上显示的各个条目,加载的是同一个xml布局文件,只不过是往里面塞了不同的数据而已。
  2. RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
  3. RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
  4. LayoutManager负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。


==【第四部分】======================================================

到这里,你已经学会了如何动态的向RecyclerView中添加数据了!...... 
但是,别骄傲,这还不够!因为你看上面的数据,都是一样的啊!现实世界中,哪有的一样的数据?甚至连两片相同的叶子都没有好吧!(ノ`Д)ノ

接下来咱们就实现不同的数据加载!
现实中的App(爱啪啪),一般都通过网络,或者手机上的数据库加载数据的。这里咱们先不搞这些,这一块不小,我日后再写相关的教程。这里咱们通过Java模拟一个小数据模型,专门来提供随机的不同的数据用于RecyclerView加载。
下面揍是咱们要实现的效果,如果图片再多一些,就能以假乱真了,这里来给你说下实现方法,课下你再自己发挥。



首先咱们先为分析下RecyclerView中的条目,那些数据需要动态改变?然后根据需要的数据,建立对应的Java Data模型专门向RecyclerView提供数据就好了。这里我上一张图:


从图上可以看出,需要改变的数据有5个:pictureId,title,name,playCount,commentsCount。好,知道了这些,写Java模型不就简单了。好,接下来掌声有请咱们的任务清单大佬粗场!✿✿ヽ(゚▽゚)ノ✿

大家好,窝是这部分的任务清单,初次见面请多多关照!←_←
--------------------------------------------------------------------------------
  1. 根据条目需要的数据,创建一个对应的Java Data数据模型。
  2. 创建一个DataGenerator类,专门用于随机生成数据。
  3. 修改ListAdapter.java类,使其可以给RecyclerView适配随机数据。
  4. 修改MainActivity.java,在这里把生成的随机数据传给Adapter。
--------------------------------------------------------------------------------

(1)根据条目需要的数据,创建一个对应的Java Data数据模型:下面在目录java/com.nidebao.playlist包下,创建一个Java文件,取名为ListData.java。然后给它添加上面的5个成员变量,并用Alt+Insert快捷键生成对应的构造器和Getter...... 最后实现的类是下面这样子。



这就是咱们这个数据模型需要的全部的代码,为了截图美观,我把代码缩排了。

(2)创建一个DataGenerator类,专门用于随机生成数据:模型建好了,咱们接下来再建一个专门提供数据的数据提供器,直接在ListData类最下面,新建一个类就好,起名叫:class DataGenerator...... 或者你建一个新类文件DataGenerator.java也好,这里随你,都一样。这两个类我写到一个文件里了,代码如下:

package com.steve.playlist;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ListData {

    private int pictureId;
    private String title;
    private String name;
    private int playCount;
    private int commentsCount;

    ListData(int pictureId, String title, String name, int playCount, int commentsCount) {
        this.pictureId = pictureId;
        this.title = title;
        this.name = name;
        this.playCount = playCount;
        this.commentsCount = commentsCount;
    }

    public int getPictureId() { return pictureId; }
    public String getTitle() { return title; }
    public String getName() { return name; }
    public int getPlayCount() { return playCount; }
    public int getCommentsCount() { return commentsCount; }
}


/** Here is the Data Generator class for generating ramdom data. */

class DataGenerator {

    private static int[] pictureIds = {
        R.drawable.image01,
        R.drawable.image02,
        R.drawable.image03
    };

    private static String[] titles = {
        "Steve is the most handsome man in the world?",
        "Free Tesla Roadster? Ask MKBHD V24!",
        "I Spent 6 Months Making A Game And Here It Is!",
        "Easy Trick to Auto Sizing UITextView in Real Time",
        "How I Make Musical.lys + Tips & Tricks!",
        "iPhone X AFTER 30 DAYS REVIEW || Should've Been An Android",
        "Parsing JSON Just Became Super Easy in Swift 4 with Decodable",
        "A day in the life of a software engineer at ASOS"
    };

    private static String[] names = {
        "Super Steve",
        "Marques Brownlee",
        "TheHappieCat",
        "Lets Build That App",
        "Lavendaire",
        "Ryleigh Marie",
        "Jared Busch",
        "Laila Nassali"
    };

    private static final Random rand = new Random();

    public static List<ListData> generate(int num) {
        List<ListData> data = new ArrayList<>();
        int a = pictureIds.length;
        int b = titles.length;
        int c = names.length;
        for (int i = 0; i < num; i++) {
            int id = pictureIds[rand.nextInt(a)];
            String title = titles[rand.nextInt(b)];
            String up = names[rand.nextInt(c)];
            int play = rand.nextInt(10000);
            int comments = rand.nextInt(2000);
            data.add(new ListData(id, title, up, play, comments));
        }
        return data;
    }
}

稍微有一点点长,如果你有点儿Java基础,一步一步读,还是很简单的,没有涉及很复杂的东西。这段代码中,难理解的就下面这一下段,我挑出来给小白白们讲解下,看懂的大佬直接略过就好。

这是我定义的一个随机生成数据的方法,返回一个List,确切的类型是ArrayList,这里涉及到多态。方法参数(int num)是指你想创建ListData的个数。紧接着调用方法时,new 了一个ArrayList,下面的int a, b, c,我直接拿到的对应的数组的大小。要不然在下面的for循环中,像这样传入rand.nextInt(xxx.length),反复生成随机数据时,需要反复计算个数组的大小,影响一丢丢性能。最后把随机生成的数据传给ListData构造器,生成一个ListData实例并添加到data List中。
我就能讲接么多了,小乖乖们如果不懂,真的需要好好补补Java基础了呢,推荐《Java编程思想》。

咱们已经写好了ListData,和DataGenerator类,这两个类先等着,马上就可以用到了。

(3)修改ListAdapter.java类,使其可以给RecyclerView适配随机数据:接着回到ListAdapter.java文件中,添加并修改下图中的代码,我很贴心的给你们圈了粗来。


这里让ListAdapter持有了一个List<ListData> listData成员,以便于其访问咱们生成的data List,并且给它创建了一个构造函数。在下面修改的代码中,其中onBindViewHolder(......)方法,增添了一条ListData item = listData.get(position); ...... 这句代码的作用就是在指定的RecyclerView的位置上,获取对应的ListData。接下来,像setImageResource(......),setText(......)的变化,你应该很轻易的就能明白,不解释了。最后修改的getItemCount(),里面直接返回listData.size(),就是你创建了多少条数据,RecyclerView上就显示多少条条目,对应的。
这里把完整的代码贴出来,送给小懒货们复制。 _(┐「ε:)_

ListAdapter.java
package com.steve.playlist;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

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

    class ViewHolder extends RecyclerView.ViewHolder {
        ImageView itemPicture;
        TextView itemTitle;
        TextView itemName;
        TextView itemPlayCount;
        TextView itemCommentsCount;
        ViewHolder(View itemView) {
            super(itemView);
            itemPicture = itemView.findViewById(R.id.iv_listitem_picture);
            itemTitle = itemView.findViewById(R.id.tv_listitem_title);
            itemName = itemView.findViewById(R.id.tv_listitem_name);
            itemPlayCount = itemView.findViewById(R.id.tv_listitem_playcount);
            itemCommentsCount = itemView.findViewById(R.id.tv_listitem_comments);
        }

    }

    private List<ListData> listData;

    ListAdapter(List<ListData> listData) {
        this.listData = listData;
    }

    @NonNull @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View ItemView = inflater.inflate(R.layout.list_item, parent, false);
        return new ViewHolder(ItemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ListData item = listData.get(position);
        holder.itemPicture.setImageResource(item.getPictureId());
        holder.itemTitle.setText(item.getTitle());
        holder.itemName.setText(item.getName());
        holder.itemPlayCount.setText(String.valueOf(item.getPlayCount()));
        holder.itemCommentsCount.setText(String.valueOf(item.getCommentsCount()));
    }

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

(4)修改MainActivity.java,在这里把生成的随机数据传给Adapter:这是最后一步,很简单,就需要修改MainActivity.java中的两条代码,如下。


其中的第一条是增加的,它的作用就是生成50条ListData,下面一句把上面生成的listData传入到ListAdapter构造器中,别忘了,咱们上面刚给ListAdapter添加的构造器哇。最后的MainActivity.java代码:

MainActivity.java
package com.steve.playlist;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;

import java.util.List;

public class MainActivity extends AppCompatActivity {

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

        List<ListData> listData = DataGenerator.generate(50);
        ListAdapter adapter = new ListAdapter(listData);
        LayoutManager layoutManager = new LinearLayoutManager(this);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(layoutManager);
    }
}

这就是所有的代码!赶紧运行一下!看看是不是你预期的我下面那样!


如果你真的走到了这里,我觉得我如果再很虚伪,很肤浅的夸你:“你真棒哦!你好腻害!” ............................................. 也是可以的。都这么牛了,你还想怎样? (๑´ڡ`๑) 

最后的最后,我再发出来咱们祖传的原理图(有改动),你再回味最后一次...... 现在你再看这张贯穿全局的图,应该有喝凉水尿床一样冰爽的感觉了吧?这就是RecyclerView的设计原理以及实现原理哇!


好啦,这教程实在不短了!到这里,咱们今天的列子就学完了啊! ✿✿ヽ(゚▽゚)ノ✿
这篇教程,重心讲的是RecyclerView的实现原理,这也是RecyclerView的核心。其他的,关于RecyclerView的Item点击事件处理,条目的增加删除,动画的实现,还有性能上的优化等等,我这里都没涉及。抽时间我再开个帖子,把没涉及到的这几块补上吧。( ͡° ͜ʖ ͡°)╯♂

=================================================================

One more thing,这是窝的第一篇教程,肯定有不足的地方。也不知道大家对这样的教程有什么感觉,你觉得不好的地方,劳烦给我提出来啊,我会改进。
我刚建了一个Android开发QQ群,有兴趣的小伙伴可以加进来。
QQ群号码是:178609314,下面有QQ群的二维码。
呕吐尿血原创教程,如果你有转载需求,请至少给打个招呼啊!
如果我写的教程帮到了你,条件宽松的小伙伴,可以给打赏点儿支持下,给我些鼓励继续写下去。  (๑´ㅂ`๑́)و✧




愿骚年们都能被这个世界温柔以待...... 笔芯。  (•̀ᴗ•́)و ̑̑ 








  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值