Android UI 組件系列(十一):RecyclerView 多類型布局與數據刷新實戰

博客專欄:Android初級入門UI組件與布局

源碼:通過網盤分享的文件:Android入門布局及UI相關案例

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

引言

在 Android 應用中,RecyclerView?是最常用的列表展示組件,無論是商品列表、新聞流、視頻推薦頁,還是內容首頁,幾乎無處不在。初學者常常從單一類型列表入門,比如簡單的文字列表或圖文卡片;但一旦進入實際項目,我們面臨的卻是更加復雜的列表結構:

  • 同一個頁面中可能出現多種布局樣式:頂部 Banner、大標題、橫向滑動卡片、豎向新聞流……
  • 用戶操作或接口更新后,列表數據需要實時刷新,并盡可能做到精準刷新、避免性能浪費

也就是說,多類型布局支持 + 高性能刷新機制,幾乎是中高級 RecyclerView 使用中繞不開的兩個課題。

本篇我們將通過一個實戰 Demo,從零開始實現這樣一個典型的首頁列表:

1. 頁面頂部展示一張 Banner 圖片

2. 中間是「推薦直播間」,每行顯示兩個直播卡片

3. 接著是「最新資訊」,展示若干新聞摘要

4. 支持兩種刷新方式:

  • 「全量刷新」:使用?notifyDataSetChanged()
  • 「智能刷新」:使用?DiffUtil?精準對比變化項

接下來,我們先來看如何實現多類型布局的支持。

一、支持多類型 Item 的 RecyclerView

1.1 定義多類型數據模型

在實際業務中,RecyclerView 常常需要展示不止一種布局。例如電商首頁中常見的結構就包括:

  • 頂部的 Banner 區域;
  • 內容分區標題,如“猜你喜歡”、“熱門直播”;
  • 模塊內容卡片,如直播間、新聞資訊等。

因此我們要先將這些內容類型抽象成數據模型類,并配合布局實現可復用的多類型渲染。

? 本 Demo 中的四種模型:

類型

用途

數據類定義

Banner

頂部大圖

data class Banner(val imageResId: Int)

TitleItem

分組標題(如推薦直播)

data class TitleItem(val text: String)

LiveItem

直播卡片

data class LiveItem(val title: String, val coverResId: Int)

NewsItem

新聞摘要卡片

data class NewsItem(val title: String, val summary: String)

這四個類分別代表頁面中四種功能塊,我們會將它們依次插入到列表數據源中(統一為?List<Any>),并在 Adapter 中通過類型判斷進行區分渲染。

Banner:

data class Banner(val imageResId: Int
)

TitleItem:

data class TitleItem(val text: String
)

LiveItem:

data class LiveItem(val title: String,val coverResId: Int
)

NewsItem:

data class NewsItem(val title: String,val summary: String
)

1.2 Adapter 與 ViewHolder 實現

有了多類型的數據模型之后,下一步就是在 Adapter 中進行“識別”和“綁定”。在 RecyclerView 中,支持多類型的關鍵機制有兩個:

? getItemViewType(position: Int): Int

這個方法用來告訴 RecyclerView:當前 position 對應的數據項屬于哪種類型,我們可以為每個類型分配一個整數常量:

override fun getItemViewType(position: Int): Int {return when (items[position]) {is Banner -> TYPE_BANNERis TitleItem -> TYPE_TITLEis LiveItem -> TYPE_LIVEis NewsItem -> TYPE_NEWSelse -> throw IllegalArgumentException("未知類型")}
}

這樣 RecyclerView 才知道該使用哪個布局去創建 ViewHolder。

? onCreateViewHolder(parent, viewType)

根據?viewType?創建不同類型的 ViewHolder 和對應布局:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {val inflater = LayoutInflater.from(parent.context)return when (viewType) {TYPE_BANNER -> BannerViewHolder(inflater.inflate(R.layout.item_banner, parent, false))TYPE_TITLE -> TitleViewHolder(inflater.inflate(R.layout.item_title, parent, false))TYPE_LIVE -> LiveViewHolder(inflater.inflate(R.layout.item_live, parent, false))TYPE_NEWS -> NewsViewHolder(inflater.inflate(R.layout.item_news, parent, false))else -> throw IllegalArgumentException("未知 viewType")}
}

? onBindViewHolder(holder, position)

再根據 position 取得數據并綁定到對應的 ViewHolder 上:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {when (val item = items[position]) {is Banner -> (holder as BannerViewHolder).bind(item)is TitleItem -> (holder as TitleViewHolder).bind(item)is LiveItem -> (holder as LiveViewHolder).bind(item)is NewsItem -> (holder as NewsViewHolder).bind(item)}
}

? ViewHolder 示例

每個類型的 ViewHolder 都可定義為內部類,綁定對應控件:

class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {private val title: TextView = itemView.findViewById(R.id.newsTitle)private val summary: TextView = itemView.findViewById(R.id.newsSummary)fun bind(item: NewsItem) {title.text = item.titlesummary.text = item.summary}
}

其他 ViewHolder 如 BannerViewHolder、LiveViewHolder 也類似,全部代碼可以查看博客頂部的demo。

1.3 布局設計與 GridLayoutManager 使用

在基礎用法中,我們通常使用?LinearLayoutManager?來實現 RecyclerView 的豎向排列。但當頁面中存在需要「多列展示」的內容(如直播卡片),我們就可以借助?GridLayoutManager?實現靈活的布局排布。

? 目標排布效果:

Item 類型

排布方式

Banner

占整行(1 列 * 100%)

TitleItem

占整行

LiveItem

每行顯示兩個(2 列)

NewsItem

占整行

? 使用 GridLayoutManager

在?MainActivity.kt?中設置 RecyclerView 的布局方式:

val layoutManager = GridLayoutManager(this, 2)

這里的?2?表示列表每行最多兩列。

? 控制每種 item 的跨列數:SpanSizeLookup

我們不希望所有 item 都是兩列的,而是只有 LiveItem 兩列,其他類型都應該「占滿整行」。為此我們通過?SpanSizeLookup?來動態指定每個 item 占幾列:

layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {override fun getSpanSize(position: Int): Int {return when (adapter.getItemViewType(position)) {HomeAdapter.TYPE_LIVE -> 1  // 每行兩個else -> 2                   // 占整行}}
}

這樣就實現了「混合布局」的效果:LiveItem 為兩列,其它都是一列跨兩格。

? item 布局

item_banner.xml:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/bannerImage"android:layout_width="match_parent"android:layout_height="180dp"android:scaleType="centerCrop"android:src="@drawable/placeholder"android:contentDescription="Banner" />

item_title.xml:

<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="16dp"android:textColor="#222222" />

item_live.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:padding="12dp"android:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/liveImage"android:layout_width="match_parent"android:layout_height="160dp"android:scaleType="centerCrop"android:src="@drawable/placeholder" /><TextViewandroid:id="@+id/liveTitle"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingTop="8dp"android:text="直播標題"android:textSize="16sp"android:textColor="#333333" />
</LinearLayout>

item_news.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:padding="12dp"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/newsTitle"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="資訊標題"android:textSize="16sp"android:textColor="#222222"android:textStyle="bold" /><TextViewandroid:id="@+id/newsSummary"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingTop="4dp"android:text="這是一個資訊概要內容..."android:textSize="14sp"android:textColor="#666666" />
</LinearLayout>

最終效果如下:

二、數據刷新機制的兩種實現方式

RecyclerView 是一個高性能的列表控件,但要想實現真正“流暢”的體驗,光靠展示還不夠 —— 列表的數據經常會更新(新增、修改、刪除),這時候就需要通過刷新機制來同步 UI。

在本 Demo 中,我們實現了兩種刷新方式:

  • ? 全量刷新(notifyDataSetChanged())
  • ? 智能刷新(DiffUtil)

2.1 全量刷新(notifyDataSetChanged)

這是最常見、也是最基礎的刷新方法。當你調用:

adapter.notifyDataSetChanged()

RecyclerView 會強制重繪整個列表,無論數據變化了多少項。雖然簡單,但這帶來了兩個明顯的問題:

  • ? 性能低:所有可見項都要重新綁定;
  • ? 無動畫:看不到“哪一項”發生了變化,用戶感知不明顯。

實現如下:

      findViewById<Button>(R.id.buttonFullRefresh).setOnClickListener {val newList = originalItems.toMutableList()// 修改兩條 NewsItem 內容newList.replaceAll {if (it is NewsItem && it.title.contains("Kotlin")) {it.copy(summary = "Kotlin 2.0 已正式上線,快來試試吧!")} else if (it is NewsItem && it.title.contains("Compose")) {it.copy(summary = "Compose 新特性:自動響應式刷新")} else it}// 添加兩條新的 LiveItem,插入在第一個 LiveItem 后val insertIndex = newList.indexOfFirst { it is LiveItem }if (insertIndex != -1) {newList.add(insertIndex, LiveItem("直播新品推薦", R.drawable.placeholder))newList.add(insertIndex + 1, LiveItem("夜間慢直播", R.drawable.placeholder))}// 替換原數據并全量刷新originalItems.clear()originalItems.addAll(newList)adapter.notifyDataSetChanged()}

? 使用場景:

  • 快速原型開發;
  • 頁面結構很小、數據量不大;
  • 數據變化劇烈、難以追蹤變化項(如每次全替換)。

2.2 智能刷新(DiffUtil)

DiffUtil?是 Android 官方推薦的列表刷新工具,可以根據「舊數據」與「新數據」之間的差異,計算出:

  • 哪些項需要插入、刪除、更新;
  • 哪些項保持不變、可復用。

? 核心用法:

val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(adapter)

你需要實現一個?DiffUtil.Callback,并重寫以下方法:

override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {// 判斷是否為同一條數據
}override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {// 判斷內容是否有變更
}

實現如下:

findViewById<Button>(R.id.buttonSmartRefresh).setOnClickListener {val newList = originalItems.toMutableList()// 修改兩條 NewsItem 內容newList.replaceAll {if (it is NewsItem && it.title.contains("Kotlin")) {it.copy(summary = "Kotlin 2.0 已正式上線,快來試試吧!")} else if (it is NewsItem && it.title.contains("Compose")) {it.copy(summary = "Compose 新特性:自動響應式刷新")} else it}// 添加兩條新的 LiveItem,插入在第一個 LiveItem 后val insertIndex = newList.indexOfFirst { it is LiveItem }if (insertIndex != -1) {newList.add(insertIndex, LiveItem("直播新品推薦", R.drawable.placeholder))newList.add(insertIndex + 1, LiveItem("夜間慢直播", R.drawable.placeholder))}val diffCallback = object : DiffUtil.Callback() {override fun getOldListSize() = originalItems.sizeoverride fun getNewListSize() = newList.sizeoverride fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {return originalItems[oldItemPosition] == newList[newItemPosition]}override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {return originalItems[oldItemPosition] == newList[newItemPosition]}}val diffResult = DiffUtil.calculateDiff(diffCallback)originalItems.clear()originalItems.addAll(newList)diffResult.dispatchUpdatesTo(adapter)}

? 使用場景:

  • 大量數據頻繁變化;
  • 想要提升滾動流暢度;
  • 希望有插入/刪除動畫過渡。

結語

在本篇實戰中,我們圍繞一個首頁式的列表頁面,完整實現了 RecyclerView 在復雜場景下的兩大關鍵能力:

? 多類型布局支持

  • 通過定義多個數據模型(Banner、TitleItem、LiveItem、NewsItem),實現頁面結構化組織;
  • 使用?getItemViewType()?方法判斷類型,搭配多個布局文件實現靈活展示;
  • 借助?GridLayoutManager?+?SpanSizeLookup?實現不同 item 的排布策略,兼顧視覺和性能。

? 數據刷新機制

  • 使用?notifyDataSetChanged()?實現最基礎的全量刷新;
  • 使用?DiffUtil?實現性能更優、體驗更佳的智能刷新;
  • 對比展示了兩種刷新機制在實現與效果上的差異,方便在項目中做出合理選擇。

📈 可擴展方向

如果你已經掌握了本文的內容,下面這些方向將進一步提升你的列表開發能力:

擴展點

描述

點擊事件封裝

為不同類型的 item 添加點擊、長按等事件回調

加載更多 / 分頁

實現滑動到底部自動加載下一頁內容

空數據 / 錯誤狀態處理

在數據為空或加載失敗時展示占位 UI

多類型封裝優化

用委托或泛型方式封裝多類型 Adapter,簡化維護

使用 Paging3

對接分頁接口,自動處理刷新與數據合并

Jetpack Compose 實現

通過 Compose 編寫聲明式列表,更加現代化

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92241.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92241.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92241.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何學習跨模態對齊(尤其是 CLIP 思想)

學習跨模態對齊&#xff08;尤其是CLIP思想&#xff09;需要結合理論基礎、經典模型原理、實踐復現和前沿擴展&#xff0c;以下是一套系統的學習路徑&#xff0c;從入門到深入逐步展開&#xff1a; 一、先補基礎&#xff1a;跨模態對齊的“前置知識” 跨模態對齊的核心是讓圖…

日記研究:一種深入了解用戶真實體驗的UX研究方法

在用戶體驗&#xff08;UX&#xff09;研究中&#xff0c;我們常常需要了解用戶在真實世界中如何與產品互動。然而&#xff0c;由于時間和空間的限制&#xff0c;我們很難像“特工”一樣全天候跟蹤用戶。這時&#xff0c;“日記研究”&#xff08;Diary Studies&#xff09;就成…

鴻蒙app 開發中 加載圖片的時候閃一下 如何解決

1.解決 在圖片上 加載這個屬性 .syncLoad(true) 參考的官方鏈接

【OS】進程與線程

進程進程實體代碼段相關數據PCB進程標識符外部標識符&#xff1a;為方便用戶對進程的訪問&#xff0c;為每個進程設置一個外部標識符&#xff0c;通常由字母和數字組成內部標識符&#xff1a;為方便系統對進程的使用&#xff0c;在OS中又為進程設置了內部標識符&#xff0c;賦予…

Django 序列化詳解:從 Model 到 JSON,全面掌握數據轉換機制

一、引言&#xff1a;什么是 Django 序列化&#xff1f;在 Web 開發中&#xff0c;序列化&#xff08;Serialization&#xff09; 是指將復雜的數據結構&#xff08;如數據庫模型對象&#xff09;轉換為可傳輸的格式&#xff08;如 JSON、XML、YAML 等&#xff09;&#xff0c;…

茶葉蛋大冒險小游戲流量主微信抖音小程序開源

游戲特點 響應式設計&#xff1a;完美適配各種移動設備屏幕尺寸 直觀的觸摸控制&#xff1a;左右滑動屏幕控制茶葉蛋移動 中式風格元素&#xff1a; 茶葉蛋角色帶有裂紋紋理和可愛表情 筷子、蒸籠等中式廚房元素作為障礙物 八角、茶葉等香料作為收集物 鍋底火焰動畫效果 游戲機…

區分郵科工業交換機與路由器

在這個數字化的時代&#xff0c;我們每天都在享受著互聯網帶來的便利。無論是工作還是娛樂&#xff0c;網絡已經成為我們生活中不可或缺的一部分。然而&#xff0c;在這個看似簡單的背后&#xff0c;隱藏著兩個至關重要的設備——郵科工業交換機和路由器。它們就像網絡世界的雙…

【數據結構入門】數組和鏈表的OJ題(2)

目錄 1.回文鏈表 分析&#xff1a; 代碼&#xff1a; 2.相交鏈表 分析&#xff1a; 代碼&#xff1a; 3.環形鏈表 分析&#xff1a; 代碼&#xff1a; 面試提問&#xff1a; 4.環形鏈表II 分析1&#xff1a; 分析2&#xff1a; 代碼&#xff1a; 5.隨機鏈表的復…

文件包含篇

web78 第一題filter偽協議直接讀源碼即可 ?filephp://filter/convert.base64-encode/resourceflag.php web79 flag.php的php無法用大小寫繞過&#xff0c;所以用Php://input只讀流 import requests url "http://fadb524a-f22d-4747-a35c-82f71e84bba7.challenge.ctf.sho…

互作蛋白組學技術對比:鄰近標記與傳統IP-MS、Pull down-MS優勢對比

在生命科學領域&#xff0c;蛋白質間的相互作用構成了生命活動的核心網絡&#xff0c;驅動著信號傳導、基因調控、代謝途徑等關鍵過程。為了繪制這幅復雜的“分子互作地圖”&#xff0c;科學家們開發了多種技術&#xff0c;其中免疫共沉淀結合質譜&#xff08;IP-MS&#xff09…

(ZipList入門筆記一)ZipList的節點介紹

ZipList是 Redis 中一種非常緊湊、節省內存的數據結構 Ziplist&#xff08;壓縮列表&#xff09; 的內部內存布局。它被用于存儲元素較少的 List、Hash 和 Zset。 下面我們來詳細介紹每一個節點的含義&#xff1a; 1. zlbytes (ziplist bytes) 含義&#xff1a; 整個壓縮列…

Unix 發展史概覽

這里是一個簡明清晰的 Unix 發展史概覽&#xff0c;涵蓋從起源到現代的重要節點和演變過程。Unix 發展史概覽 1. Unix 起源&#xff08;1969年&#xff09; 貝爾實驗室&#xff1a;Ken Thompson 和 Dennis Ritchie 開發出 Unix 操作系統。最初設計目標&#xff1a;簡潔、可移植…

基于coze studio開源框架二次定制開發教程

目錄 一、 項目介紹 1.1 什么是Coze Studio 1.2 功能清單 1.3對比商業版本 二、 功能定開說明 2.1 技術棧簡介 2.2 項目架構

RHCE認證題解

考前說明請勿更改 IP 地址。DNS 解析完整主機名&#xff0c;同時也解析短名稱。? 所有系統的 root 密碼都是 redhat? Ansible 控制節點上已創建用戶賬戶 devops。可以使用 ssh 訪問? 所需的所有鏡像保存在鏡像倉庫 utility.lab.example.compodman 可使用下述賬號登錄使用 用…

調用com對象的坑

1、諫言 最近我在弄64位調用32位dll的問題&#xff0c;在幾種IPC之間&#xff0c;最后考慮了調用COM 畢竟我們只在windows平臺 2、第一坑–修改編譯后都需要重新注冊&#xff0c;注冊表 一直以為只需要編譯就好了&#xff0c;結果調用沒反應、報錯什么的&#xff0c;需要先撤銷…

【Python】PyQt 實現 TreeWidget 多級聯動選擇邏輯,打造素材搜索自定義樹形控件!

在開發自己的寫作素材管理工具時,我遇到了一個非常典型但又略顯棘手的 UI 問題: ?? 如何實現一個“可自由勾選分類標簽”的樹形結構界面,支持父子節點自動聯動勾選,提升用戶體驗? 雖然 PyQt 的 QTreeWidget 是構建多層分類結構的好幫手,但默認卻不具備父子節點的自動級…

27-數據倉庫與Apache Hive-2

1.數倉開發語言概述 理論上來說&#xff0c;任何一款編程語言只要具備讀寫數據、處理數據的能力&#xff0c;都可以用于數倉的開發。比如大家耳熟能詳的C、java、Python等&#xff1b; 關鍵在于編程語言是否易學、好用、功能是否強大。遺憾的是上面所列出的C、Python等編程語言…

軟件測試——接口自動化

測試中的自動化分為兩類&#xff1a; 1.ui自動化&#xff08;web、移動端&#xff09;2.接口自動化 前面的博客中&#xff0c;我們已經講解了web端的ui自動化&#xff0c;感興趣的同學可以去看看&#xff1a;軟件測試——自動化測試常見函數_自動化測試代碼編寫-CSDN博客 今…

Flask一個用戶同時只能在一處登錄實現

場景&#xff1a;web頁面如果多人用同一賬號同時登錄操作&#xff0c;可能會導致數據等的混亂甚至出現故障。并且可能損害開發者的利益。為此&#xff0c;本篇文章就講下如何實現同一賬戶同時僅能一個地方登錄操作。 思路&#xff1a;1. 用戶登陸時生成token&#xff08;uuid.u…

聯發科芯片組曝高危漏洞:越界寫入缺陷危及智能手機與物聯網設備安全

漏洞概況全球領先的芯片組制造商聯發科&#xff08;MediaTek&#xff09;近日發布最新產品安全公告&#xff0c;披露了影響其智能手機、物聯網設備及其他嵌入式系統芯片的多項安全漏洞。高危漏洞分析CVE-2025-20696 作為公告披露的首個且最嚴重的漏洞&#xff0c;該高危缺陷源于…