RecyclerView使用总结(Kotlin)
一、前言
最近在学kotlin语言,所以写了这个Demo来练手,顺便复习一下RecyclerView的使用。
Demo效果图:
二、开始
1、布局文件
这是Demo的主布局文件,由于使用到一个下拉刷新,上拉加载的开源框架,所以先添加一下依赖:
implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3' //核心必须依赖
implementation 'com.scwang.smart:refresh-header-classics:2.0.3' //经典刷新头
implementation 'com.scwang.smart:refresh-footer-classics:2.0.3' //经典加载
SmartRefreshLayout框架地址:https://github.com/scwang90/SmartRefreshLayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/addBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="增加"
android:textAllCaps="false"
android:textSize="14sp" />
<Button
android:id="@+id/updateBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="更新"
android:textAllCaps="false" />
<Button
android:id="@+id/removeBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="删除"
android:textAllCaps="false" />
<Button
android:id="@+id/clearBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="清空"
android:textAllCaps="false" />
</LinearLayout>
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smart.refresh.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
</LinearLayout>
recyclerView为线性布局的item条目的布局:
(另外两种布局的item布局就不放了,跟这个差不多)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:background="#FFFFFF"
android:orientation="horizontal"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/apple_pic" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:text="TextView"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
2、初始化用到的数据
准备一下recyclerView用到的实体类数据。
先创建实体类Fruit:
class Fruit(var name:String, var imageId:Int) {
}
初始化数据:
private val fruitList = ArrayList<Fruit>()
private fun initData() {
repeat(2) {
fruitList.add(Fruit("apple", R.drawable.apple_pic))
fruitList.add(Fruit("banana", R.drawable.banana_pic))
fruitList.add(Fruit("orange", R.drawable.orange_pic))
fruitList.add(Fruit("watermelon", R.drawable.watermelon_pic))
fruitList.add(Fruit("pear", R.drawable.pear_pic))
fruitList.add(Fruit("strawberry", R.drawable.strawberry_pic))
fruitList.add(Fruit("mango", R.drawable.mango_pic))
fruitList.add(Fruit("grape", R.drawable.grape_pic))
fruitList.add(Fruit("pineapple", R.drawable.pineapple_pic))
}
showList(true)
}
3、recyclerView设置布局管理器
给recyclerView设置一个布局管理器,有三种管理器,也可以自定义布局管理器。
类名 | 含义 |
---|---|
LinearLayoutManager | 线性布局管理器 |
GridLayoutManager | 网格布局管理器 |
StaggeredGridLayoutManager | 交错网格布局管理器(瀑布流布局) |
val linearLayout = LinearLayoutManager(this)
linearLayout.orientation = LinearLayoutManager.VERTICAL
recyclerView.layoutManager = linearLayout
可以通过更改布局管理器实例的orientation属性的值来改变布局方向,水平或者垂直。
4、适配器
编写适配器类是使用RecyclerView最重要的内容。
由于我这个Demo有多种不同布局的RecyclerView,所以我对适配类进行了抽取,创建一个RecyclerViewBaseAdapter类继承RecyclerView.Adapter,复写三个重要的方法onCreateViewHolder()
,getItemCount()
,onBindViewHolder()
然后创建一个内部类MyViewHolder继承自RecyclerView.ViewHolder。
创建一个抽象方法getSubView()
该方法的作用就是让子类来提供要加载的item布局。
package com.example.recyclerviewkt
import android.telecom.Connection
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import java.util.*
import kotlin.collections.ArrayList
abstract class RecyclerViewBaseAdapter(val fruitList:ArrayList<Fruit>) : RecyclerView.Adapter<RecyclerViewBaseAdapter.MyViewHolder>(),
MyItemTouchCallback.ItemTouchResultCallback {
abstract fun getSubView(parent: ViewGroup, viewType: Int):View
inner class MyViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val fruitName : TextView = itemView.findViewById(R.id.fruit_name)
val fruitImage : ImageView = itemView.findViewById(R.id.fruit_image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = getSubView(parent,viewType)
val viewHolder = MyViewHolder(itemView)
return viewHolder
}
override fun getItemCount() = fruitList.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val fruit:Fruit = fruitList[position]
holder.fruitName.text = fruit.name
holder.fruitImage.setImageResource(fruit.imageId)
}
创建FruitListAdapter类继承RecyclerViewBaseAdapter,
复写getSubView()
方法,返回item条目的布局文件
package com.example.recyclerviewkt
import android.view.View
import android.view.ViewGroup
class FruitListAdapter(fruitList: ArrayList<Fruit>) : RecyclerViewBaseAdapter(fruitList) {
override fun getSubView(parent: ViewGroup, viewType: Int) = View.inflate(parent.context, R.layout.item_list_view, null)
}
}
给recyclerView配置适配器:
mAdapter = FruitListAdapter(fruitList)
recyclerView.adapter = mAdapter
5、设置点击事件
给recyclerView的item设置点击事件,或者item布局中的控件设置点击事件。
先在RecyclerViewBaseAdapter中创建点击事件的回调接口,并提供注册点击事件监听的方法:
interface OnItemClickListener{
fun onItemClick(position:Int)
}
fun setOnItemClickListener(listener:OnItemClickListener){
this.mListener = listener
}
在onCreateViewHolder()方法中设置点击事件,完整代码:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = getSubView(parent,viewType)
val viewHolder = MyViewHolder(itemView)
viewHolder.itemView.setOnClickListener {
//整个item的点击事件处理
val position = viewHolder.adapterPosition
//在这里直接处理或通知到外面处理
//Toast.makeText(parent.context,"你点击了" + fruitList[position].name + "条目",Toast.LENGTH_SHORT).show()
mListener.onItemClick(position)
}
viewHolder.fruitImage.setOnClickListener {
//图片的点击事件处理
val position = viewHolder.adapterPosition
//Log.d(TAG,"position : " + position)
Toast.makeText(parent.context,"你点击了" + fruitList[position].name + "图片",Toast.LENGTH_SHORT).show()
}
return viewHolder
}
然后在完成适配器对象创建后,注册点击事件监听,让当前类实现RecyclerViewBaseAdapter.OnItemClickListener接口:
mAdapter.setOnItemClickListener(this)
在回调方法中对点击事件进行处理:
override fun onItemClick(position: Int) {
//item点击事件处理
Toast.makeText(this, "你点击了" + fruitList[position].name + "条目", Toast.LENGTH_SHORT).show()
}
6、下拉刷新,上拉加载更多
使用开源框架SmartRefreshLayout完成该功能。
使用方法:
首先在布局文件中添加需要的控件,具体看上面布局代码。然后在代码中设置对上拉、下拉的监听,并进行相应的处理(添加数据或更新数据):
//下拉刷新
refreshLayout.setOnRefreshListener {
fruitList[0].name = "cherry"
fruitList[0].imageId = R.drawable.cherry_pic
mAdapter.notifyDataSetChanged()
it.finishRefresh(1000)
}
//上拉加载更多
refreshLayout.setOnLoadMoreListener {
mAdapter.add(fruitList.size,Fruit("cherry",R.drawable.cherry_pic))
recyclerView.scrollToPosition(fruitList.size-1)
it.finishLoadMore(1000)
}
7、拖拽移动、滑动删除
实现该功能需要用到ItemTouchHelper这个类,创建一个该类实例时需要传入一个ItemTouchHelper.Callback对象,创建该对象需要我们自己定义一个类继承ItemTouchHelper.Callback,从而对item的滑动和拖拽进行配置:
对于该类的解释可以看代码中的注释
package com.example.recyclerviewkt
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class MyItemTouchCallback(val mCallback:ItemTouchResultCallback) : ItemTouchHelper.Callback(){
/**
* ItemTouchHelper.UP 向上
* ItemTouchHelper.DOWN 向下
* ItemTouchHelper.LEFT 向左
* ItemTouchHelper.RIGHT 向右
* ItemTouchHelper.START 从右向左
* ItemTouchHelper.END 从左向右
* 如果某个值传0,表示不触发该操作,
* 此处设置支持上下拖拽和向右滑动
*/
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN,ItemTouchHelper.END)
/**
* 拖拽操作的回调
* @param viewHolder 被拖动的ViewHolder
* @param target 目标位置的ViewHolder
* @return 如果item发生位置交换,返回true,否则返回false
*/
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
mCallback.onItemMove(viewHolder.adapterPosition,target.adapterPosition)
return true
}
/**
* 滑动操作的回调
* @param viewHolder 滑动的ViewHolder
* @param direction 滑动的方向
*/
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
mCallback.onItemDelete(viewHolder.adapterPosition)
}
interface ItemTouchResultCallback{
//拖拽移动
fun onItemMove(fromPosition:Int,toPosition:Int)
//滑动删除
fun onItemDelete(position:Int)
}
}
同时我们还要在类中声明一个回调接口ItemTouchResultCallback
,用来通知外部对滑动和拖拽操作进行相应的数据处理。
创建完上面这个类后,让recyclerView和该类实例进行绑定,从而使recyclerView可以滑动或拖拽item。
val itemTouchHelperCallback = MyItemTouchCallback(mAdapter)
val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
最后,让适配器类实现上面说到的MyItemTouchCallback.ItemTouchResultCallback回调接口,在回调方法中进行相应的数据处理。
/**
* 拖拽移动item
*/
override fun onItemMove(fromPosition: Int, toPosition: Int) {
Collections.swap(fruitList,fromPosition,toPosition)
notifyItemMoved(fromPosition,toPosition)
}
/**
* 向右滑动删除item
*/
override fun onItemDelete(position: Int) {
fruitList.removeAt(position)
notifyItemRemoved(position)
}
8、适配器完整代码
package com.example.recyclerviewkt
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import java.util.*
import kotlin.collections.ArrayList
abstract class RecyclerViewBaseAdapter(val fruitList:ArrayList<Fruit>) : RecyclerView.Adapter<RecyclerViewBaseAdapter.MyViewHolder>(),
MyItemTouchCallback.ItemTouchResultCallback {
private val TAG = "RecyclerViewBaseAdapter"
private lateinit var mListener: OnItemClickListener
abstract fun getSubView(parent: ViewGroup, viewType: Int):View
inner class MyViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val fruitName : TextView = itemView.findViewById(R.id.fruit_name)
val fruitImage : ImageView = itemView.findViewById(R.id.fruit_image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = getSubView(parent,viewType)
val viewHolder = MyViewHolder(itemView)
viewHolder.itemView.setOnClickListener {
//整个item的点击事件处理
val position = viewHolder.adapterPosition
//在这里直接处理或通知到外面处理
//Toast.makeText(parent.context,"你点击了" + fruitList[position].name + "条目",Toast.LENGTH_SHORT).show()
mListener.onItemClick(position)
}
viewHolder.fruitImage.setOnClickListener {
//图片的点击事件处理
val position = viewHolder.adapterPosition
//Log.d(TAG,"position : " + position)
Toast.makeText(parent.context,"你点击了" + fruitList[position].name + "图片",Toast.LENGTH_SHORT).show()
}
return viewHolder
}
override fun getItemCount() = fruitList.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val fruit:Fruit = fruitList[position]
holder.fruitName.text = fruit.name
holder.fruitImage.setImageResource(fruit.imageId)
}
/**
* 增加条目
*/
fun add(position: Int,fruit: Fruit){
fruitList.add(position,fruit)
notifyItemInserted(position)
//notifyDataSetChanged()
}
/**
* 更新条目
*/
fun update(position: Int){
fruitList[position].name = "这是经过修改的数据"
fruitList[position].imageId = R.drawable.grape_pic
notifyItemChanged(position)
}
/**
* 删除条目
*/
fun remove(position: Int){
fruitList.removeAt(position)
notifyItemRemoved(position)
}
/**
* 清空条目
*/
fun clear(){
fruitList.clear()
notifyDataSetChanged()
}
/**
* 注册item点击事件
*/
fun setOnItemClickListener(listener:OnItemClickListener){
this.mListener = listener
}
/**
* item点击事件的回调接口
*/
interface OnItemClickListener{
fun onItemClick(position:Int)
}
/**
* 拖拽移动item
*/
override fun onItemMove(fromPosition: Int, toPosition: Int) {
Collections.swap(fruitList,fromPosition,toPosition)
notifyItemMoved(fromPosition,toPosition)
}
/**
* 向右滑动删除item
*/
override fun onItemDelete(position: Int) {
fruitList.removeAt(position)
notifyItemRemoved(position)
}
}
9、含多种item布局的recyclerView
要让recyclerView加载不同的item布局,关键就是根据不同的item类型加载不同的ViewHolder,所以我们需要创建多种ViewHolder,我这里创建了三种ViewHolder:
inner class BigImageViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val imageView:ImageView = itemView.findViewById(R.id.fruit_image)
}
inner class ThreeImageViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val imageView:ImageView = itemView.findViewById(R.id.fruit_image)
}
inner class RightImageViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val imageView:ImageView = itemView.findViewById(R.id.fruit_image)
}
item数据实体类要含有标明item种类的type变量,这是我声明的一个实体类:
package com.example.recyclerviewkt
class ItemWithType(val imageId:Int, val type:Int) {
}
如何在创建ViewHolder时知道当前item是那种类型,关键就在于getItemViewType()方法,该方法返回当前位置的item数据的类型:
override fun getItemViewType(position: Int): Int {
return itemList[position].type
}
从而在执行onCreateViewHolder(parent: ViewGroup, viewType: Int)
方法创建ViewHolder时,就可以知道当前item是哪种类型了。
完整适配器代码如下:
package com.example.recyclerviewkt
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
class MoreTypeItemAdapter(val itemList:ArrayList<ItemWithType>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
companion object{
@JvmStatic
val TYPE_BIG_IMAGE = 0
@JvmStatic
val TYPE_THREE_IMAGE = 1
@JvmStatic
val TYPE_RIGHT_IMAGE = 2
}
inner class BigImageViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val imageView:ImageView = itemView.findViewById(R.id.fruit_image)
}
inner class ThreeImageViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val imageView:ImageView = itemView.findViewById(R.id.fruit_image)
}
inner class RightImageViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val imageView:ImageView = itemView.findViewById(R.id.fruit_image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType){
TYPE_BIG_IMAGE -> {
BigImageViewHolder(View.inflate(parent.context,R.layout.item_big_image_view,null))
}
TYPE_THREE_IMAGE -> {
val itemView = View.inflate(parent.context,R.layout.item_three_image_view,null)
ThreeImageViewHolder(itemView)
}
TYPE_RIGHT_IMAGE -> {
val itemView = View.inflate(parent.context,R.layout.item_right_image_view,null)
RightImageViewHolder(itemView)
}
else -> {
val itemView = View.inflate(parent.context,R.layout.item_right_image_view,null)
RightImageViewHolder(itemView)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val imageId = itemList[position].imageId
when(getItemViewType(position)){
TYPE_BIG_IMAGE -> {
(holder as BigImageViewHolder).imageView.setImageResource(imageId)
}
TYPE_THREE_IMAGE -> {
(holder as ThreeImageViewHolder).imageView.setImageResource(imageId)
}
TYPE_RIGHT_IMAGE -> {
(holder as RightImageViewHolder).imageView.setImageResource(imageId)
}
}
}
override fun getItemCount(): Int {
return itemList.size
}
override fun getItemViewType(position: Int): Int {
return itemList[position].type
}
}
三、结尾
该Demo的完整源码地址:
https://github.com/gitHub-lgh/RecyclerViewKtDemo/tree/master
如果这篇文章给你带来了一点帮助,请点个赞吧,谢谢。