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

Android入门到实战(八):从发现页到详情页——跳转、传值与RecyclerView多类型布局


一. 引言

在上一篇文章里,我们从零开始实现了 App 的 发现页面,通过网络请求获取数据,并使用 RecyclerView 展示了剧集列表。

但光有发现页还不够,用户在点击一部剧时,自然希望进入到一个更详细的页面,去查看它的简介、标签以及剧集列表。本篇我们就来实现 发现详情页

主要包含以下内容:

  1. 从发现页跳转到详情页(Activity 跳转与传值)
  2. 详情页的 UI 布局(背景、Toolbar、RecyclerView)
  3. RecyclerView 多类型布局(头部 + 剧集列表)
  4. ViewModel + LiveData 数据驱动(自动刷新 UI)

通过这一篇,你将掌握 Android 开发中常见的“跳转 → 数据传递 → 多类型列表 → 数据绑定”的完整流程。

二. 从发现页跳转到详情页

2.1 发送跳转

在发现页的 Adapter 中,我们可以为每一个剧集的 Item 添加点击事件,然后通过 Intent 启动 DiscoverDetailActivity,并把 DiscoverDrama 对象传递过去:

val intent = Intent(context, DiscoverDetailActivity::class.java)
intent.putExtra("drama", drama) // drama 是 DiscoverDrama 类型
context.startActivity(intent)

这里我们用到了 putExtra,因为 DiscoverDrama 已经实现了 Serializable,所以可以直接传递。

2.2 接收参数

在 DiscoverDetailActivity 中,通过 intent.getSerializableExtra 来接收数据:

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun initData() {discoverDrama = intent.getSerializableExtra("drama", DiscoverDrama::class.java)
}

这样我们就能在详情页中拿到用户点击的剧集信息,并用于后续的 UI 展示和数据请求。

三. 详情页整体布局概览

在详情页,我们主要分为三个部分:

1. 背景与 Toolbar

  • 页面顶部是一个渐变背景 (View) 和透明的 MaterialToolbar,用于展示标题“剧集详情”。
  • 使用 enableEdgeToEdge() 和 WindowInsetsCompat 处理状态栏高度,让内容贴合屏幕边缘。

2. RecyclerView

占据主体区域,用于展示两类内容:

  1. 头部信息:封面、标题、描述、标签、词汇量
  2. 剧集列表:每一集的标题、文件大小、下载状态等

3. 布局特点

  • RecyclerView 采用 LinearLayoutManager 垂直排列。
  • 头部视图与列表项通过 Adapter 的 getItemViewType 区分,实现多类型布局。
  • 数据完全通过 ViewModel + LiveData 绑定到 RecyclerView,无需在 Activity 中手动更新视图。

这种布局方式简洁而高效,既能展示剧集的详细信息,也便于扩展后续功能(例如下载按钮或播放按钮)。

四. RecyclerView 多类型布局实现

发现详情页中,我们的 RecyclerView 既要展示 头部信息,又要展示 剧集列表。为此,我们采用 多类型布局的方式,实现两类 ViewHolder:

4.1 Adapter 设计
class DiscoverDetailAdapter(private val discoverDrama: DiscoverDrama
): RecyclerView.Adapter<RecyclerView.ViewHolder>() {companion object {const val TYPE_HEADER = 0const val TYPE_CONTENT = 1}private var episodes: List<DiscoverEpisode> = emptyList()override fun getItemViewType(position: Int): Int {return if (position == 0) TYPE_HEADER else TYPE_CONTENT}override fun getItemCount(): Int = episodes.size + 1
}

  • 第一个位置 (position == 0) 是 头部视图
  • 其余位置为 剧集列表
  • getItemCount() 返回 episodes.size + 1,因为头部占一行

4.2 ViewHolder 绑定数据

头部视图 (HeaderViewHolder)

  • 显示剧封面、标题、描述、标签、词汇量
  • 使用 Glide 加载封面图片
  • 标签动态生成 TextView 并添加到 LinearLayout
class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {private val cover = itemView.findViewById<ImageView>(R.id.ivCover)private val title = itemView.findViewById<TextView>(R.id.tvTitle)private val desc = itemView.findViewById<TextView>(R.id.tvDesc)private val wordCount = itemView.findViewById<TextView>(R.id.tvVocab)private val tagContainer = itemView.findViewById<LinearLayout>(R.id.tagContainer)fun bindData(drama: DiscoverDrama) {Glide.with(itemView.context).load(drama.realCoverUrl).into(cover)title.text = drama.titledesc.text = drama.descriptionwordCount.text = "词汇量: ${drama.vocabularyCount ?: 0}"tagContainer.removeAllViews()drama.tags?.split(",")?.forEach { tag ->val tv = TextView(itemView.context).apply {text = tag// 背景、圆角、透明度等样式}tagContainer.addView(tv)}}
}

剧集列表视图 (EpisodeViewHolder)

  • 显示剧集标题、文件大小、下载状态
  • 预留下载逻辑和进度条
class EpisodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {private val title: TextView = itemView.findViewById(R.id.episodeTitle)private val size: TextView = itemView.findViewById(R.id.episodeSize)private val statusIcon: ImageView = itemView.findViewById(R.id.statusIcon)private val statusProgress: ProgressBar = itemView.findViewById(R.id.statusProgress)fun bindData(episode: DiscoverEpisode) {title.text = "${episode.index}. ${episode.title}"size.text = episode.fileSize ?: ""// 下载状态逻辑可在此扩展}
}

4.3 数据更新

  • 通过 ViewModel 获取剧集列表数据
  • 使用 LiveData 观察数据变化,并调用 Adapter 的 setEpisodes() 更新 RecyclerView
viewModel.episodes.observe(this) { episodes ->adapter.setEpisodes(episodes)
}

这样实现了 Activity 不直接操作 RecyclerView 的思想,保证了 UI 与数据的分离。

五. 数据获取与绑定流程

在详情页中,剧集列表的数据来源于网络请求。为了实现 UI 与数据分离,我们采用 ViewModel + LiveData 的方式管理数据。

5.1 ViewModel 请求数据

DiscoverDetailViewModel 负责请求剧集列表,并将结果通过 LiveData 暴露给 UI:

class DiscoverDetailViewModel : ViewModel() {val episodes = MutableLiveData<List<DiscoverEpisode>>()val isLoading = MutableLiveData<Boolean>()private val discoverDramaRepository by lazy { DiscoverRespository() }fun fetchEpisodes(drama: DiscoverDrama) {viewModelScope.launch {isLoading.value = trueval result = discoverDramaRepository.fetchEpisodes(drama)result.onSuccess {println("获取剧集 ${drama.title} 的集列表成功: ${it.size} 条数据")episodes.value = it}.onFailure {episodes.value = emptyList()}isLoading.value = false}}
}

  • viewModelScope.launch 在协程中发起网络请求,保证不会阻塞 UI 线程
  • 成功时,将数据赋值给 episodes LiveData
  • 失败时,清空列表,保证 RecyclerView 安全更新

5.2 Activity 观察数据

在 DiscoverDetailActivity 中,RecyclerView Adapter 不直接请求数据,而是 观察 LiveData

viewModel.episodes.observe(this) { episodes ->adapter.setEpisodes(episodes)Log.d("DiscoverDetailActivity", "Episodes updated: ${episodes.size} items")
}
  • 当 LiveData 更新时,Adapter 自动刷新 RecyclerView
  • Activity 只负责 UI 初始化和 LiveData 绑定,无需手动刷新列表

5.3 请求与展示流程总结
  1. Activity 启动后,通过 Intent 获取 DiscoverDrama 参数
  2. 调用 viewModel.fetchEpisodes(drama) 发起网络请求
  3. ViewModel 请求成功后,将数据赋值给 LiveData
  4. Activity 观察 LiveData,并将数据传递给 Adapter
  5. Adapter 更新 RecyclerView,实现 UI 自动刷新

六.运行效果与总结

6.1 最终效果展示
  • 用户在 发现页面 点击某部剧集
  • 页面跳转到 详情页
  • 页面顶部展示剧的封面、标题、描述、标签和词汇量
  • 下方 RecyclerView 展示剧集列表,每一集显示标题、文件大小和下载状态(可扩展)
  • UI 完全响应 LiveData 数据更新,无需手动刷新

6.2 本篇收获

通过这一篇文章,我们掌握了:

Activity 跳转与参数传递

  • 使用 Intent 传递 Serializable 对象
  • 在目标 Activity 中安全接收数据

RecyclerView 多类型布局

  • 头部视图 + 列表视图
  • Adapter 分类型管理 ViewHolder

ViewModel + LiveData 数据驱动 UI

  • Activity 不直接操作数据
  • RecyclerView 自动响应数据变化

这种模式不仅使代码清晰、可维护,还符合 Android 架构最佳实践。

http://www.xdnf.cn/news/20147.html

相关文章:

  • 深度学习——ResNet 卷积神经网络
  • Python快速入门专业版(二):print 函数深度解析:不止于打印字符串(含10+实用案例)
  • Docker多阶段构建Maven项目
  • K8s资源管理:高效管控CPU与内存
  • React学习之路永无止境:下一步,去向何方?
  • Jmeter基础教程详解
  • STM32H750 RTC介绍及应用
  • 国产GEO工具哪家强?巨推集团、SEO研究协会网、业界科技三强对比
  • 用C++实现日期类
  • upload-labs通关笔记-第17关文件上传关卡之二次渲染jpg格式
  • 关于如何在PostgreSQL中调整数据库参数和配置的综合指南
  • Vue基础知识-脚手架开发-子传父(props回调函数实现和自定义事件实现)
  • Win11 解决访问网站525 问题 .
  • 【RK3576】【Android14】如何在Android kernel-6.1 的版本中添加一个ko驱动并编译出来?
  • Django 常用功能完全指南:从核心基础到高级实战
  • [光学原理与应用-401]:设计 - 深紫外皮秒脉冲激光器 - 元件 - 布拉格衍射在深紫外皮秒声光调制器(AOM)中的核心作用与系统实现
  • 小程序:12亿用户的入口,企业数字化的先锋军
  • Linux编程——网络编程(UDP)
  • 计算机网络模型入门指南:分层原理与各层作用
  • 对接旅游行业安全需求:旅游安全急救实训室的功能构建与育人目标
  • 网络安全初级-渗透测试
  • 用AI做TikTok影视解说,全流程全自动成片,不懂外语也能做全球矩阵!
  • 办公任务分发项目 laravel vue mysql 第一章:核心功能构建 API
  • 系统越拆越乱?你可能误解了微服务的本质!
  • 【Linux系统】线程同步
  • 正则表达式与转义符的区别。注意输入的东西经过了一次转义,一次正则表达式。\\转义是单斜杠\\在正则表达式也是单斜杠所以\\\\经过两道门才是字符单斜杠
  • MongoDB Change Streams:实时监听数据变化的实战场景
  • clickhouse迁移工具clickhouse-copier
  • Python EXCEL 小技巧:最快重新排列dataframe函数
  • 工业机器人标杆的数字化突围,珞石机器人如何以CRM实现业务重塑