首页 > 其他分享 >Flutter 实现骨架屏

Flutter 实现骨架屏

时间:2024-09-30 19:02:23浏览次数:10  
标签:const 实现 骨架 StarCard Colors child Flutter 加载

Flutter 实现骨架屏

  合集 - flutter(1)   1.Flutter 实现骨架屏09-30 收起  

什么是骨架屏

在客户端开发中,我们总是需要等待拿到服务端的响应后,再将内容呈现到页面上,那么在用户发起请求到客户端成功拿到响应的这段时间内,应该在屏幕上呈现点什么好呢?

答案是:骨架屏

那么什么是骨架屏呢,来问下 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 包裹整个 GridView 或 ListView 比包裹单个的 Card 效果要更好哟~

风险

经过测试发现 在不同的设备上、或者使用了自定义字体,▆▆▆ 之间会出现微小间距,无论将 fontWeight 设置为多大都无法避免,这时只能将  方案换为带颜色的 Container 来解决。不过 SkeletonScreen 在页面停留的时间通常不会太长,这点就要看团队内部的取舍了

标签:const,实现,骨架,StarCard,Colors,child,Flutter,加载
From: https://www.cnblogs.com/sexintercourse/p/18442336

相关文章

  • 【C++ STL】深入理解string类的底层实现
    string类的模拟实现一.string的构造与析构函数1.普通构造函数与析构函数2.拷贝构造的浅拷贝所带来的问题3.如何实现深拷贝二.运算符重载1.赋值运算符重载2.大小比较相关的运算符重载三.迭代器的实现四.string常用操作的实现1.静态const成员npos的定义2.插入操作3.查找......
  • .Net 依赖注入深入探索,做一个DI拓展,实现一个简易灵活的 自动依赖注入框架
    .Net依赖注入深入探索,做一个DI拓展,实现一个简易灵活的自动依赖注入框架 合集-.Net框架探索(2) 1..NetWeb项目中,实现轻量级本地事件总线框架09-272.依赖注入深入探索,做一个DI拓展,实现一个简易灵活的自动依赖注入框架09-30收起 一、依赖注入相关知识1.1......
  • Vue3基于SpringBoot的高校学生实习综合服务平台设计与实现
    目录功能和开发技术介绍具体实现截图开发核心技术介绍:技术创新点vue3和vue2的区别:核心代码部分展示非功能需求分析系统开发流程系统运行步骤软件测试源码获取功能和开发技术介绍本课题拟采用主流的MVC架构、开发工具idea、java语言编程、MySQL数据库技术、Vue.js技......
  • vue基于SpringBoot的在线个性化电影推荐与观影社交平台的设计与实现
    目录毕设制作流程功能和技术介绍系统实现截图开发核心技术介绍:使用说明开发步骤编译运行代码执行流程核心代码部分展示可行性分析软件测试详细视频演示源码获取毕设制作流程(1)与指导老师确定系统主要功能;(2)做需求分析及功能模块划分;(3)指导老师通过后,设计出用例图,E-R......
  • codeforces round 975 E(div.2)(lambda表达式实现dfs,min_element函数,一定要优化重复的
    解题历程:看到题目要求要用最少的消除次数让所有叶子深度相同,通过观察,那么就只需要将所有深度都尝试一遍就行了,可是我当时没多想就用dfs记录所有节点的深度,单独将所有叶子和该叶子的深度存起来,记录最大的深度,从最大深度尝试到深度0,对于深度小于当前尝试深度的叶子,用dfs的方式将与......
  • 【代码实现】opencv 高斯模糊和pytorch 高斯模糊
    wiki百科GaussianBlur,也叫高斯平滑,是在AdobePhotoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次。opencv实现opencv实现高斯滤波有两种方式,1、是使用自带的cv2.GaussianBlur,2、自己构造高斯kernel,然后调用cv......
  • 用C/C++构建自己的Redis——第五章、Redis中的AVL树实现
    用C/C++构建自己的Redis——第五章、Redis中的AVL树实现文章目录用C/C++构建自己的Redis——第五章、Redis中的AVL树实现前言一、键值对集查询概念1.1键值对集合查询1.2数据结构排序的复习排序数组(SortedArrays)树形数据结构(TreeDataStructures)通过随机性平衡(Balan......
  • Winform控件优化之圆角按钮【各种实现中的推荐做法】
    简介: Windows11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化...尝试介绍很常见的圆角效果,通过重写控件的OnPaint方法实现绘制,并在后面进一步探索对应的优化和可能的问题Windows11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化,只是这是默认的行为......
  • vue2实现字体修改(全局/局部字体引入修改)/添加文字渐变色样式
    1.创建一个全局CSS文件创建一个单独的CSS文件,例如fonts.css,然后在main.js中引入。fonts.css文件内容:@font-face{font-family:'youshebiaotihei';src:url('../../fonts/youshebiaotihei.ttf')format('truetype');/*引用字体,但非全局使用*/font-wei......
  • BERT训练之数据集处理(代码实现)
    目录1读取文件数据 2.生成下一句预测任务的数据 3.预测下一个句子 4.生成遮蔽语言模型任务的数据 5.从词元中得到遮掩的数据 6.将文本转化为预训练数据集7.封装函数类8.调用importosimportrandomimporttorchimportdltools1读取文件数据def_read_wik......