当前位置: 首页 > news >正文

Android UI 组件系列(十二):RecyclerView 嵌套及点击事件

博客专栏:Android初级入门UI组件与布局

源码:通过网盘分享的文件:Android入门布局及UI相关案例

链接: https://pan.baidu.com/s/1EOuDUKJndMISolieFSvXXg?pwd=4k9n 提取码: 4k9n

引言

在前两篇中,我们已经深入掌握了 RecyclerView 的基础使用方法,以及如何通过多类型 ViewHolder 实现复杂布局和数据刷新。这些能力,已经可以覆盖 80% 的列表需求。

但当页面结构进一步复杂,比如首页常见的「顶部横向 Banner + 底部瀑布流列表」,或者内容卡片中又嵌套横向滑动项时,普通的多类型布局已经难以胜任。这时候,我们就需要掌握 RecyclerView 的高级技能之一——嵌套 RecyclerView

此外,在列表中添加“收藏”“分享”这类操作按钮时,事件传递逻辑也变得更加复杂。如果事件处理方式混乱,极易导致点击无响应、状态不同步等问题。

因此,本篇作为 RecyclerView 实战系列的第 3 篇,将聚焦两个关键点:

  • ✅ 嵌套 RecyclerView 的实现方式与注意事项(例如横向列表嵌套、SpanSize 跨度控制等);
  • ✅ 列表项与子控件的点击事件设计与传递(包括 Adapter 到 Activity 的回调封装)。

通过一个实际的 UI Demo,我们将手把手实现如下结构:

[横向 Banner 区域] —— RecyclerView 横向嵌套
[推荐直播] 标题 —— TextView
[直播卡片1] [直播卡片2]
[直播卡片3] [直播卡片4]
...

每个直播卡片都支持点击整卡 & 收藏按钮。所有点击事件都将通过清晰的接口方式传回到 Activity 层,确保代码解耦、职责明确。

一、嵌套 RecyclerView 的常见场景与实现方式

1.1 为什么要用嵌套 RecyclerView?

在实际项目中,有很多页面需要展示不同方向或不同布局的列表组合,例如:

  • 首页顶部的 横向 Banner
  • 分类页中的 横向子频道
  • 内容详情页中嵌套的 相关推荐 区域

这些结构具有一个共同点:

需要在纵向列表中嵌套横向列表

如果强行使用一个 RecyclerView 来“拼接出横向滑动效果”,不仅实现复杂,复用性差,还容易出错。而 RecyclerView 支持子项再嵌套 RecyclerView,恰好就是官方推荐的做法。

1.2 Demo 页面结构说明

在本篇的实战中,我们设计了如下页面结构:

纵向主 RecyclerView(GridLayoutManager)
├── item 0: 横向 Banner(嵌套 RecyclerView)
├── item 1: 标题项(TextView)
├── item 2-N: 直播项(每行两个,瀑布流效果)

1.3 技术实现要点

✅ 1. 外层使用 GridLayoutManager 实现瀑布流布局

我们希望直播卡片每行两个,而 Banner 与标题各占一整行,所以采用:

        //3.设置LayoutManagerval layoutManager = GridLayoutManager(this, 2)layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {override fun getSpanSize(position: Int): Int {return when (mainAdapter.getItemViewType(position)) {MainAdapter.TYPE_LIVE -> 1else -> 2}}}

✅ 2. 横向 Banner 区域使用嵌套 RecyclerView

Banner 区域的布局本身就是一个 item,内部包含一个横向 RecyclerView:

<androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/bannerRecyclerView"android:layout_width="match_parent"android:layout_height="180dp"android:orientation="horizontal"android:clipToPadding="false" />

Adapter 中正常为其设置横向 LinearLayoutManager 和数据源即可。

   inner class BannerHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {private val bannerRecyclerView: RecyclerView = itemView.findViewById(R.id.bannerRecyclerView)fun bind() {bannerRecyclerView.layoutManager =LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)val bannerItems = listOf(BannerItem(R.drawable.placeholder_banner),BannerItem(R.drawable.placeholder_banner))bannerRecyclerView.adapter = BannerAdapter(bannerItems) { item,index ->Toast.makeText(itemView.context,"点击了 Banner 第 ${index + 1} 张图:${item.imageResId}",Toast.LENGTH_SHORT).show()}}}

1.4 实战实现:一步步搭建嵌套 RecyclerView

整个实现流程分为四步:

✅ 第一步:布局文件准备(XML)

我们需要如下布局文件来支撑不同类型的 item:

文件名

用途

activity_main.xml

主界面,包含主 RecyclerView

item_banner.xml

横向嵌套 RecyclerView 区域

item_banner_image.xml

横向列表中每个 Banner 图

item_title.xml

列表中的标题项

item_live.xml

每个直播项(卡片 + 收藏按钮)

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/mainRecyclerView"android:layout_width="match_parent"android:layout_height="match_parent"android:padding="8dp"android:clipToPadding="false" />

item_banner.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/bannerRecyclerView"android:layout_width="match_parent"android:layout_height="180dp"android:orientation="horizontal"android:paddingStart="8dp"android:paddingEnd="8dp"android:clipToPadding="false" />

item_banner_image.xml:

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/bannerImage"android:layout_width="300dp"android:layout_height="match_parent"android:scaleType="fitCenter"android:layout_marginEnd="12dp"android:background="#DDD" />

item_title.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/titleText"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="推荐直播"android:textSize="18sp"android:textStyle="bold"android:padding="12dp"android:textColor="#222" />

item_live.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="vertical"android:layout_margin="6dp"android:background="#FFF"android:elevation="2dp"android:padding="6dp"><ImageViewandroid:id="@+id/coverImage"android:layout_width="match_parent"android:layout_height="180dp"android:scaleType="centerCrop"android:background="#CCC" /><TextViewandroid:id="@+id/liveTitle"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="直播标题"android:textSize="14sp"android:textColor="#333"android:paddingTop="6dp"android:maxLines="2" /><Buttonandroid:id="@+id/btnFavorite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="收藏"android:layout_marginTop="4dp"android:textSize="12sp"/>
</LinearLayout>

✅ 第二步:数据模型设计(Model)

在多类型列表中,合理的数据模型设计可以大大简化后续的 Adapter 编写与类型判断逻辑。本项目中,我们将页面中的三类内容抽象成三个模型对象:

📌 1. BannerItem —— 横向 Banner 区域的图片模型

data class BannerItem(val imageResId: Int // 使用本地资源 ID 显示图片
)

📌 2. LiveItem —— 直播列表的单个卡片模型

data class LiveItem(val coverResId: Int,  // 封面图资源val title: String,    // 标题val isFavorite: Boolean // 是否收藏
)

📌 3. MainListItem —— 用于主 RecyclerView 的多类型封装

sealed class MainListItem {object Banner : MainListItem()data class Title(val text: String) : MainListItem()data class Live(val liveItem: LiveItem) : MainListItem()
}

MainListItem 使用 Kotlin 的 sealed class 特性,将不同类型的 item 统一封装,后续在 Adapter 中可以通过 when(item) 结构轻松判断当前 item 类型。

✅ 第三步:Adapter 与 ViewHolder 实现

通过合理划分 ViewType、使用 sealed class 管理数据模型,我们可以非常清晰地实现一个嵌套 + 多类型列表。

横向 Banner 区域的 BannerAdapter

BannerAdapter 是一个标准的 RecyclerView Adapter,用于渲染横向滚动的图片项。

class BannerAdapter(private val data: List<BannerItem>,private val onClick: (BannerItem, Int) -> Unit
) : RecyclerView.Adapter<BannerAdapter.BannerViewHolder>() {...
}

主列表 Adapter:MainAdapter

MainAdapter 是整个页面最关键的部分,它支持三种类型的 ViewHolder:

  • BannerHolder:嵌套横向 RecyclerView
  • TitleHolder:分组标题项
  • LiveHolder:直播卡片(带收藏按钮)

通过 getItemViewType() 方法,我们将 MainListItem 映射为不同的布局类型:

override fun getItemViewType(position: Int): Int {return when (items[position]) {is MainListItem.Banner -> TYPE_BANNERis MainListItem.Title -> TYPE_TITLEis MainListItem.Live -> TYPE_LIVE}
}

并在 onCreateViewHolder() 中使用 LayoutInflater 加载对应的布局:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {return when (viewType) {TYPE_BANNER -> BannerHolder(...)TYPE_TITLE -> TitleHolder(...)TYPE_LIVE -> LiveHolder(...)else -> ...}
}

BannerHolder:实现嵌套横向 RecyclerView

inner class BannerHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind() {bannerRecyclerView.layoutManager =LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)...}
}

TitleHolder:展示分组标题

inner class TitleHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(title: MainListItem.Title) {itemView.findViewById<TextView>(R.id.titleText).text = title.text}
}

LiveHolder:展示直播卡片 + 收藏按钮点击事件

inner class LiveHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(item: LiveItem) {...btn.setOnClickListener { onFavoriteClick(item) }itemView.setOnClickListener { onLiveClick(item) }}
}
✅ 第四步:Activity 页面搭建与嵌套列表初始化

完成 Adapter 之后,我们就可以在 MainActivity 中将所有内容串联起来,真正跑起来嵌套 RecyclerView 的整体布局。

MainActivity 中的核心初始化逻辑集中在一个独立的 setupRecyclerView() 方法中,包括:

  1. 构建数据源;
  2. 初始化 Adapter;
  3. 设置 LayoutManager;
  4. 将 Adapter 应用于 RecyclerView。

构建数据源

val list = mutableListOf<MainListItem>()
list.add(MainListItem.Banner)
list.add(MainListItem.Title("推荐直播"))val liveList = listOf(LiveItem(R.drawable.placeholder_live, "小王正在唱歌", false),LiveItem(R.drawable.placeholder_live, "一起看电影", true),LiveItem(R.drawable.placeholder_live, "画画直播", false),LiveItem(R.drawable.placeholder_live, "夜宵时间", false)
)
liveList.forEach {list.add(MainListItem.Live(it))
}

设置 GridLayoutManager 实现混排样式

因为直播卡片要每行两个,而 Banner 和标题要单独占一行,所以我们使用了 GridLayoutManager 并结合 spanSizeLookup 精准控制每种 item 的占列数量:

val layoutManager = GridLayoutManager(this, 2)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {override fun getSpanSize(position: Int): Int {return when (mainAdapter.getItemViewType(position)) {MainAdapter.TYPE_LIVE -> 1  // 直播项每行两个else -> 2                   // 其他项占满整行}}
}

应用 Adapter 和 LayoutManager

mainRecyclerView.layoutManager = layoutManager
mainRecyclerView.adapter = mainAdapter

二、点击事件的实现与事件传递

在列表开发中,点击事件的处理往往被低估了它的复杂度。一旦你的 UI 中既有整卡点击,又有子控件点击(比如收藏按钮),再加上 Adapter 和 ViewHolder 分离、数据结构复杂等问题,事件传递就极易变得混乱。

本节我们将通过实际代码,分步骤讲解点击事件从 ViewHolder → Adapter → Activity 的完整传递流程,并给出一套清晰的实现模式。

2.1 点击事件的需求分析

我们要实现的点击交互包括:

点击位置

事件行为

直播卡片整体

跳转或提示直播信息

直播卡片中的收藏按钮

切换收藏状态(或提示)

Banner 图片

弹出点击了第几张 Banner

2.2 事件处理的基本思路

为了让事件能从 ViewHolder 被传递到外层的 Activity,我们采用“回调函数”的方式,让 Adapter 接收一个 lambda 参数,负责将点击事件抛给外部处理。

class MainAdapter(private val items: List<MainListItem>,private val onLiveClick: (LiveItem) -> Unit,private val onFavoriteClick: (LiveItem) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {...
}

通过这种方式,我们在 Activity 中传入事件处理逻辑,而 Adapter 和 ViewHolder 仅负责绑定和回调,不关心点击之后要干什么,从而达到了职责分离

2.3 Live 项点击事件实现

✅ ViewHolder 内部设置点击回调

inner class LiveHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(item: LiveItem) {...btn.setOnClickListener { onFavoriteClick(item) }itemView.setOnClickListener { onLiveClick(item) }}
}

这样一来:

  • 用户点击整卡时,调用 onLiveClick(item)
  • 用户点击“收藏”按钮时,调用 onFavoriteClick(item)

✅ Adapter 构造时传入事件处理逻辑

mainAdapter = MainAdapter(items = list,onLiveClick = { item ->Toast.makeText(this, "点击直播:${item.title}", Toast.LENGTH_SHORT).show()},onFavoriteClick = { item ->Toast.makeText(this, "点击收藏:${item.title}", Toast.LENGTH_SHORT).show()}
)

2.4 Banner 区域点击事件实现

Banner 属于嵌套 RecyclerView 的 item,我们在 BannerAdapter 中使用类似方式处理点击:

class BannerAdapter(private val data: List<BannerItem>,private val onClick: (BannerItem, Int) -> Unit
)

✅ BannerAdapter 的 onBindViewHolder:

override fun onBindViewHolder(holder: BannerViewHolder, position: Int) {val item = data[position]holder.imageView.setImageResource(item.imageResId)holder.itemView.setOnClickListener {onClick(item, position)}
}

✅ 在 BannerHolder 中使用:

bannerRecyclerView.adapter = BannerAdapter(bannerItems) { item, index ->Toast.makeText(itemView.context,"点击了 Banner 第 ${index + 1} 张图",Toast.LENGTH_SHORT).show()
}

结语

RecyclerView 是 Android 中最灵活也最常用的 UI 组件之一,而嵌套列表与点击事件正是它“进阶玩法”的核心。本篇我们通过一个完整的实战 Demo,从页面布局、数据模型、Adapter 多类型处理,到 RecyclerView 的嵌套实现与点击事件回调,逐步构建出一个具备真实场景意义的页面结构。

通过这个案例,你应该已经掌握:

  • 如何在 RecyclerView 中优雅地嵌套另一个 RecyclerView(如横向 Banner);
  • 如何利用 GridLayoutManager 的 spanSizeLookup 实现复杂的布局混排;
  • 如何通过 lambda 回调的方式,从 ViewHolder 将点击事件向上传递至 Activity 层;
  • 如何设计结构清晰、可扩展的数据模型与多类型 Adapter。

在实际项目中,这类嵌套 + 多事件交互的页面非常常见,比如内容首页、电商推荐页、发现页等,掌握这一套设计模式后,你可以更从容地应对各类复杂列表页面的挑战。

至此,RecyclerView 实战系列的第三篇就结束啦。如果你还没读前两篇,也欢迎回顾:

  • 第一篇:RecyclerView 的基础使用

  • 第二篇:RecyclerView 多类型布局与数据刷新实战

http://www.lryc.cn/news/611963.html

相关文章:

  • git 清理submodule
  • 每日算法刷题Day57:8.6:leetcode 单调栈6道题,用时2h
  • K8S、Docker安全漏洞靶场
  • 实战 Seata:实现分布式事务解决方案
  • ORACLE进阶操作
  • 在NVIDIA Orin上用TensorRT对YOLO12进行多路加速并行推理时内存泄漏
  • 完整的登陆学生管理系统(配置数据库)
  • 电商支付异常测试全攻略
  • 013 HTTP篇
  • 秋招笔记-8.6
  • eclipse2023创建工作集
  • 使用python与streamlit构建的空间微生物分析
  • harbor仓库搭建(配置https)
  • 虚幻GAS底层原理解剖五 (AS)
  • 常见的大模型分类
  • #3:Maven进阶与私服搭建
  • 面试问题11
  • 用html写一个类似于postman可以发送请求
  • PyCharm vs. VSCode 到底哪个更好用
  • 面试题:基础的sql命令
  • 使用Nginx部署前后端分离项目
  • AS32S601 芯片 ADC 模块交流耦合测试:技术要点与实践
  • 大前端游戏应用中 AI 角色行为智能控制
  • AdGuard 安卓修改版:全方位广告拦截与隐私保护专家
  • webrtc弱网-OveruseFrameDetector源码分析与算法原理
  • Template 显式实例化 隐式实例化
  • C++之vector类的代码及其逻辑详解 (下)
  • java学习 leetcode24交换链表节点 200岛屿数量 +一些开发任务
  • win10/11网络防火墙阻止网络连接?【图文详解】防火墙阻止连接网络的解决方法
  • 最新教程 | CentOS 7 下 MySQL 8 离线部署完整手册(含自动部署脚本)