目录
1.2 为什么选择 RecyclerView 而不是 ListView?
前言
RecyclerView 是 Android 中用于显示大量数据的强大控件,它提供了比 ListView 更强大的功能和性能。随着需求的多样化,RecyclerView 也提供了多种方式来定制和优化列表显示。在本篇博客中,将介绍 RecyclerView 的基础用法,详细展示如何实现多类型条目显示、布局切换、点击事件处理等常见功能,同时讲解一些扩展应用技巧,帮助你入门使用 RecyclerView 构建现代 Android 应用。
一、RecyclerView 基础
1.1 RecyclerView 是什么?
RecyclerView 是 Android 中一个用于显示长列表、网格等多项内容的控件,它相较于传统的 ListView 和 GridView,具有更高的性能,支持更多灵活的布局和动画效果。RecyclerView 通过复用视图来提高性能,减少内存消耗。
- 性能优化:使用 ViewHolder 模式,避免重复加载视图。
- 灵活性:支持自定义布局管理器和条目装饰。
- 动画效果:内置了丰富的添加、删除和移动动画。
1.2 为什么选择 RecyclerView 而不是 ListView?
ListView 在处理大量数据时会面临性能瓶颈,因为它每次滚动时都会重新加载所有的视图。而 RecyclerView 则通过 ViewHolder 进行视图复用,不仅能显著提高性能,还支持多种布局管理器(LayoutManager)和动画效果。
1.3 关键组件
RecyclerView 的实现主要依赖于以下三个核心组件:
- Adapter:Adapter 是数据源与 RecyclerView 之间的桥梁,负责将数据绑定到视图上。
- ViewHolder:ViewHolder 是对 RecyclerView 中每个条目视图的缓存,它避免了每次滑动时都要重新查找视图的性能问题。
- LayoutManager:LayoutManager 决定了 RecyclerView 中条目的排列方式,它支持多种布局类型,如 LinearLayoutManager(线性布局)、GridLayoutManager(网格布局)、StaggeredGridLayoutManager(瀑布流布局)等。
1.4 布局文件的基本配置
在项目的布局文件中,我们首先定义一个简单的布局文件 activity_main.xml作为项目的主布局
,其中包含一个 RecyclerView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
二、实现多类型条目
2.1 多类型条目的需求
在实际开发中,我们经常需要在 RecyclerView 中显示不同类型的条目,比如文本项、图片项,或是带有图片的文本项,比如在QQ的聊天列表中,与好友的聊天栏就是头像+昵称的组合:
为此,我们需要实现一个支持多种条目的 RecyclerView Adapter,来实现项目开发时的不同需求。
2.2 多类型条目的实现
我们首先定义一个 MultiTypeAdapter
,这个适配器支持三种类型的条目:文本条目、图片条目和文本图片混合的条目。我们将为每种条目类型创建一个对应的 ViewHolder,并在 Adapter 中通过 getItemViewType()
方法动态返回当前条目的类型。接着,在 onCreateViewHolder()
方法中,根据不同的条目类型加载不同的布局,最终在 onBindViewHolder()
方法中将数据绑定到相应的视图中。
1)文本条目
对于纯文本条目,我们只需要显示一个 TextView
,并通过绑定文本数据来显示内容。创建一个item_text.xml来实现。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Text Item"
android:textSize="16sp" />
</LinearLayout>
2)图片条目
图片条目需要显示一个 ImageView
,并通过绑定图片资源 ID 来显示图片。创建一个item_image.xml来实现。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/presence_video_online" />
</LinearLayout>
3)图片在左,文本在右的条目
这种布局需要我们设计一个包含 ImageView
和 TextView
的布局文件。创建一个item_image_text.xml来实现。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/imageViewLeft"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@android:drawable/star_big_on" />
<TextView
android:id="@+id/textViewRight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:paddingStart="16dp" />
</LinearLayout>
数据结构
通常情况下,需要预设一个容器来储存条目,这里使用MutableList,这类容器适合需要动态管理元素、并且可能会进行元素增删改操作的场景。
private val itemList = mutableListOf<Any>(
"Text Item 1", // 文本条目
"Text Item 2", // 文本条目
android.R.drawable.btn_star_big_on, // 图片条目
Pair(android.R.drawable.btn_star_big_off, "Image and Text Item 1"), // 图片和文本条目
"Text Item 3", // 文本条目
Pair(R.drawable.ic_launcher_background, "Image and Text Item 2") // 图片和文本条目
)
Adapter 实现
在Adapter类中,需要定义条目的类型常量数字,方便调用。同时创建ViewHolder的启动方法,来根据条目类型的不同加载对应的布局。
package com.example.blog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.blog.R
class MultiTypeAdapter(private val items: List<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// 定义条目的类型常量
companion object {
const val TYPE_TEXT = 0 // 文本条目类型
const val TYPE_IMAGE = 1 // 图片条目类型
const val TYPE_IMAGE_TEXT = 2 // 混合条目类型
}
// 创建 ViewHolder 的方法,根据条目类型加载不同的布局
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
// 如果条目类型是 TYPE_TEXT,加载文本条目的布局
TYPE_TEXT -> TextViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false))
// 如果条目类型是 TYPE_IMAGE,加载图片条目的布局
TYPE_IMAGE -> ImageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false))
// 如果条目类型是 TYPE_IMAGE_TEXT,加载混合条目的布局
TYPE_IMAGE_TEXT -> ImageTextViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_image_text, parent, false))
// 如果条目类型无效,抛出异常
else -> throw IllegalArgumentException("Invalid view type")
}
}
// 绑定数据到 ViewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// 根据当前条目的类型,进行相应的数据绑定
when (getItemViewType(position)) {
TYPE_TEXT -> (holder as TextViewHolder).bind(items[position] as String)
TYPE_IMAGE -> (holder as ImageViewHolder).bind(items[position] as Int)
TYPE_IMAGE_TEXT -> (holder as ImageTextViewHolder).bind(items[position] as Pair<Int, String>)
}
}
// 返回列表项的数量
override fun getItemCount(): Int = items.size
// 根据当前位置返回条目的类型
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
// 如果是字符串类型,返回文本条目类型
is String -> TYPE_TEXT
// 如果是整数类型,返回图片条目类型
is Int -> TYPE_IMAGE
// 如果是 Pair 类型(包含图片和文本),返回图片在左,文本在右条目类型
is Pair<*, *> -> TYPE_IMAGE_TEXT
// 如果条目类型无效,抛出异常
else -> throw IllegalArgumentException("Invalid item type")
}
}
// 文本条目的 ViewHolder,负责绑定文本数据
class TextViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val textView: TextView = view.findViewById(R.id.textView)
fun bind(text: String) {
textView.text = text
}
}
// 图片条目的 ViewHolder,负责绑定图片资源
class ImageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView: ImageView = view.findViewById(R.id.imageView)
fun bind(imageRes: Int) {
imageView.setImageResource(imageRes)
}
}
// 混合条目的 ViewHolder,负责绑定图片资源和文本数据
class ImageTextViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView: ImageView = view.findViewById(R.id.imageViewLeft)
private val textView: TextView = view.findViewById(R.id.textViewRight)
fun bind(item: Pair<Int, String>) {
imageView.setImageResource(item.first)
textView.text = item.second
}
}
}
MainActivity实现
为了呈现一个基本的布局,MainActivity还是不可少的,在onCreate方法中,需要初始化各种支持控件,如RecyclerView,MultiTypeAdapter,并设置主布局。
package com.example.blog
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: MultiTypeAdapter
private val itemList = mutableListOf<Any>(
"Text Item 1", // 文本条目
"Text Item 2", // 文本条目
android.R.drawable.btn_star_big_on, // 图片条目
Pair(android.R.drawable.btn_star_big_off, "Image and Text Item 1"), // 图片和文本条目
"Text Item 3", // 文本条目
Pair(R.drawable.ic_launcher_background, "Image and Text Item 2") // 图片和文本条目
)
// 在 Activity 创建时调用此方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 获取 RecyclerView 控件
recyclerView = findViewById(R.id.recyclerView)
// 初始化适配器
adapter = MultiTypeAdapter(itemList) { position ->
onItemClicked(position)
}
recyclerView.adapter = adapter
// 设置 RecyclerView 初始布局管理器
recyclerView.layoutManager = LinearLayoutManager(this)
}
}
还可以在MainActivity中,添加一个点击条目时的Toast弹窗:
// 点击 RecyclerView 项目时的处理逻辑
private fun onItemClicked(position: Int) {
val item = itemList[position]
Toast.makeText(this, "Item clicked: $item", Toast.LENGTH_SHORT).show()
}
效果如图:
三、动态布局切换
切换布局是一种增强用户体验的常用功能。在实际开发中,可能需要动态切换布局,比如线性布局显示条目有点长,可以切换为网格布局。以下代码展示了如何在 RecyclerView 中切换布局管理器:
创建切换布局按钮
在activity_main.xml中添加一个按钮:
<Button
android:id="@+id/btnSwitchLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切换布局" />
在onCreate中为按钮加载点击监听器
// 切换布局
findViewById<View>(R.id.btnSwitchLayout).setOnClickListener {
switchLayoutManager()
}
方法实现
// 切换布局管理器
private fun switchLayoutManager() {
val currentLayoutManager = recyclerView.layoutManager
if (currentLayoutManager is LinearLayoutManager && currentLayoutManager !is GridLayoutManager) {
// 从线性布局切换到网格布局
recyclerView.layoutManager = GridLayoutManager(this, 2)
} else if (currentLayoutManager is GridLayoutManager) {
// 从网格布局切换到瀑布流布局
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
} else if (currentLayoutManager is StaggeredGridLayoutManager) {
// 从瀑布流布局切换回线性布局
recyclerView.layoutManager = LinearLayoutManager(this)
} else {
// 默认切换到线性布局
recyclerView.layoutManager = LinearLayoutManager(this)
}
// 更新适配器
recyclerView.adapter = adapter
}
由于条目高度设置的问题,网格布局和瀑布布局的区别可能会不太显眼 。因此,我额外添加了几个条目,用于展示效果
切换后的网格布局:
切换后的瀑布布局(可以看到左边缩上去了,更加紧凑):
四、添加,删除与筛选条目
在完成了基本的条目显示和常用的布局切换功能后,可以尝试实现一些在实际使用中,有利于用户体验的功能(如标题所说)。为了实现这些功能,我添加了添加条目按钮(其实在上一张图就看见了,主要是做完了懒得改代码),删除功能将通过左右滑动条目实现,筛选条目功能则在顶部Search栏中,根据输入的内容动态筛选条目。
在加入添加按钮,搜索框后,activity_main.xml布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/searchEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<Button
android:id="@+id/btnAddItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加条目" />
<Button
android:id="@+id/btnSwitchLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切换布局" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
4.1 添加条目
在MainActivity类的onCreate方法中,加载按钮点击监听器
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ...其他代码...
// 动态添加数据的按钮
findViewById<View>(R.id.btnAddItem).setOnClickListener {
// 弹出对话框让用户输入文本
showInputDialog()
}
}
随后在MainActivity类下添加方法
// 弹出输入框让用户输入新的文本条目
private fun showInputDialog() {
// 创建一个 EditText 控件作为输入框
val inputEditText = EditText(this)
inputEditText.hint = "输入文本..."
// 创建一个 AlertDialog 对话框
val dialog = AlertDialog.Builder(this)
.setTitle("添加新内容")
.setView(inputEditText) // 将 EditText 添加到对话框中
.setPositiveButton("确认") { _, _ ->
val inputText = inputEditText.text.toString().trim() // 获取输入的文本
if (inputText.isNotEmpty()) {
// 如果输入的文本不为空,添加到列表
itemList.add(inputText)
// 通知适配器插入新条目
adapter.notifyItemInserted(itemList.size - 1)
} else {
// 如果输入为空,弹出提示
Toast.makeText(this, "请输入文本", Toast.LENGTH_SHORT).show()
}
}
.setNegativeButton("取消", null) // 取消按钮,关闭对话框
.create()
// 显示对话框
dialog.show()
}
效果如下图:
4.2 删除条目
在MultiTypeAdapter类中添加方法
// 处理删除
fun onItemDismiss(position: Int) {
// 删除items中对应下标的itemView
items.removeAt(position)
// 通知适配器该条目已被删除
notifyItemRemoved(position)
}
在MainActivity类中添加代码:
// 这是为了引入组件
private lateinit var callback:ItemTouchHelperCallback
private lateinit var itemTouchHelper:ItemTouchHelper
在MainActivity类的onCreate方法中添加callback和itemTouchHelper的初始化代码 ,需要注意的是,代码中的adapter是2.2中实例化过的adapter,因此以下代码需要添加在初始化适配器的代码下方,否则会导致App闪退。
//初始化 callback
callback = ItemTouchHelperCallback(adapter)
// 初始化 ItemTouchHelper 绑定callback
itemTouchHelper = ItemTouchHelper(callback)
// 初始化 ItemTouchHelper 绑定的RecycleView
itemTouchHelper.attachToRecyclerView(recyclerView)
编写ItemTouchHelperCallback,在这个类中处理item长按/滑动对应的代码逻辑
package com.example.blog
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
// 自定义 ItemTouchHelper 的回调类,用于处理拖拽排序和滑动删除
class ItemTouchHelperCallback(var adapter: MultiTypeAdapter) : ItemTouchHelper.Callback() {
// 获取 RecyclerView 项目可以执行的动作标志
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
// 设置拖拽方向:可以向上或向下拖动
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
// 设置滑动方向:可以向左或向右滑动
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
// 返回组合的标志:拖拽和滑动
return makeMovementFlags(dragFlags, swipeFlags)
}
// 处理拖拽交换项位置的操作
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// 获取源项和目标项的位置
val fromPosition = source.adapterPosition
val toPosition = target.adapterPosition
// 调用适配器中的方法,执行项的交换
adapter.onItemMove(fromPosition, toPosition)
return true // 返回 true 表示已处理该事件
}
// 处理滑动删除项操作
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// 获取当前被滑动项的位置
val position = viewHolder.adapterPosition
// 调用适配器中的方法,删除该位置的项
adapter.onItemDismiss(position)
// 通知适配器数据已被删除,确保数据同步
adapter.notifyItemRemoved(position)
}
// 选中项时的处理逻辑,修改选中项的透明度来显示拖动状态
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
// 如果状态不是空闲状态(即有拖动或滑动操作)
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
// 修改选中项的透明度,表示该项正在被拖动
viewHolder?.itemView?.alpha = 0.5f
}
}
// 清除选中项时的状态,恢复透明度
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
// 恢复拖动后的透明度
viewHolder.itemView.alpha = 1f
}
// 设置是否启用长按拖动功能
override fun isLongPressDragEnabled(): Boolean {
// 返回 true 表示启用长按拖动
return true
}
// 设置是否启用滑动删除功能
override fun isItemViewSwipeEnabled(): Boolean {
// 返回 true 表示启用滑动删除
return true
}
}
展示效果(有点糊,不打紧)
4.3 筛选条目
在MainActivity类的onCreate方法中,加载文本框以及添加文本框监听器
// 搜索框过滤功能
val searchEditText = findViewById<EditText>(R.id.searchEditText)
searchEditText.addTextChangedListener {
// 获取搜索框的输入内容
val query = it.toString().trim()
// 根据输入的内容过滤列表
filterItems(query)
}
在MainActivity类中添加对应的方法:
// 搜索框内容过滤方法
private fun filterItems(query: String) {
val filteredList = if (query.isEmpty()) {
// 如果搜索框为空,显示所有条目
itemList
} else {
// 否则,根据输入的查询条件过滤条目
itemList.filterTo(mutableListOf()) {
when (it) {
is String -> it.contains(query, ignoreCase = true) // 文本条目匹配查询
is Int -> true // 图片条目不受影响
is Pair<*, *> -> (it.first as? Int)?.let { it1 -> true } ?: false // 图片和文本条目
else -> false
}
}
}
// 更新适配器的数据
adapter.updateData(filteredList)
}
展示效果:
五、完整代码
MainActivity代码
package com.example.blog
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: MultiTypeAdapter
private lateinit var callback:ItemTouchHelperCallback
private lateinit var itemTouchHelper:ItemTouchHelper
private val itemList = mutableListOf<Any>(
"Text Item 1", // 文本条目
"Text Item 2", // 文本条目
android.R.drawable.btn_star_big_on, // 图片条目
Pair(android.R.drawable.btn_star_big_off, "Image and Text Item 1"), // 图片和文本条目
"Text Item 3", // 文本条目
Pair(R.drawable.ic_launcher_background, "Image and Text Item 2") // 图片和文本条目
)
// 在 Activity 创建时调用此方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 获取 RecyclerView 控件
recyclerView = findViewById(R.id.recyclerView)
// 初始化适配器
adapter = MultiTypeAdapter(itemList) { position ->
onItemClicked(position)
}
recyclerView.adapter = adapter
//初始化 callback
callback = ItemTouchHelperCallback(adapter)
// 初始化 ItemTouchHelper 绑定callback
itemTouchHelper = ItemTouchHelper(callback)
// 初始化 ItemTouchHelper 绑定的RecycleView
itemTouchHelper.attachToRecyclerView(recyclerView)
// 设置 RecyclerView 初始布局管理器
recyclerView.layoutManager = LinearLayoutManager(this)
// 动态添加数据的按钮
findViewById<View>(R.id.btnAddItem).setOnClickListener {
// 弹出对话框让用户输入文本
showInputDialog()
}
// 切换布局
findViewById<View>(R.id.btnSwitchLayout).setOnClickListener {
switchLayoutManager()
}
// 搜索框过滤功能
val searchEditText = findViewById<EditText>(R.id.searchEditText)
searchEditText.addTextChangedListener {
// 获取搜索框的输入内容
val query = it.toString().trim()
// 根据输入的内容过滤列表
filterItems(query)
}
}
// 弹出输入框让用户输入新的文本条目
private fun showInputDialog() {
// 创建一个 EditText 控件作为输入框
val inputEditText = EditText(this)
inputEditText.hint = "输入文本..."
// 创建一个 AlertDialog 对话框
val dialog = AlertDialog.Builder(this)
.setTitle("添加新内容")
.setView(inputEditText) // 将 EditText 添加到对话框中
.setPositiveButton("确认") { _, _ ->
val inputText = inputEditText.text.toString().trim() // 获取输入的文本
if (inputText.isNotEmpty()) {
// 如果输入的文本不为空,添加到列表
itemList.add(inputText)
// 通知适配器插入新条目
adapter.notifyItemInserted(itemList.size - 1)
} else {
// 如果输入为空,弹出提示
Toast.makeText(this, "请输入文本", Toast.LENGTH_SHORT).show()
}
}
.setNegativeButton("取消", null) // 取消按钮,关闭对话框
.create()
// 显示对话框
dialog.show()
}
// 切换布局管理器
private fun switchLayoutManager() {
val currentLayoutManager = recyclerView.layoutManager
if (currentLayoutManager is LinearLayoutManager && currentLayoutManager !is GridLayoutManager) {
// 从线性布局切换到网格布局
recyclerView.layoutManager = GridLayoutManager(this, 2)
} else if (currentLayoutManager is GridLayoutManager) {
// 从网格布局切换到瀑布流布局
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
} else if (currentLayoutManager is StaggeredGridLayoutManager) {
// 从瀑布流布局切换回线性布局
recyclerView.layoutManager = LinearLayoutManager(this)
} else {
// 默认切换到线性布局
recyclerView.layoutManager = LinearLayoutManager(this)
}
// 更新适配器
recyclerView.adapter = adapter
}
// 搜索框内容过滤方法
private fun filterItems(query: String) {
val filteredList = if (query.isEmpty()) {
// 如果搜索框为空,显示所有条目
itemList
} else {
// 否则,根据输入的查询条件过滤条目
itemList.filterTo(mutableListOf()) {
when (it) {
is String -> it.contains(query, ignoreCase = true) // 文本条目匹配查询
is Int -> true // 图片条目不受影响
is Pair<*, *> -> (it.first as? Int)?.let { it1 -> true } ?: false // 图片和文本条目
else -> false
}
}
}
// 更新适配器的数据
adapter.updateData(filteredList)
}
// 点击 RecyclerView 项目时的处理逻辑
private fun onItemClicked(position: Int) {
val item = itemList[position]
Toast.makeText(this, "Item clicked: $item", Toast.LENGTH_SHORT).show()
}
}
MultiTypeAdapter代码:
package com.example.blog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.blog.R
class MultiTypeAdapter(
private var items: MutableList<Any>,
private val onItemClicked: (Int) -> Unit // 接收点击事件的回调
) :RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// 定义条目的类型常量
companion object {
const val TYPE_TEXT = 0 // 文本条目类型
const val TYPE_IMAGE = 1 // 图片条目类型
const val TYPE_IMAGE_TEXT = 2 // 混合条目类型
}
// 创建 ViewHolder 的方法,根据条目类型加载不同的布局
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
// 如果条目类型是 TYPE_TEXT,加载文本条目的布局
TYPE_TEXT -> TextViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false))
// 如果条目类型是 TYPE_IMAGE,加载图片条目的布局
TYPE_IMAGE -> ImageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false))
// 如果条目类型是 TYPE_IMAGE_TEXT,加载混合条目条目的布局
TYPE_IMAGE_TEXT -> ImageTextViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_image_text, parent, false))
// 如果条目类型无效,抛出异常
else -> throw IllegalArgumentException("Invalid view type")
}
}
// 绑定数据到 ViewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// 根据当前条目的类型,进行相应的数据绑定
when (getItemViewType(position)) {
// 如果是文本条目,绑定文本数据
TYPE_TEXT -> (holder as TextViewHolder).bind(items[position] as String)
// 如果是图片条目,绑定图片资源 ID
TYPE_IMAGE -> (holder as ImageViewHolder).bind(items[position] as Int)
// 如果是混合条目,绑定图片资源和文本数据
TYPE_IMAGE_TEXT -> (holder as ImageTextViewHolder).bind(items[position] as Pair<Int, String>)
}
// 设置点击事件
holder.itemView.setOnClickListener {
onItemClicked(position) // 调用传递的回调函数
}
}
// 返回列表项的数量
override fun getItemCount(): Int = items.size
// 根据当前位置返回条目的类型
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
// 如果是字符串类型,返回文本条目类型
is String -> TYPE_TEXT
// 如果是整数类型,返回图片条目类型
is Int -> TYPE_IMAGE
// 如果是 Pair 类型(包含图片和文本),混合条目类型
is Pair<*, *> -> TYPE_IMAGE_TEXT
// 如果条目类型无效,抛出异常
else -> throw IllegalArgumentException("Invalid item type")
}
}
// 处理拖拽排序
fun onItemMove(fromPosition: Int, toPosition: Int) {
val movedItem = items.removeAt(fromPosition)
items.add(toPosition, movedItem)
notifyItemMoved(fromPosition, toPosition)
}
// 处理删除
fun onItemDismiss(position: Int) {
// 删除items中对应下标的itemView
items.removeAt(position)
// 通知适配器该条目已被删除
notifyItemRemoved(position)
}
// 文本条目的 ViewHolder,负责绑定文本数据
class TextViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val textView: TextView = view.findViewById(R.id.textView)
// 绑定文本数据
fun bind(text: String) {
textView.text = text
}
}
// 图片条目的 ViewHolder,负责绑定图片资源
class ImageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView: ImageView = view.findViewById(R.id.imageView)
// 绑定图片资源
fun bind(imageRes: Int) {
imageView.setImageResource(imageRes)
}
}
// 图片在左,文本在右条目的 ViewHolder,负责绑定图片资源和文本数据
class ImageTextViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView: ImageView = view.findViewById(R.id.imageViewLeft)
private val textView: TextView = view.findViewById(R.id.textViewRight)
// 绑定图片资源和文本数据
fun bind(item: Pair<Int, String>) {
imageView.setImageResource(item.first)
textView.text = item.second
}
}
// 更新数据并通知适配器刷新
fun updateData(newItems: MutableList<Any>) {
items = newItems
// 或者使用 notifyItemInserted, notifyItemRemoved 来优化性能
notifyDataSetChanged()
}
}
ItemTouchHelpCallback代码
package com.example.blog
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
// 自定义 ItemTouchHelper 的回调类,用于处理拖拽排序和滑动删除
class ItemTouchHelperCallback(var adapter: MultiTypeAdapter) : ItemTouchHelper.Callback() {
// 获取 RecyclerView 项目可以执行的动作标志
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
// 设置拖拽方向:可以向上或向下拖动
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
// 设置滑动方向:可以向左或向右滑动
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
// 返回组合的标志:拖拽和滑动
return makeMovementFlags(dragFlags, swipeFlags)
}
// 处理滑动删除项操作
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// 获取当前被滑动项的位置
val position = viewHolder.adapterPosition
// 调用适配器中的方法,删除该位置的项
adapter.onItemDismiss(position)
// 通知适配器数据已被删除,确保数据同步
adapter.notifyItemRemoved(position)
}
// 选中项时的处理逻辑,修改选中项的透明度来显示拖动状态
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
// 如果状态不是空闲状态(即有拖动或滑动操作)
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
// 修改选中项的透明度,表示该项正在被拖动
viewHolder?.itemView?.alpha = 0.5f
}
}
// 清除选中项时的状态,恢复透明度
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
// 恢复拖动后的透明度
viewHolder.itemView.alpha = 1f
}
// 设置是否启用长按拖动功能
override fun isLongPressDragEnabled(): Boolean {
// 返回 true 表示启用长按拖动
return true
}
// 设置是否启用滑动删除功能
override fun isItemViewSwipeEnabled(): Boolean {
// 返回 true 表示启用滑动删除
return true
}
}