学安卓的小伙伴们,今天咱们通过一个列子,来学学Android RecyclerView的用法。这个教程面向的读者定位是刚接触Android开发不久的新人,所以写的比较详细。Android开发大佬看这个教程,可能会有点啰嗦,尽量憋喷。
下图就是我们今天要实现的列子。_(•̀ω•́ 」∠)_
先来点吐槽:ღ⊙□⊙╱
之前窝在学这块的时候,真是费了不少精。当初搜教程的时候,我发现很多写教程的大佬基本有一个共同的尿性,那就是他们往往会说:“废话少说,直接上代码!”......
每当看到这句话,当时作为Android开发小透明的我心里总是很慌的,面对着一坨坨我撸不透搞不懂的代码,心里有一万句mmp油燃而生...... (;´༎ຶД༎ຶ`)
烟鬼正传...... 在开始之前,我得先硬塞给你一张图,先给你简单科普下RecyclerView的实现原理,让你心里有点数。我知道你可能还没接触过RecyclerView这东西,读下面这一段可能会犯晕。没事,先尽力尝试理解下就好,也不用刻意记忆。下面这段话和下面的图,我会贯穿本教程始终,慢慢的你就从了....... ←_←
下面这里敲黑板了啊!要划重点了啊!ღ⊙□⊙╱
=================================================================
- RecyclerView在屏幕上显示的各个条目,加载的是同一个xml布局文件,只不过是往里面塞了不同的数据而已。(这个不难理解吧,你结合下面的图看看,每个条目都是一样的布局,只是数据不一样。)
- RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
- RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
- 有一个叫LayoutManager的家伙负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
看完上面那一段话,我估计很多萌萌小白们,已经开始犯晕了,没事,犯晕是对学习RecyclerView最起码的尊重,挺好的...... (●´ϖ`●)
==【第一部分】======================================================
好,接下来咱们需要着手实现咱们的列子了。但在咱们实现上面那个相对比较复杂的列子之前,咱们先通过用最少,最简单的代码,先把最简单的RecyclerView运行起来。然后咱们一步一步的,增添代码,实现咱们最终的列子。好吧?
首先咱们要实现的Demo是这样子的:
交代一下我的开发环境:我写这篇教程的时候,我用的Android Studio版本是3.1,本地的JDK是1.8。
这部分的任务清单(To-Do List)
----------------------------------------------------------------------
- 新建Project
- 添加RecyclerView库依赖
- 修改下App主题颜色
- 在activity_main.xml中添加RecyclerView组件
- 创建RecyclerView条目的布局文件
- 实现RecyclerView需要的ViewHolder和Adapter两个辅助类
- 在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的实现原理,铺垫一下:
- RecyclerView中的各个条目,加载的是同一个xml布局文件,只不过是往里面塞了不同的数据而已。
- RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
- RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
- 有一个叫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的。咱们的代码,分三步实现这一目的:
- 通过LayoutInflater.from(......)方法拿到inflater实例,如果翻译的话,我想把它翻译为”控件吹鼓器“比较合适,inflate的意思就是吹鼓,吹气球的意思。它的作用就是解析指定的xml,并把它像气球一样”吹起来“,用于显示在屏幕上。
- 接下来通过inflater.inflat(......)方法,把咱们之前定义的list_item.xml条目布局文件”吹鼓“为View实例。
- 把上面拿到的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)
-----------------------------------------
- 为Project添加几个Icon图标素材
- 重新布局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)
-----------------------------------------------------------------------------
- 为Project添加两张图片素材。
- 修改ViewHolder内部类,让其持有RecyclerView条目中的各个控件。
- 修改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条,参照下图片,是不不是有点儿酸爽的感觉?
- RecyclerView在屏幕上显示的各个条目,加载的是同一个xml布局文件,只不过是往里面塞了不同的数据而已。
- RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
- RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
- LayoutManager负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
==【第四部分】======================================================
到这里,你已经学会了如何动态的向RecyclerView中添加数据了!......
但是,别骄傲,这还不够!因为你看上面的数据,都是一样的啊!现实世界中,哪有的一样的数据?甚至连两片相同的叶子都没有好吧!(ノ`Д)ノ
接下来咱们就实现不同的数据加载!
现实中的App(爱啪啪),一般都通过网络,或者手机上的数据库加载数据的。这里咱们先不搞这些,这一块不小,我日后再写相关的教程。这里咱们通过Java模拟一个小数据模型,专门来提供随机的不同的数据用于RecyclerView加载。
下面揍是咱们要实现的效果,如果图片再多一些,就能以假乱真了,这里来给你说下实现方法,课下你再自己发挥。
首先咱们先为分析下RecyclerView中的条目,那些数据需要动态改变?然后根据需要的数据,建立对应的Java Data模型专门向RecyclerView提供数据就好了。这里我上一张图:
从图上可以看出,需要改变的数据有5个:pictureId,title,name,playCount,commentsCount。好,知道了这些,写Java模型不就简单了。好,接下来掌声有请咱们的任务清单大佬粗场!✿✿ヽ(゚▽゚)ノ✿
大家好,窝是这部分的任务清单,初次见面请多多关照!←_←
--------------------------------------------------------------------------------
- 根据条目需要的数据,创建一个对应的Java Data数据模型。
- 创建一个DataGenerator类,专门用于随机生成数据。
- 修改ListAdapter.java类,使其可以给RecyclerView适配随机数据。
- 修改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群的二维码。
呕吐尿血原创教程,如果你有转载需求,请至少给打个招呼啊!
如果我写的教程帮到了你,条件宽松的小伙伴,可以给打赏点儿支持下,给我些鼓励继续写下去。 (๑´ㅂ`๑́)و✧
愿骚年们都能被这个世界温柔以待...... 笔芯。 (•̀ᴗ•́)و ̑̑