一. 引言
在上一篇文章里,我們從零開始實現了 App 的?發現頁面,通過網絡請求獲取數據,并使用 RecyclerView 展示了劇集列表。
但光有發現頁還不夠,用戶在點擊一部劇時,自然希望進入到一個更詳細的頁面,去查看它的簡介、標簽以及劇集列表。本篇我們就來實現?發現詳情頁。
主要包含以下內容:
- 從發現頁跳轉到詳情頁(Activity 跳轉與傳值)
- 詳情頁的 UI 布局(背景、Toolbar、RecyclerView)
- RecyclerView 多類型布局(頭部 + 劇集列表)
- 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
占據主體區域,用于展示兩類內容:
- 頭部信息:封面、標題、描述、標簽、詞匯量
- 劇集列表:每一集的標題、文件大小、下載狀態等
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?請求與展示流程總結
- Activity 啟動后,通過 Intent 獲取?DiscoverDrama?參數
- 調用?viewModel.fetchEpisodes(drama)?發起網絡請求
- ViewModel 請求成功后,將數據賦值給 LiveData
- Activity 觀察 LiveData,并將數據傳遞給 Adapter
- Adapter 更新 RecyclerView,實現 UI 自動刷新
六.運行效果與總結
6.1?最終效果展示
- 用戶在?發現頁面?點擊某部劇集
- 頁面跳轉到?詳情頁
- 頁面頂部展示劇的封面、標題、描述、標簽和詞匯量
- 下方 RecyclerView 展示劇集列表,每一集顯示標題、文件大小和下載狀態(可擴展)
- UI 完全響應?LiveData?數據更新,無需手動刷新
6.2?本篇收獲
通過這一篇文章,我們掌握了:
Activity 跳轉與參數傳遞
- 使用 Intent 傳遞 Serializable 對象
- 在目標 Activity 中安全接收數據
RecyclerView 多類型布局
- 頭部視圖 + 列表視圖
- Adapter 分類型管理 ViewHolder
ViewModel + LiveData 數據驅動 UI
- Activity 不直接操作數據
- RecyclerView 自動響應數據變化
這種模式不僅使代碼清晰、可維護,還符合 Android 架構最佳實踐。