Flutter 實現骨架屏

来源:https://www.cnblogs.com/hhsk/p/18442178
-Advertisement-
Play Games

什麼是骨架屏 在客戶端開發中,我們總是需要等待拿到服務端的響應後,再將內容呈現到頁面上,那麼在用戶發起請求到客戶端成功拿到響應的這段時間內,應該在屏幕上呈現點什麼好呢? 答案是:骨架屏 那麼什麼是骨架屏呢,來問下 GPT: 骨架屏(Skeleton Screen)是一種現代的用戶界面設計技術,用於提 ...


什麼是骨架屏

在客戶端開發中,我們總是需要等待拿到服務端的響應後,再將內容呈現到頁面上,那麼在用戶發起請求到客戶端成功拿到響應的這段時間內,應該在屏幕上呈現點什麼好呢?

答案是:骨架屏

那麼什麼是骨架屏呢,來問下 GPT:

骨架屏(Skeleton Screen)是一種現代的用戶界面設計技術,用於提升應用或網站在載入內容時的用戶體驗。在內容的完全載入和呈現之前,骨架屏提供了一種模糊的預覽,形似最終內容的空白版,通常用灰色的塊、線條或元素占位符表示。這種設計方法可以有效減少用戶的感知等待時間,增強用戶的交互感。

功能和用途

  1. 提高感知性能:骨架屏通過立即顯示頁面的基本結構(而非旋轉的載入圖標或完全空白的屏幕),給用戶一種內容即將呈現的感覺,這可以使等待時間感覺上更短。

  2. 改善用戶體驗:使用骨架屏可以減少用戶在載入過程中的焦慮,用戶看到界面元素已經在逐步載入,會有更多的耐心等待最終內容的呈現。

  3. 提供內容載入的視覺提示:骨架屏體現了頁面內容載入的進度,可以讓用戶知道哪些內容即將出現,這樣用戶就不會感到突然或困惑。

實現方式

骨架屏的實現通常包括以下幾個步驟:

  1. 設計:設計與最終內容佈局相似的基本框架,使用灰色或淺色塊代表將要載入的各種元素,如文本行、圖片、按鈕等。

  2. 前端實現:在前端代碼中,可以使用HTML和CSS來創建這些占位符。對於複雜的動態載入內容,可以使用JavaScript或前端框架如React、Vue等來動態控制骨架屏的顯示和隱藏。

  3. 數據載入後的處理:一旦相應的數據載入完成,骨架屏應被實際內容替換。這通常涉及到監聽數據載入的完成事件,然後更新UI。

示例

在一個簡單的網頁應用中,如果你正在載入一個包含標題、幾段文本和圖片的文章,骨架屏可能包括:

  • 一個灰色的矩形塊預留給圖片。
  • 幾個灰色的條形預留給文本標題和段落。

隨著實際內容的逐漸載入到瀏覽器中,這些灰色占位符將被實際的圖片和文本內容替換。

結論

骨架屏是一種非常有效的用戶界面技術,尤其適用於網路速度較慢或數據處理較慢的應用場景,能顯著提升用戶的等待體驗和整體滿意度。通過合理設計和實現,開發者可以利用骨架屏減少用戶流失,提升應用的專業感和友好感。

我們要實現的效果

上面是一個 內容展示的卡片,下麵的是 載入中狀態的該卡片的骨架圖

如何實現呢?

1. 先定義卡片部分 ui 代碼

一個 Column 中有三行元素,分別是 第一行: 圖片第二行: 卡片標題第三行: 頭像 昵稱 瀏覽量

class StarCard extends StatelessWidget {
  const StarCard({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 180,
      clipBehavior: Clip.hardEdge,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(4),
      ),
      child: Column(
        children: [
          SizedBox(
            width: 180,
            child: AspectRatio(
              aspectRatio: 9 / 11,
              child: Image.network('https://pic1.zhimg.com/80/v2-fc35089cfe6c50f97324c98f963930c9_720w.jpg', fit: BoxFit.cover),
            ),
          ),
          Padding(
            padding: EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '魔法少女李知恩!!!',
                  style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.w500),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                SizedBox(height: 8),
                Row(
                  children: [
                    ClipRRect(
                      borderRadius: BorderRadius.circular(10),
                      child: Image.network(
                        'https://pic1.zhimg.com/80/v2-1956eeb2c894f1785362411aa306f882_1440w.webp?source=1def8aca',
                        height: 20,
                        width: 20,
                        fit: BoxFit.cover,
                      ),
                    ),
                    SizedBox(width: 4),
                    Text('是 IU 吖', style: TextStyle(fontSize: 12, color: const Color(0xff86909C))),
                    const Spacer(),
                    Text('21 瀏覽', style: TextStyle(fontSize: 12, color: const Color(0xff86909C))),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

2. 引入 shimmer

pubspec.yaml 中加入

dependencies:
  shimmer: ^3.0.0

其作用是為我們的元素加上 閃動 流光 效果,類似於一道光照射到一把光滑的寶劍上,隨著寶劍角度發生變化 光的反射發生位移的現象

我們使用它的 fromColors 構造器,先隨便放進去一個 Container 試試效果:

Shimmer.fromColors(
  baseColor: Colors.orange,
  highlightColor: Colors.blue,
  child: Container(
	color: Colors.white,
	height: 180,
	width: 180,
  ),
)

效果還不錯,就是配色有點醜

調整下顏色,在用來包裹下我們剛剛定義的 StarCard :

Shimmer.fromColors(
  baseColor: Colors.grey[300]!, // 骨架基色
  highlightColor: Colors.grey[100]!, // 骨架高亮色
  child: StarCard(),
),

誒?怎麼跟我們要實現的效果有點出入?

這是因為 StarCard 的根組件是一個帶有顏色的 Container

return Container(
  width: 180,
  clipBehavior: Clip.hardEdge,
  decoration: BoxDecoration(
	color: Colors.white,
	borderRadius: BorderRadius.circular(4),
  ),
  ...
);

而這樣 Shimmer 效果便會被加到整個跟組件上,child 也就看不到 Shimmer 了。那我們將根組件的 color 屬性移除試試呢:

嗯...... 底層的元素確實展示出來了,不過我們所期望的並不是展示出文字和數據啊,況且這個時候我們還尚未拿到伺服器返回給我的的數據

3. Magic symbol

這個時候我們就需要用到一個 魔法符號 ,不過在引入之前,先分離下組件:

StarCard 需要一個構造函數,裡面接收一個從服務端 反序列化 來的 model ;再新定義一個 StarCardSkeleton 組件,他有一個無參構造器,用作 StarCard 的骨架圖;也就是說在獲取到數據之前,我們使用一個 StarCardSkeleton 來占 StarCard 的位,獲取到數據之後使用 StarCard 來展示真實的數據,代碼如下:

class StarCard extends StatelessWidget {
  const StarCard({super.key, required this.starModel});

  final StarModel starModel;

  @override
  Widget build(BuildContext context) {...}
}

class StarCardSkeleton extends StatelessWidget {
  const StarCardSkeleton({super.key});

  @override
  Widget build(BuildContext context) {...}
}

現在該回歸正題了,我們要引入的 魔法符號 就是:

這是一個 全寬 純色 占位符,數個 連起來,配合加粗 fontWeight 可以實現我們想要的 一道長條色塊 的效果,且要比定義 SizedBox 少改動更多代碼,用它來代替Shimmer 文本再合適不過了

我們先將 StarCard build 中的代碼全部拷貝到 StarCardSkeleton 裡面,並改動 卡片標題用戶昵稱瀏覽量 Text 中的文字為自定義數量的

class StarCardSkeleton extends StatelessWidget {
  const StarCardSkeleton({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 180,
      clipBehavior: Clip.hardEdge,
      decoration: BoxDecoration(
        // color: Colors.white,
        borderRadius: BorderRadius.circular(4),
      ),
      child: Column(
        children: [
          SizedBox(
            width: 180,
            child: AspectRatio(
              aspectRatio: 9 / 11,
              child: Container(color: Colors.white),
            ),
          ),
          Padding(
            padding: EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '▆▆▆▆▆▆',
                  style: TextStyle(fontSize: 15, fontWeight: FontWeight.w900),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                SizedBox(height: 8),
                Row(
                  children: [
                    Container(
                      width: 20,
                      height: 20,
                      decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white),
                    ),
                    SizedBox(width: 4),
                    // NickNameText(articleData.nickName, views: articleData.playTimes),
                    Text('▆▆▆', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w900)),
                    const Spacer(),
                    Text('▆▆', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w900)),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

如果想進一步優化渲染性能,可以把 Image 換成帶背景色的 Container

再看下效果呢

完美!簡直一模一樣!

抽離出 Simmer 組件

為了方便組件復用,可以將 Shimmer 封裝出來

/// 骨架屏閃爍
class BaseShimmer extends StatelessWidget {
  const BaseShimmer({super.key, required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Shimmer.fromColors(
      baseColor: Colors.grey[300]!, // 骨架基色
      highlightColor: Colors.grey[100]!, // 骨架高亮色
      child: child,
    );
  }
}

再次用到可以直接:BaseShimmer(child: StarCardSkeleton())

拓展

這章的標題是 骨架屏 ,為什麼從頭到尾一直再講怎麼生成一個骨架圖呢?

先別急罵標題黨!

所謂的 骨架屏 不就是一張一張的骨架圖拼出一個屏幕,不就是一個骨架屏 嘛

BaseShimmer(
	child: MasonryGridView.count(
		padding: EdgeInsets.only(bottom: 10, left: 12, right: 12, top: 8),
		physics: const NeverScrollableScrollPhysics(),
		itemCount: 9,
		mainAxisSpacing: 5,
		crossAxisSpacing: 5,
		crossAxisCount: 2,
		itemBuilder: (BuildContext context, int index) {
		  return StarCardSkeleton();
		}),
	)

這裡用了 flutter_staggered_grid_view 包,這個包還可以做出卡片高度不一的瀑布流佈局效果

註:使用 BaseShimmer 包裹整個 GridViewListView 比包裹單個的 Card 效果要更好喲~

風險

經過測試發現 在不同的設備上、或者使用了自定義字體,▆▆▆ 之間會出現微小間距,無論將 fontWeight 設置為多大都無法避免,這時只能將 方案換為帶顏色的 Container 來解決。不過 SkeletonScreen 在頁面停留的時間通常不會太長,這點就要看團隊內部的取捨了


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文內容來自YashanDB官網,具體內容請見https://www.yashandb.com/newsinfo/7488285.html?templateId=1718516 背景 OCI 是Oracle調用介面(Oracle Call Interface 簡稱OCI) 提供了一組對Oracle數 ...
  • 本文內容來自YashanDB官網,具體內容請見https://www.yashandb.com/newsinfo/7459465.html?templateId=1718516 問題現象 某局點yashandb cpu使用率100%,經線上分析是由於幾個sql執行慢,其中一個sql為簡單的單行等值綁 ...
  • Apache SeaTunnel 2.3.8版本即將於大家見面,近日,Apache SeaTunnel PMC Member 範佳在社區的交流會上為大家提前透露了關於這個新版本即將進行的功能與特性更新概況,詳細內容如下: SeaTunnel 簡介 SeaTunnel是一個高性能的開源分散式數據集成系 ...
  • 在大數據時代,工作流任務調度系統成為了數據處理和業務流程管理的核心組件,在大數據平臺的構建和開發過程中尤為重要。隨著數據量的激增和業務需求的多樣化,合理的任務調度不僅能夠提高資源利用率,還能保證業務流程的穩定和高效運行。本文將結合實際場景,探討目前市面上常見的工作流任務調度及其關鍵特性。 一、工作流 ...
  • 本文轉自YashanDB官網,具體內容請見https://www.yashandb.com/newsinfo/7441388.html?templateId=1718516 問題現象 客戶剛開始使用YashanDB odbc的時候,需要查看調用日誌詳情, 確認相應介面調用情況。 問題的風險及影響 客 ...
  • postgresql 與PostGis 離線環境安裝 上傳文件至伺服器 #安裝所需依賴 yum install /opt/PGsql-13-gis/rpm/* -y Postgresql安裝 tar -zxvf postgresql-13.2.tar.gz #進入該目錄 ./configure -- ...
  • 1. 讓數據可信 1.1. 每個終端用戶(End User)都有一個共同的需求:訪問想要的數據 1.2. 真的能夠相信我正在訪問的這些數據嗎? 1.2.1. 終端用戶很快就會發現,訪問數據和相信正在訪問的數據是兩回事 1.2.2. 訪問數據和相信數據不是同一回事 1.2.3. 如果數據不可信,可能會 ...
  • Sql介紹 與 Sql基礎查詢 SQL SQL也稱為結構化查詢語言(Structure Query Language),是一種用於管理和操作關係型資料庫的標準化電腦語言,SQL語言廣泛應用於各種關係型資料庫系統(RDBMS)如Mysql,Oracle,Microsoft SQL Server等等 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...