Flutter 实现骨架屏
什么是骨架屏
在客户端开发中,我们总是需要等待拿到服务端的响应后,再将内容呈现到页面上,那么在用户发起请求到客户端成功拿到响应的这段时间内,应该在屏幕上呈现点什么好呢?
答案是:骨架屏
那么什么是骨架屏呢,来问下 GPT:
骨架屏(Skeleton Screen)是一种现代的用户界面设计技术,用于提升应用或网站在加载内容时的用户体验。在内容的完全加载和呈现之前,骨架屏提供了一种模糊的预览,形似最终内容的空白版,通常用灰色的块、线条或元素占位符表示。这种设计方法可以有效减少用户的感知等待时间,增强用户的交互感。
功能和用途
- 提高感知性能:骨架屏通过立即显示页面的基本结构(而非旋转的加载图标或完全空白的屏幕),给用户一种内容即将呈现的感觉,这可以使等待时间感觉上更短。
- 改善用户体验:使用骨架屏可以减少用户在加载过程中的焦虑,用户看到界面元素已经在逐步加载,会有更多的耐心等待最终内容的呈现。
- 提供内容加载的视觉提示:骨架屏体现了页面内容加载的进度,可以让用户知道哪些内容即将出现,这样用户就不会感到突然或困惑。
实现方式
骨架屏的实现通常包括以下几个步骤:
- 设计:设计与最终内容布局相似的基本框架,使用灰色或浅色块代表将要加载的各种元素,如文本行、图片、按钮等。
- 前端实现:在前端代码中,可以使用HTML和CSS来创建这些占位符。对于复杂的动态加载内容,可以使用JavaScript或前端框架如React、Vue等来动态控制骨架屏的显示和隐藏。
- 数据加载后的处理:一旦相应的数据加载完成,骨架屏应被实际内容替换。这通常涉及到监听数据加载的完成事件,然后更新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
- 该 package 的文档在这: https://pub.dev/packages/shimmer
其作用是为我们的元素加上 闪动 流光 效果,类似于一道光照射到一把光滑的宝剑上,随着宝剑角度发生变化 光的反射发生位移的现象
我们使用它的 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 包裹整个
GridView或ListView比包裹单个的Card效果要更好哟~
风险
经过测试发现 在不同的设备上、或者使用了自定义字体,▆▆▆ 之间会出现微小间距,无论将 fontWeight 设置为多大都无法避免,这时只能将 ▆ 方案换为带颜色的 Container 来解决。不过 SkeletonScreen 在页面停留的时间通常不会太长,这点就要看团队内部的取舍了
Flutter 实现骨架屏的更多相关文章
- Vue单页面骨架屏实践
github 地址: VV-UI/VV-UI 演示地址: vv-ui 文档地址:skeleton 关于骨架屏介绍 骨架屏的作用主要是在网络请求较慢时,提供基础占位,当数据加载完成,恢复数据展示.这样给 ...
- 《前端之路》之 前端图片 类型 & 优化 & 预加载 & 懒加载 & 骨架屏
目录 09: 前端图片 类型 & 优化 & 预加载 & 懒加载 & 骨架屏 09: 前端图片 类型 & 优化 & 预加载 & 懒加载 & ...
- Skeleton Screen -- 骨架屏--应用
案例:使用 现已经在支付的项目使用 用户体验一直是前端开发需要考虑的重要部分,在数据请求时常见到锁屏的loading动画,而现在越来越多的产品倾向于使用Skeleton Screen Loading( ...
- Vue 项目骨架屏注入与实践
作为与用户联系最为密切的前端开发者,用户体验是最值得关注的问题.关于页面loading状态的展示,主流的主要有loading图和进度条两种.除此之外,越来越多的APP采用了“骨架屏”的方式去展示未加载 ...
- 微信小程序 - 深度定义骨架屏(提示)
此举每个页面必须创建对应的css样式,比较麻烦(但非常准确),推荐使用组件化的skeleton组件 原理很简单:知晓一下this.setData原理,就OK了,可能大家会因此了解到全屏加载loadin ...
- 微信小程序 - 骨架屏
骨架屏 - “与其等待网络加载,不如提前给点暗示” 注:不适用复杂交互效果 演示 示例解释以及使用全在index.wxml中,观看需了解组件使用. 示例下载:微信小程序-骨架屏演示
- Vue项目骨架屏注入实践
相比于早些年前后端代码紧密耦合.后端工程师还得写前端代码的时代,如今已发展到前后端分离,这种开发方式大大提升了前后端项目的可维护性与开发效率,让前后端工程师关注于自己的主业.然而在带来便利的同时,也带 ...
- vue搭建骨架屏步骤配置
1.什么是骨架屏幕? 在页面加载数据之前,有一段空白时间,要么用loading加载,要么就用骨架屏. 在开发webapp的时候总是会受到首屏加载时间过长的影响,主流的解决方法是在载入完成之前显示loa ...
- Vue页面骨架屏(二)
实现思路 参考原文中在构建时使用 Vue 预渲染骨架屏一节介绍的思路,我将骨架屏也看成路由组件,在构建时使用 Vue 预渲染功能,将骨架屏组件的渲染结果 HTML 片段插入 HTML 页面模版的挂载点 ...
- Vue页面骨架屏(一)
在开发webapp的时候总是会受到首屏加载时间过长的影响,主流的解决方法是在载入完成之前显示loading图效果,而一些大公司会配置一套服务端渲染的架构来解决这个问题.考虑到ssr所要解决的一系列问题 ...
随机推荐
- LM Studio + open-webui 快速本地部署大语言模型
目录 一.前言 二.环境准备 三.安装设置 四.下载模型并运行 五.配置 open-webui 写在结尾 一.前言 自 OpenAi 发布 ChatGPT 对话性大语言模型,AI 这两年发展迎来爆发, ...
- SpringBoot实战:Spring Boot接入Security权限认证服务
引言 Spring Security 是一个功能强大且高度可定制的身份验证和访问控制的框架,提供了完善的认证机制和方法级的授权功能,是一个非常优秀的权限管理框架.其核心是一组过滤器链,不同的功能经由不 ...
- 「模拟赛」暑期集训CSP提高模拟4(7.21)
很祭的一次比赛,啥也不会. 题目列表: A.White and Black B.White and White C.Black and Black D.Black and White A.White ...
- 2023/4/22 SCRUM个人博客
1.我昨天的任务 学习如何使用QTdesign,并完善UI 2.遇到了什么困难 在QTable上无法理解前后端互通·的问题 3.我今天的任务 学习Qt知识QTableWidgetItem完善Pyqt5 ...
- Linux安装软件命令详解
Linux安装软件命令详解 目录 一.deb包的简介.安装及卸载步骤 二.rpm包的简介.安装及卸载步骤 三.AppImage包的简介.执行步骤 四.tar.gz.tar.bz2源代码包的简介.安装及 ...
- 国产AI训练卡,对标美国NVIDIA公司的A100,华为昇腾Atlas 300T A2(Ascend 910B4)高性能GPU/NPU/AI推理/国产计算/信创训练卡 —— 电商平台已开售
China has successfully achieved the localization of AI chips, breaking through the technological res ...
- python语言下的迷宫游戏的实现猜想
由于本人是研究AI的,尤其是AI的强化学习方向,有时候就会对一些小游戏环境的实现有几分兴趣,因为刚看了有关reinforcement learning解决maze游戏的论文,于是就突发奇想的对这个ma ...
- 国产显卡如何正确打开 —— Windows平台下使用驱动精灵为国产显卡更新驱动(兆芯平台)
买了一个国产的电脑,全国产,CPU慢些也就忍了,软件兼容性差.稳定性差也忍了,大不了就用来上网看电影嘛,关键问题是这个国产显卡放电影居然有些卡,播放电影的时候存在明显的卡顿感,这简直是把国产电脑在我脑 ...
- 【转载】 MPP大规模并行处理架构详解
本文来自博客园,作者:五分钟学大数据 原文链接:https://www.cnblogs.com/itlz/p/14998858.html =============================== ...
- Mongolia地区民间风俗的一些理解
声明:本文的内容为自己学习历史后的一些个人理解,其中内容的真实性并未考证. 总所周知,Mongolia地区有内外之分现在,但是以前均为我国领土,后来由于种种历史原因导致外Mongolia分离了出去,这 ...