Flutter 小技巧之有趣的 UI 骨架屏框架 skeletonizer

很久沒有更新過小技巧系列,今天簡單介紹一個非常好用的骨架屏框架 skeletonizer ,它主要是通過將你現有的布局自動簡化為簡單的骨架,并添加動畫效果來實現加載過程,而使用成本則是簡單的添加一個 Skeletonizer 作為 parent :

Skeletonizer(enabled: _loading,child: ListView.builder(itemCount: 7,itemBuilder: (context, index) {return Card(child: ListTile(title: Text('Item number $index as title'),subtitle: const Text('Subtitle here'),trailing: const Icon(Icons.ac_unit),),);},),
)

=

當然,在實際使用場景中,一般情況在列表返回之前我們是沒有數據的,所以可以在加載過程中,通過 skeletonizer 提供的 BoneMock 來組裝一個你需要長度的數據列表:

 final fakeUsers = List.filled(7, User(name: BoneMock.name,jobTitle: BoneMock.words(2),email: BoneMock.email,createdAt: BoneMock.date, ),);final users = _loading ? fakeUsers : realUsers;return Skeletonizer(enabled: _loading,child: UserList(users: users),);

那 skeletonizer 是如何做到這個自動轉換控件為骨架屏的呢?核心就是在繪制 child 時,通過自定義 context 來替換默認 PaintingContext

在 skeletonizer 內部,它的 RenderSkeletonizer 是一個 RenderProxyBox 實現,作為一個 RenderProxyBox 的子類,它在布局階段表現得像一個透明代理,但在繪制階段會接管控制權,決定是繪制真實的子節點還是繪制骨架。

簡單來說,skeletonizer 就是通過自定義 PaintingContext 來攔截處理 child 的渲染 ,這里我們先簡單看看它的核心代碼的作用:

  • render_skeletonizer.dart:

    • 它是 RenderObject 的實現,也就是實際負責渲染的對象, RenderSkeletonizerRenderSliverSkeletonizer 的核心就是 override paint 方法,當 Skeletonizer 被激活時,它們不會像平常一樣繪制 child,而是創建一個自定義的 SkeletonizerPaintingContext 來接管繪制工作
  • skeletonizer_painting_context.dart:

    • 骨架屏效果的關鍵,繼承自 PaintingContext,但是提供了一個自定義的 Canvas 對象 SkeletonizerCanvas,這個自定義的 Canvas 會攔截所有來自 child 的繪制,然后用骨架的樣式來替代它們
  • uniting_painting_context.dart:

    • 在 paint 里對應 Skeleton.unite 的特殊實現,它提供了一個名為 UnitingCanvas 的特殊 Canvas,當 child 在這個 Canvas 上繪制時,它不會真的去繪制每個元素,而是計算所有繪制操作的區域,并將它們合并成一個大的矩形(unitedRect),最終這個合并后的大矩形會被統一渲染成一個骨架塊
  • /effects/\*.dart:

    • 這個目錄主要用于定義骨架屏的視覺動畫效果,其中 painting_effect.dart 定義了所有效果必須遵守的抽象基類 PaintingEffect,主要是通過構建 Paint 來構建動畫,默認的對應實現有:
      • shimmer_effect.dart: 實現了最常見的“微光”或“閃爍”效果,通過一個滑動的 LinearGradient (線性漸變) 來實現
      • pulse_effect.dart: 實現了“脈沖”效果,在兩種顏色之間來回漸變
      • sold_color_effect.dart: 純色效果,沒有動畫

所以,整個骨架屏的渲染流程如上圖所示,可以總結為:

  • 啟用 Skeletonizer:

    • Skeletonizer(enabled: true, child: ...) 被構建時,它會啟動一個動畫控制器(AnimationController),并根據配置選擇一個 PaintingEffect (例如 ShimmerEffect)
  • 創建 RenderObject:

    • Skeletonizer 會創建一個 RenderSkeletonizer (或 RenderSliverSkeletonizer) 對象,這個 RenderObject 會將自己標記為 isRepaintBoundary = true,這意味著它會創建一個獨立的繪制層 (Layer)
  • 接管繪制上下文:

    • paint 階段,RenderSkeletonizer 不會像普通 RenderObject 那樣直接調用 super.paint 來繪制 child,相反它會創建一個 SkeletonizerPaintingContext 實例,用于攔截繪制
  • 攔截繪制指令:

    • SkeletonizerPaintingContext 內部包含一個 SkeletonizerCanvas,當 Flutter 引擎嘗試繪制 child 時(比如 TextContainerIcon 等),所有對 canvas 的操作(如 drawParagraph, drawRect, drawImage)都會被 SkeletonizerCanvas 攔截
  • 替換為骨架樣式:

    • SkeletonizerCanvas 會根據攔截到的繪制指令的類型和位置,繪制出相應的骨架形態,并實現一些系列繪制方法,比如:
      • 文本 (drawParagraph): 它會計算出文本的每一行在哪里,然后用一系列矩形來代替真實的文字,矩形的圓角、是否對齊等:
      • 矩形/圓角矩形 (drawRect/drawRRect): 它會檢查這個矩形是否被標記為“葉子節點”(比如一個沒有子節點的 Container 或被 Skeleton.leaf 包裹的 Widget),如果是,它就會使用從 PaintingEffect (如 ShimmerEffect) 創建的 shaderPaint (帶有閃爍效果的畫筆) 來填充這個區域,如果不是,它可能會根據配置繪制一個純色背景,或者干脆忽略它:
      • ······
  • 應用動畫效果:

    • 所有用于繪制骨架的 shaderPaint 都來自于當前的 PaintingEffectSkeletonizerAnimationController 會不斷更新動畫值 (animationValue),PaintingEffect 根據這個值來創建每一幀的 Paint 對象,對于 ShimmerEffect 來說,這就表現為一個不斷移動的漸變,從而產生了微光流動的效果:

而在使用使用中,skeletonizer 也提供了豐富的可配置細節,例如:

  • skeleton.dart: 提供了一系列控制場景:

    • Skeleton.ignore: 忽略某個子 Widget,不對其進行骨架化

      Card(child: ListTile(title: Text('The title goes here'),subtitle: Text('Subtitle here'),trailing: Skeleton.ignore( // the icon will not be skeletonizedchild: Icon(Icons.ac_unit, size: 40),),),
      )
      

    • Skeleton.leaf : 容器標記為葉子控件,直接還用 shader paint 繪制

      Skeleton.leaf(child : Card(child: ListTile(title: Text('The title goes here'),subtitle: Text('Subtitle here'),trailing: Icon(Icons.ac_unit, size: 40),),)
      )
      

    • Skeleton.keep: 在骨架化時,保持某個子 Widget 的原始樣貌

      Card(child: ListTile(title: Text('The title goes here'),subtitle: Text('Subtitle here'),trailing: Skeleton.keep( // the icon will be painted as ischild: Icon(Icons.ac_unit, size: 40),),),
      )
      

    • Skeleton.replace: 在骨架化時,用一個替代的 Widget (比如一個簡單的灰色方塊) 來顯示,比如遇到需要 Image 空間的場景

          Card(child: ListTile(title: Text('The title goes here'),subtitle: Text('Subtitle here'),trailing: Skeleton.replace( // the icon will be replaced when skeletonizer is enabledwidth: 50, // the width of the replacementheight: 50, // the height of the replacementreplacement: // defaults to a DecoratedBoxchild: Icon(Icons.ac_unit, size: 40),),),);
      

    • Skeleton.unite: 將多個子 Widget 合并成一個大的骨架塊

      Card(child: ListTile(title: Text('Item number 1 as title'),subtitle: Text('Subtitle here'),trailing: Skeleton.unite(child: Row(mainAxisSize: MainAxisSize.min,children: [Icon(Icons.ac_unit, size: 32),SizedBox(width: 8),Icon(Icons.access_alarm, size: 32),],),),),
      )
      

    作用場景
    Skeleton.ignore完全跳過骨架化在加載時也需原樣顯示的 Logo 或品牌元素
    Skeleton.leaf將容器標記為終端骨骼將一個 Card 組件顯示為一整個實心骨架塊
    Skeleton.keep保持自身,骨架化子孫保持一個帶特殊邊框的容器,但骨架化其內部的文本和圖標
    Skeleton.shade為自定義繪制應用效果骨架化一個使用 CustomPainter 繪制的圖表或圖形
    Skeleton.replace在骨架化時替換組件處理 Image.network,用一個占位方塊替換加載中的網絡圖片
    Skeleton.unite將多個骨骼合并為一個將一行緊鄰的多個 Icon 合并成一個連續的長條形骨架
    Skeleton.ignorePointers禁用指針事件防止用戶點擊處于加載狀態的按鈕或列表項
  • bone.dart: 支持通過 Skeletonizer.zone 場景,手動自定義提供了一系列預設的“骨骼”Widget,用于手動搭建骨架屏布局,支持:

    • Bone.text()
    • Bone.multiText()
    • Bone.circle()
    • Bone.square()
    • Bone.icon()
    • Bone.button()
    • Bone.iconButton()
    Skeletonizer.zone(child: Card(child: ListTile(leading: Bone.circle(size: 48),  title: Bone.text(words: 2),subtitle: Bone.text(),trailing: Bone.icon(), ),),);
    

  • effects/*.dart, 主要用于定義了骨架屏的視覺動畫效果,其中 painting_effect.dart 定義了抽象基類 PaintingEffect

    • shimmer_effect.dart: 實現了最常見的“微光”或“閃爍”效果,通過一個滑動的 LinearGradient (線性漸變) 來實現

    • pulse_effect.dart: 實現了“脈沖”效果,在兩種顏色之間來回漸變

    • sold_color_effect.dart: 純色效果,沒有動畫

當然,在一些復雜嵌套場景,或者某些特殊控件,比如 SwitchListTile ,還有比如 RoundedSuperellipseBorder 這樣的自定義邊框形狀 等,框架在便利和處理時會無法處理對應的狀態或者復現形狀,這也算是它的局限性。

但是瑕不掩瑜,除了需要處理的 fake 數據部分,整體使用還是相當便捷,skeletonizer 的自動化能力可以極大地減少樣板代碼,并保證 UI 占位的一致性,這也是它值的推薦的原因。

那么,你會在你的應用里使用骨架屏嗎?

參考鏈接

  • https://github.com/Milad-Akarie/skeletonizer

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

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

相關文章

基于SpringBoot的寵物用品系統【2026最新】

作者:計算機學姐 開發技術:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源碼”。 專欄推薦:前后端分離項目源碼、SpringBoot項目源碼、Vue項目源碼、SSM項目源碼、微信小程序源碼 精品專欄:…

MongoDB 分片集群修改管理員密碼

記得關注一下博主,博主每天都會更新IT技術,讓你有意想不到的小收獲哦^_^ 文章目錄*記得關注一下博主,博主每天都會更新IT技術,讓你有意想不到的小收獲哦^_^*一、注釋MongoDB分片集群認證參數(三臺主機都要操作&#xf…

C++函數重載與引用詳解

一、函數重載:同名函數的 “差異化生存”?1. 概念定義?函數重載(Function Overloading)是 C 的重要特性,指在同一作用域內,允許存在多個同名函數,但要求這些函數的參數列表必須不同。(參數個數…

2025-08-17 李沐深度學習16——目標檢測

文章目錄1 介紹1.1 實際應用1.2 邊界框1.3 數據集2 錨框2.1 什么是錨框2.2 交并比2.3 分配標簽2.4 非極大值抑制3 經典目標檢測網絡3.1 R-CNN3.1.1 R-CNN (原始版本)3.1.2 Fast R-CNN3.1.3 Faster R-CNN3.1.4 Mask R-CNN3.2 單階段檢測器:SSD 和 YOLO3.2.1 SSD (Sin…

Bluedroid vs NimBLE

🔹 對比:Bluedroid vs NimBLE 1. 協議棧體積 & 內存占用 Bluedroid:體積大,RAM 占用也大(幾十 KB 到上百 KB)。NimBLE:輕量級,內存占用大概是 Bluedroid 的一半甚至更少。 &…

(純新手教學)計算機視覺(opencv)實戰八——四種邊緣檢測詳解:Sobel、Scharr、Laplacian、Canny

邊緣檢測詳解:Sobel、Scharr、Laplacian、Canny邊緣檢測是圖像處理和計算機視覺中的重要步驟,主要用于發現圖像中亮度變化劇烈的區域,即物體的輪廓、邊界或紋理特征。OpenCV 提供了多種常用的邊緣檢測算子,本教程將通過四種方法帶…

PyTorch 環境配置

目錄一、安裝 CUDA二、安裝 PyTorch1. 創建虛擬環境2. 安裝 PyTorch三、在 PyCharm 上創建一個 PyTorch 項目參考文章: 【2025年最新PyTorch環境配置保姆級教程(附安裝包)】 【超詳細 CUDA 安裝與卸載教程(圖文教程)】…

鴻蒙中冷啟動分析:Launch分析

啟動的分類(熱身環節) 啟動動類型觸發條件系統開銷 & 速度主要優化方向冷啟動應用進程不存在(首次啟動或進程被殺后啟動)最高,需創建進程、加載資源、初始化所有組件主要優化目標,減少主線程任務&…

告別盲目排查,PolarDB+DAS Agent智能運維新突破

1.概述 周五下午6點正準備下班,數據庫CPU突然爆滿,業務告警響成一片,DBA卻要手動翻查CPU/內存/負載等多個監控指標,還要查詢是否有新增慢SQL,死鎖等問題?” 這可能是數據庫DBA最鬧心的場景了,…

Linux------《零基礎到聯網:CentOS 7 在 VMware Workstation 中的全流程安裝與 NAT 網絡配置實戰》

(一)Linux的發行版Centos安裝與配置 下載Linux發行版本Centos:centos-7-isos-x86_64安裝包下載_開源鏡像站-阿里云點擊CentOS-7-x86_64-DVD-2009.torrent ,CentOS-7-x86_64-DVD-2009.torrent是官方提供的 BT 種子文件(176.1 KB&a…

iOS App 混淆工具實戰,教育培訓類 App 的安全保護方案

隨著在線教育、企業培訓、知識付費平臺的興起,越來越多的 iOS 應用需要保護自己的課程資源和核心邏輯。然而,教育類 App 面臨的最大風險并非傳統的外掛或刷分,而是 視頻盜鏈、題庫數據泄露、源碼邏輯被二次利用。 在這種場景下,合…

RabbitMQ:SpringAMQP Topic Exchange(主題交換機)

目錄一、案例需求二、基礎配置三、代碼實現TopicExchange與DirectExchange類似,區別在于RoutingKey可以是多個單次的列表,并且以.分割。 Queue與Exchange指定BindingKey時可以使用通配符: #:代指0個或多個單詞。*:代…

(純新手教學)計算機視覺(opencv)實戰六——圖像形態學(腐蝕、膨脹、開運算、閉運算、梯度、頂帽、黑帽)

圖像形態學在圖像處理中,形態學(Morphology) 是一種基于圖像中物體形狀的處理方法,通常用于二值圖像和灰度圖像。它通過腐蝕、膨脹等基本操作,結合開運算、閉運算、梯度運算、頂帽、黑帽等派生操作,來實現去…

學習嵌入式第三十五天

文章目錄網絡(續上)1.函數接口2.相關功能實現1.TCP連接2.UDP習題網絡(續上) 1.函數接口 sendto 原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, sockle…

為什么給數據表加了索引,寫入速度反而變慢了

為數據表增加索引后之所以會導致寫入(包括插入、更新、刪除)操作的速度變慢,其根本原因在于索引本質上是一個獨立的、需要與主表數據保持實時同步的“數據結構”。這一機制的核心邏輯涵蓋五個方面:因為索引本質上是一個“獨立的數…

.NET Core 中采用獨立數據庫的SAAS(多租戶)方法

介紹多租戶是指一種軟件架構,其中軟件的單個實例在服務器上運行并為多個租戶提供服務。在基于 SAAS 的平臺中,租戶是指使用該平臺開展業務運營的客戶。每個租戶都擁有獨立的數據、用戶帳戶和配置設置,并且與其他租戶隔離。多租戶允許有效利用…

運維日常工作100條

這是一份非常詳細和實用的“運維日常工作100條”清單。它涵蓋了從日常巡檢、變更管理、故障處理到安全、優化和文檔等運維工作的方方面面,可以作為運維工程師的日常工作指南和檢查清單。 運維日常工作100條 一、日常巡檢與監控 (20條) 檢查核心監控大盤:查看整體業務健康狀態…

OpenHarmony子系統介紹

OpenHarmony子系統OpenHarmony子系統1. AI業務子系統2. 方舟運行時子系統3. ArkUI框架子系統4. DFX子系統5. DeviceProfile子系統6. XTS子系統7. 上傳下載子系統8. 主題框架子系統9. 事件通知子系統10. 位置服務子系統11. 元能力子系統12. 全局資源調度子系統13. 全球化子系統1…

博士招生 | 英國謝菲爾德大學 招收計算機博士

內容源自“圖靈學術博研社”gongzhonghao學校簡介謝菲爾德大學(The University of Sheffield)是英國久負盛名的公立研究型大學,也是羅素集團成員之一。在 2026 年 QS 世界大學排名中,謝菲爾德大學位列第92位,其中計算機…

如何理解面向過程和面向對象,舉例說明一下?

面向過程和面向對象是兩種不同的編程思想,核心區別在于解決問題的視角不同:前者關注 “步驟和過程”,后者關注 “對象和交互”。面向過程的核心思想是把問題拆解成一系列步驟,通過函數實現每個步驟,然后按順序調用這些…