Android RecyclerView:什么是,通过简单示例学习

⚡ 智能摘要

RecyclerView 是一款高级的 Android 该组件通过循环利用有限数量的视图,高效地显示大型可滚动数据集。它依赖于适配器、布局管理器和视图持有者来绑定运行时会发生变化的数据集合。

  • 📱 它是什么: RecyclerView 是 ListView 和 GridView 的灵活替代品,用于显示大型数据集合。
  • 🧱 视图和视图组: Android 使用 View 和 ViewGroup 类绘制界面,通过 LayoutParams 控制大小和位置。
  • 🔄 适配器: 适配器模式将数据数组转换为 RecyclerView 可以接受的 ViewHolder。
  • 📐 布局管理器: 布局管理器按线性或网格方式排列项目,并发出视图可回收利用的信号。
  • ♻️ 回收: 屏幕外视图会被重复使用而不是重新创建,这样可以节省内存并保持滚动流畅。
  • 🛠️ 构建步骤: 添加 Gradle 依赖项,创建项目布局、ViewHolder、适配器和LayoutManager。

Android 回收站视图

RecyclerView 是什么 Android?

回收站视图 RecyclerView 是 GridView 和 ListView 的更灵活、更高级的版本。它是一个用于显示大型数据集的容器,通过控制视图数量,可以高效地滚动浏览。当您的数据集合中的元素会根据网络事件或用户操作在运行时发生变化时,可以使用 RecyclerView 组件。

观看数

此 Android 平台使用 View 和 ViewGroup 类在屏幕上绘制项目。这些类是绝对的。tract 和 t 被扩展到不同的实现方式以适应不同的使用场景。例如,TextView 的功能很简单,就是在屏幕上显示文本内容。EditText 继承自同一个 View 类,并添加了更多功能,使用户能够输入数据。

我们可以创建自己的自定义视图,以便在开发时获得更大的灵活性。ping 用户界面。View 类提供了一些可以重写的方法,用于在屏幕上绘制图形,并允许我们传入宽度、高度等参数,以及我们想要添加到 View 中的自定义属性,以使其按预期运行。

视图组

ViewGroup 类是一种 View,但与仅负责显示视图的普通 View 类不同,ViewGroup 允许我们将多个视图组合成一个视图,并作为一个整体来引用它。在这种情况下,我们向其添加其他简单视图的顶层视图称为“父视图”,而添加到其中的视图则称为“子视图”。

我们可以将视图想象成一个数组,将视图组想象成一个数组的数组。鉴于数组的数组本身也是一个数组,我们就能理解为什么视图组可以像视图一样被对待。

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our analogy, we can now group views
//together and the structure that would hold that would be what we call the ViewGroup

ViewGroup 还允许我们定义视图内子元素的排列方式,例如,它们是垂直排列还是水平排列。我们可以为视图内的交互设置不同的规则。例如,相邻的 TextView 之间的距离应为 12dp,而 ImageView 后跟 TextView 之间的距离应为 5dp。

如果我们正在开发,情况就是如此。ping 我们从零开始创建了自己的 ViewGroup。为了简化这些配置, Android 提供了一个名为LayoutParams的类,我们可以用它来输入这些配置。

Android 文件 它提供了一些默认参数,我们在配置自己的 ViewGroup 时会用到这些参数。一些常用参数涉及宽度、高度和边距。默认情况下,这些配置的结构为:高度使用 `android:layout_height`,宽度使用 `android:layout_width`。因此,在创建 ViewGroup 时,您可以进一步创建 LayoutParams,以指定 ViewGroup 的具体行为方式。

Android 它自带默认的视图和视图组,可以用来完成许多常见的任务。例如,我们之前提到的 TextView 就是一个简单的视图,它提供了高度、宽度和文本大小等可配置属性。此外,我们还有用于显示图像的 ImageView 和 EditText,以及许多其他视图。 Android 还有自定义的 ViewGroups,我们可以向其中添加我们的视图并获得预期的行为。

线性布局

LinearLayout 允许我们向其中添加 View 项。LinearLayout 具有一个 orientation 属性,用于决定其在屏幕上的布局方式。它还具有 LinearLayout.LayoutParams 属性,用于定义内部 View 的布局规则。例如,属性 android:center_horizo​​ntal 会使 View 沿水平轴居中,而 android:center_vertical 会使 View 的内容沿垂直轴居中。

这里有一些图片可以帮助理解居中。我们假设这是一个位于 200px x 200px 空间内的简单 TextView;居中属性会使其行为如下所示。

机器人:中心水平

水平居中内容

水平居中的内容

机器人:中心垂直

垂直居中内容

垂直居中的内容

安卓:中心

居中内容

居中内容

RecyclerView 的核心组件

RecyclerView 的核心组件

RecyclerView 的核心组件

以下是RecyclerView的重要组件:

RecyclerView.Adapter

基础工作#1 – 适配器模式

适配器是一种将一个系统或设备的属性转换为另一个原本不兼容的系统或设备的属性的装置。有些适配器会改变信号或电源属性,而另一些适配器则只是简单地将一个连接器的物理形状适配到另一个连接器上。

一个简单的生活实例就能解释适配器的用途:当我们想要连接设备,但它们的接口却不匹配时,就需要用到适配器。这种情况可能发生在你去使用不同插座的国家旅行时。如果你携带手机或笔记本电脑充电器,就无法直接连接到当地的电源插座上。不过,你不会就此放弃,而是会购买一个适配器,把它连接在电源插座和充电器之间,这样就能正常充电了。

在编程中,当我们想要将两个数据结构连接起来完成一项任务时,就会出现这种情况,但它们的默认端口之间没有通信方式。

我们将以设备和充电器为例。我们将使用两种充电器,一种是美式充电器,一种是英式充电器。

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var chargingPower = 5
}

然后我们将制造两个设备。

class AmericanDevice()
class BritishDevice()

作为示例,我们可以创建一些设备实例来进行播放。

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

接下来,我们将通过在设备中添加一个名为 `charge()` 的方法来引入两个设备的充电概念。该方法接收各自对应的充电器参数,并根据该充电器进行充电。

sealed trait Device
class AmericanDevice : Device{
    fun charge(charger:AmericanCharger){
        //Do some American charging
    }
}
class BritishDevice: Device{
    fun charge(charger:BritishCharger){
        //Do some British charging
    }
}

在这种情况下,根据我们的类比,由于某种原因,我们在使用美国设备时需要使用英国充电器,反之亦然。

在编程世界中,这通常是将提供相同功能的库混合在一起时发生的(在我们的上下文中,我们的共享功能是充电)。我们需要找到一种方法来实现这一点。

如果沿用这个比喻,我们就需要去电子商店购买一个适配器,以便用英国的充电器给美国设备充电。从编程的角度来看,我们就是这个适配器的制造商。

我们将创建一个适配器,使其完全符合创建另一个适配器所需的模式。我们将把它实现为一个类,如下所示。它不一定非得是一个类,也可以是一个函数,用来说明适配器模式的一般作用。我们使用类是因为它符合大多数用法。 Android.

class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){
    fun returnNewCharger(): BritishCharger{
            //convert the American charger to a BritishCharger
            //we would change the American charging functionality
            //to British charging functionality to make sure the
            //adapter doesn't destroy the device. The adapter could
            //, for example, control the power output by dividing by 2
            //our adapter could encompass this functionality in here

           var chargingPower:Int = theAmericanCharger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2

        return newBritishCharger
    }
}

在编程领域,插座的差异类似于充电方式的差异。如果充电器采用不同的充电方式,那么它们就无法正常工作。

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

尝试使用 americanChargerIFound 调用 myBritishDevice 的 charge() 方法是行不通的,因为 AmericanDevice 只接受 AmericanCharger。所以这样做是不可能的:

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

在这种情况下,我们创建的适配器 AmericanToBritishChargerAdapter 就派上用场了。我们可以使用 returnNewCharger() 方法创建一个新的 BritishCharger 对象,用于充电。我们只需要创建一个适配器实例,并将我们已有的 AmericanCharger 对象传递给它,它就会创建一个可供使用的 BritishCharger 对象。

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(americanChargerIFound)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

布局管理器

处理 ViewGroup 时,我们会在其中放置 View。LayoutManager 的任务是描述 View 在 ViewGroup 内的布局方式。

相比之下,在使用 LinearLayout ViewGroup 时,我们希望能够垂直或水平放置项目。这可以通过添加一个 orientation 属性轻松实现,该属性告诉我们 LinearLayout 在屏幕上的放置方式。我们可以使用以下方法来实现: android:orientation=VERTICAL|HORIZONTAL 属性。

我们还有一个名为 GridLayout 的 ViewGroup。它的用途是将视图排列成矩形网格结构。这样做是为了方便应用用户查看数据。GridLayout 的设计允许通过定义网格尺寸来实现这一目标;例如,我们可以设置 4×4 网格或 3×2 网格。

RecyclerView.ViewHolder

ViewHolder 是一个 abstrac我们还继承了 RecyclerView 类。ViewHolder 为我们提供了常用方法,帮助我们引用已放置在 RecyclerView 中的 View,即使 RecyclerView 的回收机制已经更改了我们未知的各种引用。

大型列表

当我们想向用户展示大量视图,同时又不耗尽内存时,可以使用 RecyclerView。 内存 在我们的设备上为创建的每一个视图实例。

以联系人列表为例,我们首先要大致了解联系人在列表中的显示方式。接下来,我们会创建一个模板布局(实际上是一个视图),其中包含用于填充联系人列表中各种数据的插槽。以下伪代码解释了其全部用途:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

这样我们就能得到一份如下类型的联系人列表:

<ContactList>
</ContactList>

如果我们把内容硬编码到代码中,就无法通过编程方式向列表中添加新内容,除非重写应用程序。幸运的是,向 ViewGroup 添加 View 是受支持的。 addView(view:View) 即便如此,这也不是 RecyclerView 添加子视图的方式。

在我们的用例中,我们会有一个很长的联系人列表。对于列表中的每个联系人,我们需要创建一个 OneContactView,并根据 Contact 类中的字段填充视图中的数据。然后,创建好视图后,我们需要将其添加到 RecyclerView 中以显示列表。

data  class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int)

var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991)
var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992)
var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993)

var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)

我们有一个联系人数组。OneContactView 包含槽位,用于从 Contact 类中获取内容并显示它们。在 RecyclerView 中,我们需要向其中添加视图,以便它能够帮助我们利用其资源回收功能。

RecyclerView 实际上并不允许我们添加视图,而是允许我们添加 ViewHolder。因此,在这种情况下,我们有两个想要连接但并不匹配的部分。这时适配器就派上用场了。RecyclerView 为我们提供了一个适配器,就像我们的 ViewHolder 一样。 AmericanToBritishChargerAdapter() 之前我们利用这个方法,将无法与英国设备一起使用的美国充电器,改造成了类似现实生活中的电源适配器那样可用的东西。

在这种情况下,适配器将接收我们的联系人数组和视图,并从中生成 RecyclerView 愿意接受的 ViewHolder。

RecyclerView 提供了一个接口,我们可以通过 RecyclerView.Adapter 类来扩展该接口并创建适配器。在这个适配器中,我们可以创建 RecyclerView 需要使用的 ViewHolder 类。因此,情况与之前相同,只是多了一个适配器。

我们有一个联系人数组、一个用于显示单个联系人的视图(OneContactView)以及一个 RecyclerView,RecyclerView 是一个视图列表,提供回收服务,但只接受 ViewHolder。在这种情况下,我们现在有一个 RecyclerView.Adapter 类,其中包含一个用于创建 ViewHolder 的方法。

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

RecyclerView.ViewHolder 是一个绝对值tract 类接受我们的 View 作为参数,并将其转换为 ViewHolder。它使用包装器模式来扩展类的功能。

基础工作 #2 – 包装图案

我们将用一个简单的例子来演示如何让动物说话。

sealed trait Animal{
    fun sound():String
}

data class Cat(name:String):Animal{
    fun sound(){
        "Meow"
        }
}
data class Dog(name:String):Animal{
    fun sound(){
        "Woof"
        }
}

var cat1 = Cat("Tubby")
var dog1 = Dog("Scooby")
cat1.sound() //meow
dog1.sound() //woof

在上面的例子中,我们有两个动物。如果我们碰巧想添加一个让它们说话的方法,但库的作者不太友好,我们仍然可以找到办法。我们需要的是一个动物类的包装器。我们可以通过将动物对象作为构造函数传入来实现这一点。

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

现在我们可以将一个动物实例传递给 SpeechPoweredAnimalByWrapper。调用它的 sound() 方法会调用传入的动物的 sound() 方法。我们还有一个额外的 speak() 方法,这算是我们为传入的动物添加的新功能。我们可以按如下方式使用它:

var cat1 =  Cat("Garfield")
cat1.sound()//"meow"
cat1.speak()// doesn't work as it isn't implemented
var talkingCat = new SpeechPoweredAnimalByWrapper(cat1)
talkingCat.sound() //"meow" the sound method calls the one defined for cat1
talkingCat.speak() //"Hello, my name is Garfield"

利用这种模式,我们可以对类进行扩展并添加功能。我们只需要传递一个类实例和由我们的包装器定义的新方法即可。ping 类。

在上面的例子中,我们使用了一个具体的类。同样,也可以用抽象类来实现。tract 类。我们需要将 SpeechPoweredAnimalByWrapper 类更改为 abs。trac好了,我们就完成了。我们会把类名改成更短一些的,让它更易读。

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

虽然和之前一样,但含义却不同了。在普通的类中,我们可以像创建 cat1 和 dog1 那样创建类的实例。绝对值trac然而,t 类并非用于实例化,而是用于扩展其他类。那么,我们该如何使用新的 SpeechPowered(var myAnimal:Animal) abs 呢?tract 类?我们可以通过创建继承它的新类来使用它,从而获得它的功能。

在我们的示例中,我们将创建一个名为 SpeechPoweredAnimal 的类,该类继承自 SpeechPoweredAnimal 类。

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

这与 ViewHolder 中使用的模式相同。RecyclerView.ViewHolder 类是一个绝对值。trac这个类为视图添加了功能,就像我们为动物添加了说话方法一样。正是这些新增功能使其能够在处理 RecyclerView 时正常工作。

以下是如何从 OneContactView 创建 OneContactViewHolder 的方法:

//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView
class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)

RecyclerView 有一个适配器,允许我们将联系人数组连接到 RecyclerView 中的 ContactsView。

添加视图

ViewGroup 不会自动重绘,而是遵循特定的刷新时间表。在您的设备上,它可能每 10 毫秒或 100 毫秒重绘一次;或者,如果我们选择一个不太常见的时间,比如 1 分钟,那么当您向 ViewGroup 添加一个 View 时,您会在 1 分钟后 ViewGroup “刷新”时看到更改。

RecyclerView.Recycler

基础工作#3——藏宝

我们经常刷新页面的一个典型例子就是浏览器。例如,假设我们正在访问的网站是静态的,不会动态更新内容;那么我们就需要不断刷新页面才能看到变化。

在这个例子中,我们假设这个网站是 Twitter。网站上会列出一系列静态推文,我们只能通过点击刷新按钮来重新获取内容,从而查看新的推文。

重新绘制整个屏幕显然会消耗大量资源。试想一下,如果我们的手机流量有限,而我们的推文列表里又有很多图片和视频,那么每次刷新页面都要重新下载所有内容,成本将会非常高昂。

我们需要一种方法来存储已加载的推文,并确保下一次请求能够识别出哪些推文已经存在。这样,它就不会重新下载所有内容,而只获取新的推文,并且还会检查本地保存的推文是否已不存在,以便将其从本地删除。我们所描述的就是缓存。

我们发送给网站的关于我们拥有的内容的信息称为元数据。所以实际上,我们不仅仅是说“我们想加载你的网站”,而是说“我们想加载你的网站,以下是我们上次加载时已经保存的一些内容;请只向我们发送其中没有的内容,这样我们就不会占用太多带宽。”

布局调用 – 推文列表一定很疯狂

布局调用的一个例子是 scrollToPosition。这在聊天应用中很常见。如果聊天线程中的某人回复了之前的聊天气泡,一些聊天应用会在回复内容旁边显示一个指向原聊天气泡的链接,点击该链接即可跳转到原聊天气泡所在的位置。

如果在将 LayoutManager 添加到 RecyclerView 之前以及在添加 RecyclerView.Adapter 之前调用此方法,则 scrollToPosition(n:Int) 将被忽略。

RecyclerView 组件之间的通信

基础工作#4——回调

RecyclerView 在运行过程中涉及许多组件。它需要与 LayoutManager 交互,LayoutManager 决定了视图的排列方式,可以是线性排列,也可以是网格排列。它还需要与适配器交互,适配器负责将我们的数据项(contactList)转换为视图(OneContactView),然后再转换为 RecyclerView 可以使用其提供的方法来操作的 ViewHolder(OneContactViewHolder)。

RecyclerView 的原材料是我们的视图(例如 OneContactView)和数据源。

contactList:Array<Contact>

我们首先使用了一个简单的场景作为起点,以便更好地理解 RecyclerView 的工作原理。假设我们有一个包含 1000 个联系人的静态数组,并希望将其展示给用户,那么这个基本场景很容易理解。但当列表不再是静态的时,RecyclerView 的机制才真正开始发挥作用。对于动态列表,我们需要考虑在向列表中添加或删除项目时,屏幕上的视图会发生什么变化。

布局管理器

除了决定视图的布局方式(线性或网格)之外,LayoutManager 还在底层做了大量工作。ping 回收者知道何时进行回收。

它负责保持ping trac布局管理器负责记录屏幕上当前可见的视图数量,并将此信息传递给回收机制。当用户向下滚动页面时,布局管理器会将顶部失去焦点的视图通知回收系统,以便它们可以被重新利用,而不是继续占用内存或被销毁后再创建新的视图。

这意味着布局管理器需要保持 track 指示用户在滚动列表时的位置。它通过维护一个基于索引的位置列表来实现这一点,即第一个项目从 0 开始,并递增以匹配列表中的项目数。

假设我们一开始只能查看列表中的 10 个项目(例如,列表共有 100 个项目),那么布局管理器会知道它当前聚焦的是视图 0 到视图 9。随着我们滚动,布局管理器能够计算出哪些视图会失去焦点。

布局管理器能够将这些视图释放到回收机制中,以便它们可以被重用(可以将新数据绑定到它们;例如,可以删除视图的联系人数据,并将下一个段中的新联系人数据替换占位符)。

如果列表是静态的,那当然没问题。但 RecyclerView 最常见的应用场景之一是处理动态列表,数据可能来自在线端点,甚至来自传感器。不仅会添加数据,列表中的数据有时也会被删除或更新。

由于数据的动态状态,LayoutManager 的逻辑很难被正确理解。因此,LayoutManager 维护着一个独立的列表,其中包含项目及其位置信息,该列表与回收组件使用的列表是分开的。这样可以确保 LayoutManager 正确地完成布局工作。

同时,RecyclerView 的 LayoutManager 不希望出现数据错误。为了确保正常运行,LayoutManager 会定期(60 毫秒)与 RecyclerView.Adapter 同步,共享列表项的信息,例如新增、更新、删除或位置移动等操作。LayoutManager 接收到这些信息后,会在必要时重新排列屏幕上的内容以匹配这些更改。

RecyclerView 的许多核心操作都围绕着 RecyclerView.LayoutManager 和 RecyclerView.Adapter 之间的通信展开,RecyclerView.Adapter 存储着我们的列表,这些列表有时是静态的,有时是动态的。

此外,RecyclerView 还为我们提供了监听事件的方法,例如 onBindViewHolder,当 RecyclerView.Adapter 将列表中的内容(例如联系人)绑定到 ViewHolder 时,就可以使用该 ViewHolder 在屏幕上显示信息。

另一个是 onCreateViewHolder,它告诉我们 RecyclerView.Adapter 何时接收一个像 OneContactView 这样的常规 View 并将其转换为 RecyclerView 可以操作的 ViewHolder 项。

除了实现视图回收的核心机制之外,RecyclerView 还提供了在不影响回收的情况下自定义行为的方法。视图的重用使得我们难以像在静态视图中那样执行一些常见操作,例如响应 onClick 事件。

我们都知道, RecyclerView.LayoutManager 向用户呈现视图的组件可能会暂时与 RecyclerView.Adapter 该列表存储在数据库中或从数据源流式传输。直接在视图上添加 OnClick 事件可能会导致意外行为,例如删除错误的联系人。

Gradle

如果要使用 RecyclerView,我们需要将其作为依赖项添加到 build.gradle 文件中。在下面的示例中,我们使用了 implementation “androidx.recyclerview:recyclerview:1.1.0”,根据本文所述,这是最新版本。

在将依赖项添加到我们的 Gradle 文件,我们将提示 Android Studio 为了同步更改。这就是我们的方法 Gradle 文件将检查在仅使用默认值的空项目中添加 RecyclerView 的情况。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.guru99.learnrecycler"
        minSdkVersion 17
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
             'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.recyclerview:recyclerview:1.1.0"
}

目前我们只有一个布局文件。我们将从一个简单的例子开始,其中使用 RecyclerView 在屏幕上显示水果名称列表。

物品清单

我们将导航到 MainActivity 文件,并在设置过程中生成的 onCreate() 方法之前创建一个包含水果名称的数组。

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

我们的下一个目标是使用 RecyclerView 在屏幕上展示这个列表。为此,我们将导航到存放布局文件的布局目录,并创建一个视图来负责显示一种水果。

列表中每个项目所用的布局

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fruitName"
    />
</TextView>

在上面的 TextView 中,我们添加了一个 id 字段,用于标识视图。默认情况下不会生成该字段。我们给 TextView 赋予了 id 为 fruitName 的名称,以便与将要绑定到该视图的数据相匹配。

将 RecyclerView 添加到主布局

在同一个活动中,默认情况下会生成一个 main_layout.xml 布局文件。如果我们选择一个空项目,它也会生成一个 XML 其中包含一个 ConstraintLayout,其内部将有一个显示文本“Hello”的 TextView。我们将删除所有内容,使布局仅包含 RecyclerView,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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:id="@+id/fruitRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

我们还为 RecyclerView 添加了一个 id 属性,我们将使用该属性在代码中引用它。

android:id="@+id/fruitRecyclerView"

然后我们将返回到 MainActivity 文件。利用我们创建的 ID,我们可以引用刚刚创建的视图。首先,我们将使用提供的 findViewById() 方法来引用 RecyclerView。 Android我们将在 onCreate() 方法中实现这一点。我们的 onCreate() 方法如下所示。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
}

创建一个 ViewHolder

接下来,我们将创建一个 RecyclerView.ViewHolder,它负责将我们的 View 转换为 ViewHolder,RecyclerView 使用该 ViewHolder 来显示我们的项目。我们将在 onCreate() 方法之后立即执行此操作。

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

}

创建一个 RecyclerViewAdapter

接下来,我们将创建一个继承自 RecyclerView.Adapter 类的 FruitArrayAdapter 类。我们创建的 FruitArrayAdapter 将负责以下工作:从水果数组中获取水果名称,使用我们的视图 one_fruit_view.xml 创建一个 ViewHolder,然后将水果绑定到 ViewHolder,并将 ViewHolder 的内容动态绑定到我们创建的视图。

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio 会在我们的 FruitArrayAdapter 上添加红色波浪线,告诉我们需要实现 RecyclerView 可以用来将我们的数组连接到 ViewHolder 的方法。

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

     }

     override fun getItemCount(): Int {

     }

     override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

     }
 }

我们将从生成的代码中最简单的部分——getItemCount() 方法入手。我们知道可以通过调用数组的大小属性来获取数组中的元素数量。

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

接下来,我们将实现 onCreateViewHolder 方法。RecyclerView 会在这里请求我们帮助它创建一个 FruitViewHolder 对象。如果我们还记得的话,我们的 FruitViewHolder 类最初是这样的:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

它需要我们创建的 fruitView,该 fruitView 是一个 XML 文件 one_fruit_view.xml。我们可以创建对该 XML 的引用并将其转换为 View,如下所示。

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

剩下的部分是 onBindViewHolder 重写。

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter 会请求一个位置整数,我们用它来从列表中获取一个项目。它还会提供一个 ViewHolder,以便我们可以将从 fruitArray 获取的项目绑定到 ViewHolder 中包含的 View。ViewHolder 中包含的 View 可以通过 ViewHolder.itemView 字段访问。获取到 View 后,我们可以使用之前创建的 id fruitName 来设置其内容。

override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

          var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

至此,我们的 FruitArrayAdapter 就完成了,其结构如下所示。

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

最后,我们准备连接 RecyclerView 的剩余部分,即创建一个 LayoutManager,它将告诉 RecyclerView 如何显示列表的内容,是使用 LinearLayoutManager 以线性方式显示,还是使用 GridLayoutManager 或 StaggeredGridLayoutManager 以网格方式显示。

创建布局管理器

我们将回到 onCreate 函数中,添加 LayoutManager。

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

       var fruitLinearLayout = LinearLayoutManager(this)

       myFruitRecyclerView.layoutManager =fruitLinearLayout

   }

将我们的适配器挂接到项目上,并将其设置在 RecyclerView 上

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

我们还创建了一个 fruitListAdapter 实例,并将水果名称数组传递给了它。基本上,我们就完成了所有工作。完整的 MainActivity.kt 文件如下所示。

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

下载项目

常见问题

RecyclerView 是 ListView 更灵活、更高效的继任者。它强制执行 ViewHolder 模式,通过布局管理器支持线性、网格和交错布局,并添加了内置的项目动画,而 ListView 默认情况下不提供这些功能。

GridView 只能以固定网格形式显示项目。RecyclerView 可以使用 GridLayoutManager 生成网格,但它还支持线性布局和交错布局、视图回收和动画,使其比 GridView 更加灵活。

这个 RecyclerView 示例是用 Kotlin 编写的,Kotlin 是官方推荐的语言。 Google HPMC胶囊 Android 开发中,相同的 RecyclerView、适配器和布局管理器 API 也同样适用。 Java.

在 ViewHolder 或 onBindViewHolder 中附加点击监听器,然后通过接口回调将其暴露给 Activity 或 Fragment。使用 ViewHolder 的位置信息可以避免将点击事件绑定到错误的已回收项。

DiffUtil 是一个实用工具类,用于计算两个列表之间的差异,并将最少的更新操作分发给适配器。它能高效地处理动态列表更改,并自动呈现添加、删除和移动操作的动画效果。

Jetpack Compose 在创建新屏幕时使用 LazyColumn 和 LazyRow 而非 RecyclerView。然而,RecyclerView 在现有的基于 View 的项目中仍然被广泛使用,并且可以通过互操作性 API 嵌入到 Compose 中。

是的。像 GitHub Copilot 和基于 GPT 的工具这样的 AI 助手可以根据简短的提示生成 RecyclerView 适配器、ViewHolder 和 onBindViewHolder 的样板代码。开发者仍然需要检查生成的代码,确保其正确性和性能。

机器识别 Android 应用程序使用 RecyclerView 来渲染动态输出,例如推荐内容、搜索排名或图像分类。适配器将模型结果绑定到列表项,从而使庞大且频繁变化的数据集能够流畅滚动。

总结一下这篇文章: