一. 引言

在上一篇文章里,我们从零开始实现了 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.pswp.cn/bicheng/96166.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96166.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/96166.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工具】41K star!网页一键变桌面应用

项目中遇到了一个需要将现有的 web 页面打包成一个 桌面应用 的需求。 最一开始想到的是 Electron&#xff0c;但是它还需要一些开发工作并且打包后的应用体积比较大&#xff0c;调研后发现了开源工具 Pake。 它能让你用最轻量的方式&#xff0c;把任何网页一键打包成跨平台桌…

浪潮CD1000-移动云电脑-RK3528芯片-2+32G-安卓9-2种开启ADB ROOT刷机教程方法

浪潮CD1000-移动云电脑-RK3528芯片-232G-安卓9-2种开启ADB ROOT刷机教程方法 往期文章&#xff1a; 浪潮CD1000-移动云电脑-RK3528芯片-232G-安卓9-开启ADB ROOT破解教程 地址1&#xff1a;浪潮CD1000-移动云电脑-RK3528芯片-232G-开启ADB ROOT破解教程-CSDN博客 中国移动浪潮…

Day23_【机器学习—聚类算法—K-Means聚类 及评估指标SSE、SC、CH】

一、聚类算法概念属于无监督学习算法&#xff0c;即有特征无标签&#xff0c;根据样本之间的相似性&#xff0c;将样本划分到不同的类别中。所谓相似性可以理解为欧氏距离、曼哈顿距离、切比雪夫距离... 。分类按颗粒度分为&#xff1a;粗聚类、细聚类。按实现方法分为&#xf…

android seekbar显示刻度

SeekBar简介 SeekBar是Android中的一个可交互UI组件&#xff0c;允许用户通过拖动滑块在特定范围内选择数值。继承自ProgressBar&#xff0c;但增加了用户手动调节功能&#xff0c;常用于音量控制、亮度调节等场景。 核心属性 android:maxHeight // 背景高度 android:progres…

【高并发内存池】五、页缓存的设计

文章目录Ⅰ. page cache页缓存的结构设计Ⅱ. 完善central cache中的 get_span() 函数Ⅲ. 实现页缓存获取span对象的接口Ⅰ. page cache页缓存的结构设计 ​ 首先页缓存还是一个哈希桶的结构&#xff0c;但是和前两者不同的是&#xff0c;页缓存的哈希桶中存放的是一个或者多个…

Elasticsearch(text和keyword)区别分析

text:全文检索类型,经过分词处理,支持模糊匹配‌ keyword:精确匹配类型,适用于聚合、排序和过滤‌ text 1. 核心属性 ‌analyzer属性‌: 指定用于索引和搜索的分词器 默认使用标准分析器(Standard Analyzer) 示例:"analyzer": "ik_max_word"(中文…

通过tailscale实现一台电脑上vscode通过ssh连接另一台电脑上的VMware Linux 虚拟机

当需要通过一台windows电脑上的vscode来ssh连接另一台电脑上的linux虚拟机进行远程操作&#xff0c;可以通过tailscale来实现。 Linux虚拟机上安装tailscale 由于挂代理下载仍然很慢&#xff0c;而清华镜像源又没有tailscale的软件包&#xff0c;所以可以通过下载 DEB 包安装…

[Upscayl图像增强] docs | 前端 | Electron工具(web->app)

链接&#xff1a;https://upscayl.org/docs&#xff1a;Upscayl Upscayl是一款桌面应用程序&#xff0c;允许用户使用人工智能放大和增强图像。 提供了一个用户友好的图形界面&#xff08;渲染器用户界面&#xff09;&#xff0c;用户可以选择图像或文件夹&#xff0c;从多种AI…

阿里云通义MoE全局均衡技术:突破专家负载失衡的革新之道

MoE模型的基本原理与核心价值 混合专家模型&#xff08;Mixture of Experts&#xff0c;MoE&#xff09;是当前AI大模型领域最重要的架构创新之一&#xff0c;其核心思想是通过多个“专家”网络协同处理输入数据&#xff0c;并由门控网络动态选择或组合各个专家的输出&#xf…

macOS中设置环境变量的各文件及作用域

在 macOS 中&#xff0c;~/.zshrc 和 ~/.bash_profile 是 Shell 的配置文件&#xff0c;用于设置环境变量、命令别名、启动命令等。它们在你每次打开终端时会被自动加载。文件对应 Shell作用~/.zshrcZsh&#xff08;macOS Catalina 及以后默认&#xff09;每次打开新的终端窗口…

【华为培训笔记】OptiX OSN 9600 设备保护专题

OptiX OSN 9600 设备保护专题 1、光层保护 定义 方式 应用

Python开篇撬动未来的万能钥匙 从入门到架构的全链路指南

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 持续学习&#xff0c;不断…

LabVIEW 与 PLC 通讯

在工业自动化领域&#xff0c;LabVIEW 与 PLC 的通讯极为关键&#xff0c;它能实现设备间高效的数据交互与协同运作。接下来&#xff0c;将从应用场景、软件架构、功能实现、特点、开发问题及解决方法等层面展开阐述。 应用场景​ 智能工厂生产线监控系统中&#xff0c;LabVIE…

11-FreeRTOS任务相关的其他API函数

数据来源地址&#xff1a;gitee.com FreeRTOS任务相关的其他API函数 一、FreeRTOS任务相关的其他API函数介绍 1、FreeRTOS任务相关API函数介绍(部分常用的) 答&#xff1a; 二、任务状态查询API函数 1、获取任务优先级函数 答&#xff1a; UBaseType_t uxTaskPriorityGet…

ECMAScript(2)核心语法课件(Node.js/React 环境)

&#x1f4da; ECMAScript 核心语法课件&#xff08;Node.js/React 环境&#xff09; 1. 变量与作用域 变量声明方式 var&#xff1a;函数作用域&#xff0c;存在变量提升&#xff08;hoisting&#xff09;console.log(a); // undefined&#xff08;变量提升&#xff09; var a…

Selenium 页面加载超时pageLoadTimeout与 iframe加载关系解析

引言 在 Web 自动化测试中&#xff0c;处理页面加载超时是每个 Selenium 使用者都会遇到的挑战。特别是当页面包含 iframe 时&#xff0c;加载行为变得更加复杂。许多测试工程师困惑于&#xff1a;pageLoadTimeout 究竟能否控制 iframe 的加载&#xff1f;本文将深入探讨这一问…

AI面试将重塑企业招聘流程:从效率到精准度的全面升级

每年校招季&#xff0c;HR团队总被“面试官不够用”“简历太多看不清”“候选人放鸽子”等问题折磨。传统招聘流程冗长、成本高昂、标准参差&#xff0c;已难以适应快速变化的用人需求。而AI面试技术的突破&#xff0c;正在从底层逻辑上重塑招聘链条——从初筛到终面&#xff0…

IOC为什么交由spring容器管理?

根本原因&#xff1a;在 Spring 框架中&#xff0c;将控制反转&#xff08;IoC&#xff09; 交由 Spring 容器管理&#xff0c;是为了解决传统编程模式中 “对象创建与依赖管理耦合度高” 的核心问题&#xff0c;最终实现代码的低耦合、高可维护性、高可测试性。要理解这一设计…

Java反射与动态代理学习笔记

Java 反射与动态代理学习笔记反射概述反射允许对成员变量、成员方法和构造方法进行编程访问&#xff0c;提供了在运行时分析类和对象的能力。获取Class对象的三种方式方式代码示例说明Class.forName()Class.forName("全类名")通过类的全限定名获取Class对象对象.getC…

RAG提示词分解

RAG提示词分解 System Message # 智能问答助手&#xff08;RAG系统提示&#xff09;## 角色定义 您是"智能问答助手"&#xff0c;专门基于提供的上下文信息回答用户问题。## 核心规则 1. **严格基于上下文**&#xff1a;仅使用用户提供的<context>中的信息&…