Android RecyclerView:什么是,通过简单示例学习
RecyclerView 是什么 Android?
- 回收站视图 是一个比 GridView 和 ListView 更灵活、更高级的小部件。它是一个用于显示大型数据集的容器,可以通过维护有限数量的视图来高效滚动。当您拥有数据集合时,其元素在运行时会根据网络事件或用户操作而发生变化,您可以使用 RecyclerView 小部件。
观看数
- Android 平台使用 View 和 ViewGroup 类在屏幕上绘制项目。这些类是抽象的,并扩展到不同的实现以适应用例。例如,TextView 的用途很简单,就是在屏幕上显示文本内容。EditText 从相同的 View 类扩展而来,并添加了更多功能以使用户能够输入数据。
我们可以创建自己的自定义视图,以便在开发用户界面时实现更大的灵活性。View 类提供了我们可以重写的方法,用于在屏幕上绘图,以及传递宽度、高度等参数的方法,以及我们想要添加到 View 中的自定义属性,以使其按我们的意愿运行。
视图组
ViewGroup 类是一种 View,但与仅负责显示的简单 View 类不同,ViewGroup 使我们能够将多个视图合并为一个视图,并可以将其作为一个整体引用。在这种情况下,在顶层创建的、可向其添加其他简单视图(我们也可以添加 viewGroup)的 View 称为“父级”,而添加在其中的视图称为“子级”。
我们可以将 View 想象为一个数组,将 ViewGroup 想象为一个数组的数组。鉴于数组的数组本身就是一个数组,我们可以理解如何将 ViewGroup 视为一个 View。
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 anology, we can now group views //together and the structure that would hold that would be what we call the ViewGroup
ViewGroup 还使我们能够定义子视图在视图内的组织方式,例如,垂直或水平放置。我们可以为视图内的交互设置不同的规则。例如,TextView 彼此之间应有 12dp 的距离,而 TextView 后面的 ImageView 应有 5dp 的距离。
如果我们从头开始开发自己的 ViewGroup,情况就会如此。为了简化这些配置, Android 提供了一个名为LayoutParams的类,我们可以用它来输入这些配置。
Android 文件 提供了一些我们在配置自己的 ViewGroup 时会实现的默认参数。一些常见参数与宽度、高度和边距有关。默认情况下,这些配置具有 android:layout_height 结构(用于高度),例如 android:layout_width 在这方面,当您创建 ViewGroup 时,您可以进一步创建特定于您希望 ViewGroup 表现方式的 LayoutParams。
Android 带有默认的 Views 和 ViewGroup,我们可以用它们来执行许多常见的任务。我们提到的一个例子是 TextView。这是一个简单的视图,带有可配置的方面,例如高度、宽度、文本大小等。我们有一个 ImageView 用于显示图像和 EditText,正如我们提到的,还有许多其他的。 Android 还有自定义的 ViewGroups,我们可以向其中添加我们的视图并获得预期的行为。
线性布局
LinearLayout 允许我们在其中添加 View 项目。LinearLayout 是一个方向属性,它决定了它在屏幕上的布局方式。它还具有 LinearLayout.LayoutParams,用于规定内部视图的规则,例如,属性 android:center_horizontal 将使视图沿水平轴居中,而 android:center_vertical 将使视图的内容沿垂直轴居中。
以下是一些图片,帮助您了解居中。我们将其视为 200px x 200px 空间内的简单 TextView,居中属性将使其行为如下。
机器人:中心水平
机器人:中心垂直
安卓:中心
RecyclerView 的核心组件
以下是RecyclerView的重要组件:
RecyclerView.Adapter
基础工作#1 – 适配器模式
适配器是一种将系统或设备的属性转换为原本不兼容的设备或系统的属性的设备。其中一些适配器会修改信号或电源属性,而另一些适配器则只是将一个连接器的物理形式适配到另一个连接器。
我们在现实生活中找到的一个简单例子来解释适配器,就是当我们需要将设备连接在一起时,但它们的连接端口彼此不匹配。当您访问使用不同插座的其他国家/地区时,可能会出现这种情况。如果您携带手机或笔记本电脑充电器,则无法将其连接到电源插座。但是,您不会放弃,只需购买一个适配器,它可以位于电源插座和充电器之间,并允许充电。
在编程中,我们想要将两个数据结构连接在一起以完成任务,但它们的默认端口无法相互通信。
我们将使用设备和充电器的简单示例。我们将有两个充电器实例。一个是美国的,一个是英国的
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 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 } }
在这种情况下,根据我们的类比,出于某种原因,我们在使用 AmericanDevice 时需要使用 BritishCharger,反之亦然。
在编程世界中,这通常是将提供相同功能的库混合在一起时发生的(在我们的上下文中,我们的共享功能是充电)。我们需要找到一种方法来实现这一点。
如果我们按照类比,我们需要去一家电子产品商店购买一个适配器,这样我们就可以为 AmericanDevices 充电,只要有 BritishChargers。从编程的角度来看,我们将是适配器的制造商。
我们将为其中一个创建一个适配器,使其与创建另一个所需的模式完全匹配。我们将按如下方式将其实现为一个类。它不一定需要是一个类,也可以是一个突出显示适配器模式通常做什么的函数。我们将使用一个类,因为它与大多数用法相匹配 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 charingPower:Int = charger.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(theAmericanCharger) //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 在其中的布局方式。
为了进行比较,在使用 Linearlayout ViewGroup 时,我们想要的用例是能够垂直或水平放置项目。这可以通过添加 orientation 属性轻松实现,该属性告诉我们 linearlayout 将如何放置在屏幕上。我们可以通过使用 android:orientation=VERTICAL|HORIZONTAL
属性。
我们还有另一个名为 GridLayout 的 ViewGroup,它的用例是当我们想要将视图放置在矩形网格结构中时。这可能是出于某些原因,例如让我们呈现给应用用户的数据易于使用。根据设计,GridLayout 启用配置来帮助您实现此目标,通过配置我们可以定义网格的尺寸,例如,我们可以有一个 4×4 网格、3 x 2 网格。
RecyclerView.ViewHolder
ViewHolder 是一个抽象类,也是我们从 RecyclerView 扩展而来。ViewHolder 为我们提供了常用方法,帮助我们引用已放置在 RecyclerView 上的 View,即使 RecyclerView 中的回收机制更改了我们不知道的各种引用。
大型列表
当我们想要向用户呈现一组非常大的视图时,可以使用 RecyclerViews,同时又不耗尽我们的 内存 在我们的设备上为创建的每一个视图实例。
如果我们以联系人列表为例,我们会大致了解联系人在列表中的样子。然后我们要做的就是创建一个模板布局 - 实际上是视图 - 其中包含用于填充联系人列表中各种数据的插槽。以下是解释整个目的的伪代码:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
然后我们将得到一个这种性质的 ContactList
<ContactList> </ContactList>
如果是这种情况,我们就会硬编码内容,那么我们就没有办法在不重写应用程序的情况下以编程方式将新内容添加到列表中。幸运的是,将视图添加到 ViewGroup 受 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 为我们提供了一个适配器,就像我们的 AmericanToBritishChargerAdapter()
之前我们已经将无法在 BritishDevice 上使用的 AmericanCharger 转换为可用的东西,类似于现实生活中的电源适配器。
在这种情况下,适配器将采用我们的联系人数组和视图,并从那里生成 RecyclerView 愿意接受的 ViewHolders。
RecyclerView 提供了一个接口,我们可以扩展该接口,通过 RecyclerView.Adapter 类来创建我们的适配器。此适配器内有一种方法可以创建 RecyclerView 想要使用的 ViewHolder 类。因此,我们的情况与之前相同,但多了一个东西,即适配器。
我们有一个联系人数组,一个用于显示一个联系人的视图 OneContactView。RecyclerView 是一个提供回收服务的视图列表,但只愿意接受 ViewHolders
但在这种情况下,我们现在有了 RecyclerView.Adapter 类,它里面有一个创建 ViewHolders 的方法。
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder 是一个抽象类,它将我们的 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
在上面的例子中,我们有两只动物。如果我们偶然想添加一种方法来让它们说话,但库作者并不好玩,我们仍然可以找到一种方法。我们需要的是 Animal 类的包装器。我们可以通过将 Animal 作为我们类的构造函数来实现这一点
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
现在我们可以将一个动物实例传递给 SpeechPoweredAnimalByWrapper。调用它的 sound() 方法将调用传入的动物 sound() 方法。我们还有一个额外的 talk() 方法,它算作我们为传入的动物添加的新功能。我们可以按如下方式使用它:
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"
使用这种模式,我们可以获取类并添加功能。我们需要传递的只是一个类实例和由我们的包装类定义的新方法。
在上述情况下,我们使用了一个具体类。也可以在抽象类中实现相同的功能。我们需要做的就是将 SpeechPoweredAnimalByWrapper 类更改为抽象类,这样就大功告成了。我们将类名更改为更短的名称,使其更具可读性。
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
它与之前相同,但含义不同。在普通类中,我们可以像创建 cat1 和 dog1 一样拥有一个类的实例。但是,抽象类不是为了实例化而是为了扩展其他类。那么我们将如何使用新的 SpeechPowered(var myAnimal:Animal) 抽象类呢?我们可以通过创建新类来使用它,这些新类将扩展它,进而获得它的功能。
在我们的示例中,我们将创建一个 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 类是一个抽象类,它为 View 添加功能,就像我们为动物添加了说话方法一样。添加的功能使它在处理 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 有一个适配器,允许我们将 Contacts 数组连接到 RecyclerView 中的 ContactsView
添加视图
ViewGroup 不会自动重绘,而是遵循特定的时间表。您的设备可能每 10 毫秒或 100 毫秒重绘一次,或者如果我们选择一个荒谬的数字,比如 1 分钟,当我们将 View 添加到 ViewGroup 时,您将在 1 分钟后 ViewGroup “刷新”时看到更改。
RecyclerView.Recycler
基础工作 #3. 缓存
我们定期刷新的最佳示例之一是浏览器。假设我们访问的网站是静态的,不会动态发送内容,我们需要不断刷新才能看到变化。
对于此示例,我们假设所讨论的网站是 Twitter。我们将列出一系列静态推文,而查看新推文的唯一方法是单击刷新按钮重新获取内容。
重新绘制整个屏幕显然是一件代价高昂的事情。想象一下,如果情况确实如此,我们的电话提供商的带宽有限。而且我们的推文列表中有很多图片和视频,那么每次刷新都要重新下载页面的所有内容,成本将非常高昂。
我们需要一种方法来存储已加载的推文,并确保我们的下一个请求能够说出它已经拥有的推文。因此,它不会重新下载所有内容,而只会获取它拥有的新推文,还会检查本地保存的某些推文是否不再存在,以便它可以在本地删除它。我们所描述的称为缓存。
我们发送给网站的有关我们拥有的内容的信息称为元数据。因此,实际上我们不只是说“我们想要加载您的网站”,而是说“我们想要加载您的网站,这是我们上次加载时已经保存的一些内容,请使用它只向我们发送其中不存在的内容,这样我们就不会使用大量带宽,因为我们没有太多资源。”
布局调用 – 推文列表一定很疯狂
布局调用的一个示例是 scrollToPosition
这是聊天应用等应用中常见的示例。如果聊天线程中的某人回复了之前的聊天气泡,某些聊天应用会包含回复和聊天气泡的链接,点击后会将您导航到原始消息所在的位置。
在这种情况下,我们在将 LayoutManager 添加到 RecyclerView 之前和拥有 RecyclerView.Adapter 之前调用此方法,scrollToPosition(n:Int) 会被忽略。
RecyclerView 组件之间的通信
基础工作 #4. 回调
RecyclerView 在工作时有很多移动部件。它必须处理 LayoutManager,后者告诉我们如何组织视图,无论是线性组织还是网格组织。它必须处理一个适配器,该适配器负责将我们的项目 contactList 转换为 Views OneContactView,然后转换为 ViewHolders OneContactViewHolder,RecyclerView 愿意在它为我们提供的方法中使用它。
RecyclerView 的原材料是我们的视图,例如 OneContactView 和数据源。
contactList:Array<Contact>
我们使用一个简单的场景作为起点来了解 RecyclerView 想要实现的目标。
当我们拥有一个包含 1000 个联系人的静态数组并希望向用户显示时,一个基本情况很容易理解。
当列表不再处于静态时,RecyclerView 机制才真正开始发挥作用。
对于动态列表,我们必须考虑当我们向列表中添加项目或从列表中删除项目时,屏幕上的视图会发生什么。
布局管理器
除了决定我们的视图如何以线性或网格形式布局之外,LayoutManager 还在幕后做了很多工作来帮助 Recycler 知道何时进行回收。
它负责跟踪屏幕上当前可见的视图,并将此信息传达给回收机制。当用户向下滚动时,布局管理器负责通知回收系统顶部失去焦点的视图,以便可以重复使用它们,而不是让它们留在那里并消耗内存,或者不必销毁它们并创建新的视图。
这意味着 LayoutManager 需要跟踪用户在滚动列表时的位置。它通过一个以索引为基础的位置列表来实现这一点,即第一个项目从 0 开始,然后增加以匹配列表中的项目数。
如果我们可以查看 10 个列表中的 100 个项目,那么在开始时,LayoutManager 就知道它已经从焦点视图 0 一直到视图 9 处于焦点状态,当我们滚动时,LayoutManager 能够计算出失去焦点的视图。
LayoutManager 能够将这些视图释放到回收机制,以便可以重复使用它们(可以将新数据绑定到它们,例如可以删除视图的联系人数据,并且下一个段中的新联系人数据可以替换占位符)。
如果我们拥有的 List 是静态的,这将是一个令人满意的情况,但使用 RecyclerView 的最常见用例之一是动态列表,其中数据可能来自在线端点,甚至可能来自传感器。不仅会添加数据,而且有时还会删除或更新 List 上的数据。
数据的动态状态使得 LayoutManager 很难推理。因此,LayoutManager 维护着一个有关项目和位置的自己的列表,该列表与回收组件使用的列表是分开的。这确保它能够正确完成布局工作。
同时,RecyclerView 的 LayoutManager 不想错误地表示它拥有的数据。为了正确操作,LayoutManager 会以给定的间隔(60 毫秒)与 RecyclerView.Adapter 同步,共享有关我们的列表项的信息。即,添加、更新、删除、从一个位置移动到另一个位置的项目)。LayoutManager 在收到此信息后,会在必要时重新组织屏幕上的内容以匹配更改。
处理 RecylerView 的许多核心操作都围绕 RecyclerView.LayoutManager 和 RecyclerView.Adapter 之间的通信进行,它们存储了有时静态或有时动态的数据列表。
更重要的是,当我们的 RecyclerView.Adapter 将列表中的内容(例如,联系人)绑定到 ViewHolder 时,RecyclerView 为我们提供了可用于监听 onBindViewHolder 等事件的方法,以便现在它可以用来在屏幕上显示信息。
另一个是 onCreateViewHolder,它告诉我们 RecyclerView.Adapter 何时会将常规 View(如 OneContactView)转换为 RecyclerView 可以使用的 ViewHolder 项目。从我们的 ViewHolder 中获取,以供 RecyclerView 使用。onViewDetached
除了实现回收的核心机制之外,RecyclerView 还提供了自定义行为而不影响回收的方法。
视图的重用使得我们很难用静态视图来做一些常见的事,比如对 onClick 事件的反应。
我们知道 RecyclerView.LayoutManager
向用户呈现视图的组件可能会暂时与 RecyclerView.Adapter
其中包含我们存储在数据库中或从源流式传输的列表。将 OnClick 事件直接放在视图上可能会导致意外行为,例如删除错误的联系人或更改联系人。
Gradle
如果我们想使用 RecyclerView,我们需要在我们的构建 .gradle 文件中将其添加为依赖项。
在下面的示例中,我们使用了实现“androidx.recyclerview:recyclerview:1.1.0”,这是本文中最新版本。
在将依赖项添加到我们的 Gradle 文件,我们将提示 Android 工作室 Sync确认变更,
这就是我们的方式 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 字段,用于识别 View。
它不是默认生成的。我们为 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 使用它来显示我们的项目。
我们将在有趣的 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,并将内容动态绑定到我们创建的 view one_fruit_view.xml。
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 可以使用该方法将我们的数组连接到 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() 方法。我们知道如何通过调用 array.length 方法获取数组中的项目数。
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) { } }
然后我们将实现方法override fun onCreateViewHolder。
这是 RecyclerView 要求我们帮助它构建一个 FruitHolder 的地方。
如果我们回想一下,我们的 FruitViewHolder 类看起来是这样的:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
它需要我们创建为 xml 文件 one_fruit_view.xml 的 fruitView
我们可以创建对此 xml 的引用并将其转换为视图,如下所示。
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) { } }
剩下的部分是覆盖
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter 会询问一个位置整数,我们将使用它从列表中获取一个项目。它还为我们提供了一个容器,以便我们可以将从 fruitArray 中获取的项目绑定到容器内的 View。
ViewHoder 中保存的 View 可以通过字段 ViewHolder.itemView 访问。获取视图后,我们可以使用之前创建的 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) } } }