简单学习Android RecyclerView:从基本使用到简单功能的实现

目录

前言

一、RecyclerView 基础

1.1 RecyclerView 是什么?

1.2 为什么选择 RecyclerView 而不是 ListView?

1.3 关键组件

1.4 布局文件的基本配置

二、实现多类型条目

2.1 多类型条目的需求

2.2 多类型条目的实现

数据结构

Adapter 实现

MainActivity实现

三、动态布局切换

四、添加,删除与筛选条目

4.1 添加条目

4.2 删除条目

4.3 筛选条目

五、完整代码


前言

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)图片在左,文本在右的条目

这种布局需要我们设计一个包含 ImageViewTextView 的布局文件。创建一个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
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值