news 2026/5/2 12:21:35

Flutter + OpenHarmony 骨架屏组件开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter + OpenHarmony 骨架屏组件开发实战

Flutter + OpenHarmony 骨架屏组件开发实战

欢迎加入开源鸿蒙跨平台社区→ https://openharmonycrosplatform.csdn.net

一、效果展示

📱 运行效果预览

在鸿蒙虚拟机上运行后的实际效果如下:

列表项骨架屏 :

  • 三个列表项占位符

  • 第一项:带头像、单行文本、尾部时间

  • 第二项:带头像、双行文本

  • 第三项:带头像、双行文本

  • 闪烁动画从左到右滑过
    卡片骨架屏 :

  • 第一张卡片:带图片、标题、三行文本

  • 第二张卡片:无图片、标题、两行文本

  • 图片区域占位、文本行占位

  • 圆角卡片容器
    文章骨架屏 :

  • 大标题占位

  • 8行正文占位

  • 图片占位(180px高度)

  • 5行正文占位

  • 模拟真实文章布局
    个人资料骨架屏 :

  • 圆形头像占位(80px)

  • 用户名占位

  • 个人简介占位

  • 三个统计数据占位
    基础元素 :

  • 圆形骨架元素

  • 矩形骨架元素

  • 文本行骨架元素

  • 组合使用示例

🎨 闪烁动画效果

静态骨架: 动态闪烁: ┌────────┐ ┌────────┐ │ │ │░░░░░░░░│ ← 高亮 区 │ │ → │ │ │ │ │ │ └────────┘ └────────┘

🎨 骨架屏类型对比

圆形: ●●● 矩形: ████ 文本: ────

二、组件概述

骨架屏组件是提升应用加载体验的重要手段。在内容加载时显示占位符,让用户感知内容即将到来,避免空白等待的焦虑感。在 OpenHarmony 环境下开发 Flutter 应用时,骨架屏组件需要支持闪烁动画、预设模板、自定义元素等功能。

三、核心功能特性

✅ 闪烁动画效果 - ShimmerEffect实现平滑过渡
✅ 预设骨架屏模板 - 列表项、卡片、文章、个人资料
✅ 三种基础元素 - 圆形、矩形、文本行
✅ 暗色主题适配 - 自动适配深色模式
✅ 高度可定制 - 尺寸、颜色、间距可配置
✅ 性能优化 - AnimationController高效管理

四、技术实现架构

4.1 骨架元素类型枚举

enum SkeletonType { circle, // 圆形 - 头像、图标 rectangle, // 矩形 - 图片、卡片 text // 文本行 - 文字占位 }

4.2 闪烁效果组件

class ShimmerEffect extends StatefulWidget { final Widget child; // 子组件 final Duration duration; // 动画时长 final Color? baseColor; // 基础颜色 final Color? highlightColor; // 高亮颜色 }

4.3 基础骨架盒组件

class SkeletonBox extends StatelessWidget { final double? width; // 宽度 final double? height; // 高度 final SkeletonType type; // 类型 final double borderRadius; // 圆角 final EdgeInsetsGeometry? margin; // 外边距 final EdgeInsetsGeometry? padding; // 内边距 }

4.4 预设骨架屏模板

// 列表项骨架屏 class SkeletonListItem // 卡片骨架屏 class SkeletonCard // 文章骨架屏 class SkeletonArticle // 个人资料骨架屏 class SkeletonProfile

五、ShimmerEffect 闪烁效果实现

5.1 动画控制器

class _ShimmerEffectState extends State<ShimmerEffect> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: widget.duration, )..repeat(); // 无限循环 _animation = Tween<double> (begin: -2, end: 2).animate( CurvedAnimation(parent: _controller, curve: Curves. easeInOutSine), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }

动画原理 :

  • 使用 AnimationController 控制动画
  • repeat() 让动画无限循环
  • Tween 定义动画范围(-2到2)
  • Curves.easeInOutSine 实现平滑过渡

5.2 ShaderMask实现闪烁

@override Widget build(BuildContext context) { final isDark = Theme.of(context). brightness == Brightness.dark; final baseColor = widget. baseColor ?? (isDark ? const Color (0xFF2A2A2A) : const Color (0xFFE0E0E0)); final highlightColor = widget. highlightColor ?? (isDark ? const Color(0xFF3A3A3A) : const Color (0xFFF5F5F5)); return AnimatedBuilder( animation: _animation, builder: (context, child) { return ShaderMask( blendMode: BlendMode. srcATop, shaderCallback: (bounds) { return LinearGradient( begin: Alignment. topLeft, end: Alignment. bottomRight, colors: [ baseColor, highlightColor, baseColor, ], stops: const [0.0, 0.5, 1.0], transform: _SlideGradientTransform (_animation.value), ).createShader(bounds); }, child: widget.child, ); }, ); }

ShaderMask原理 :

  • 使用 LinearGradient 创建渐变
  • 三色渐变:基础色 → 高亮色 → 基础色
  • _SlideGradientTransform 平移渐变
  • BlendMode.srcATop 混合模式

5.3 渐变变换器

class _SlideGradientTransform extends GradientTransform { final double slidePercent; const _SlideGradientTransform (this.slidePercent); @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.translationValues (bounds.width * slidePercent, 0. 0, 0.0); } }

变换原理 :

  • 使用 Matrix4 矩阵变换
  • translationValues 实现水平平移
  • bounds.width * slidePercent 计算平移距离

六、SkeletonBox 基础元素实现

6.1 圆形骨架

case SkeletonType.circle: skeleton = Container( width: width ?? 48, height: height ?? 48, decoration: BoxDecoration( color: baseColor, shape: BoxShape.circle, ), );

6.2 矩形骨架

case SkeletonType.rectangle: skeleton = Container( width: width, height: height ?? 80, decoration: BoxDecoration( color: baseColor, borderRadius: BorderRadius. circular(borderRadius), ), );

6.3 文本行骨架

case SkeletonType.text: skeleton = Container( width: width ?? double.infinity, height: height ?? 14, decoration: BoxDecoration( color: baseColor, borderRadius: BorderRadius. circular(4), ), );

七、预设骨架屏模板实现

7.1 列表项骨架屏

class SkeletonListItem extends StatelessWidget { final bool hasLeading; // 是 否有前导元素 final bool hasTrailing; // 是否 有尾部元素 final int textLines; // 文本 行数 final double leadingSize; // 前导 元素大小 @override Widget build(BuildContext context) { return ShimmerEffect( child: Container( padding: padding ?? const EdgeInsets.symmetric (horizontal: 16, vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (hasLeading) Container( width: leadingSize, height: leadingSize, decoration: BoxDecoration( color: baseColor, shape: BoxShape. circle, ), ), if (hasLeading) const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment. start, children: List. generate(textLines, (index) { return Container( margin: EdgeInsets.only (bottom: index < textLines - 1 ? 8 : 0), width: index == textLines - 1 ? 0.6 : 1.0, // 最后一行较短 height: 14, decoration: BoxDecoration( color: baseColor, borderRadius: BorderRadius. circular(4), ), ); }), ), ), if (hasTrailing) ...[ const SizedBox(width: 12), Container( width: 60, height: 14, decoration: BoxDecoration( color: baseColor, borderRadius: BorderRadius. circular(4), ), ), ], ], ), ), ); } }

7.2 卡片骨架屏

class SkeletonCard extends StatelessWidget { final bool hasImage; // 是否 有图片 final double? imageHeight; // 图片 高度 final int textLines; // 文本 行数 @override Widget build(BuildContext context) { return ShimmerEffect( child: Container( margin: padding ?? const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E1E1E) : Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (hasImage) Container( width: double. infinity, height: imageHeight ?? 150, decoration: BoxDecoration( color: baseColor, borderRadius: const BorderRadius. vertical(top: Radius.circular (12)), ), ), Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment. start, children: [ // 标题占位 Container(width: 0.7, height: 18, ...), // 文本行占位 ...List.generate (textLines, (index) { return Container ( width: index == textLines - 1 ? 0.5 : 1. 0, height: 14, ... ); }), ], ), ), ], ), ), ); } }

7.3 文章骨架屏

class SkeletonArticle extends StatelessWidget { @override Widget build(BuildContext context) { return ShimmerEffect( child: Padding( padding: padding ?? const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 大标题占位 Container(width: 0.8, height: 24, ...), // 8行正文占位 ...List.generate(8, (index) { return Container( margin: EdgeInsets. only(bottom: index == 2 ? 24 : 8), // 第3行后留空 width: index == 2 ? 0.3 : (index == 7 ? 0.6 : 1.0), ... ); }), // 图片占位 Container(width: double. infinity, height: 180, ...), // 5行正文占位 ...List.generate(5, (index) { ... }), ], ), ), ); } }

7.4 个人资料骨架屏

class SkeletonProfile extends StatelessWidget { @override Widget build(BuildContext context) { return ShimmerEffect( child: Padding( padding: padding ?? const EdgeInsets.all(16), child: Column( children: [ // 头像占位 Container(width: 80, height: 80, shape: BoxShape.circle, ...), // 用户名占位 Container(width: 120, height: 18, ...), // 个人简介占位 Container(width: 180, height: 14, ...), // 统计数据占位 Row( mainAxisAlignment: MainAxisAlignment. spaceEvenly, children: List. generate(3, (index) { return Column( children: [ Container (width: 40, height: 18, ...), // 数 值 Container (width: 50, height: 12, ...), // 标 签 ], ); }), ), ], ), ), ); } }

八、使用示例集锦

示例1:基础圆形骨架

SkeletonBox( type: SkeletonType.circle, width: 48, height: 48, )

示例2:矩形骨架

SkeletonBox( type: SkeletonType.rectangle, width: double.infinity, height: 100, borderRadius: 12, )

示例3:文本行骨架

SkeletonBox( type: SkeletonType.text, width: 200, height: 14, )

示例4:列表项骨架屏

SkeletonListItem( hasLeading: true, hasTrailing: true, textLines: 2, leadingSize: 48, )

示例5:卡片骨架屏

SkeletonCard( hasImage: true, imageHeight: 150, textLines: 3, )

示例6:文章骨架屏

SkeletonArticle()

示例7:个人资料骨架屏

SkeletonProfile()

示例8:自定义闪烁效果

ShimmerEffect( duration: const Duration (milliseconds: 2000), baseColor: Colors.grey[300], highlightColor: Colors.grey[100], child: Container( width: 100, height: 100, color: Colors.grey[300], ), )

九、性能优化策略

9.1 动画优化

  • AnimationController :高效管理动画生命周期
  • repeat() :无限循环,无需手动控制
  • dispose() :及时释放资源

9.2 渲染优化

  • ShaderMask :GPU加速的渐变效果
  • AnimatedBuilder :局部重建,避免全局刷新
  • const构造函数 :减少不必要的重建

9.3 内存优化

  • SingleTickerProviderStateMixin :单一动画控制器
  • 及时dispose :避免内存泄漏

十、常见问题解答

Q1: 如何调整闪烁速度?

设置 duration 参数:

ShimmerEffect( duration: const Duration (milliseconds: 2000), child: ..., )

Q2: 如何自定义骨架颜色?

设置 baseColor 和 highlightColor :

ShimmerEffect( baseColor: Colors.grey[300], highlightColor: Colors.grey[100], child: ..., )

Q3: 如何在数据加载时显示骨架屏?

使用条件渲染:

if (isLoading) SkeletonCard() else ActualCard()

Q4: 如何创建自定义骨架屏模板?

组合使用基础元素:

ShimmerEffect( child: Row( children: [ SkeletonBox(type: SkeletonType.circle, width: 40, height: 40), SizedBox(width: 12), Expanded(child: SkeletonBox (type: SkeletonType.text, height: 14)), ], ), )

Q5: 骨架屏会影响性能吗?

骨架屏使用GPU加速的ShaderMask,性能开销很小。建议在数据加载时使用,加载完成后立即移除。
运行效果截图

十一、总结

本文详细介绍了如何在 Flutter + OpenHarmony 环境中开发一个功能完善的骨架屏组件。该组件具备以下技术亮点:

🎯 流畅的闪烁动画 - ShaderMask实现高性能渐变
🎨 丰富的预设模板 - 列表项、卡片、文章、个人资料
⚡ 高效的性能表现 - AnimationController精确控制
🔧 高度可定制 - 颜色、尺寸、间距全面可控

实际应用场景 :

  • 列表数据加载
  • 卡片内容加载
  • 文章详情加载
  • 个人资料加载
  • 图片加载占位
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/2 12:20:40

GeoAgent:基于强化学习的亚米级高精度定位技术解析

1. 项目背景与核心价值 去年在参与某城市智慧交通项目时&#xff0c;我们遇到了一个棘手问题&#xff1a;如何让导航系统在复杂城区环境中更准确地预测用户位置&#xff1f;传统GPS定位在高层建筑密集区经常出现10-20米的漂移&#xff0c;这个痛点直接催生了我们对GeoAgent的研…

作者头像 李华
网站建设 2026/5/2 12:20:27

InfoUtil:优化信息与效用的数据集蒸馏技术

1. 项目概述 InfoUtil是一种创新的数据集蒸馏方法&#xff0c;它通过同时优化信息性和效用两个关键指标&#xff0c;实现了对原始数据集的高效压缩。这种方法能够在保留数据集核心特征的同时&#xff0c;大幅减少数据存储和处理的开销&#xff0c;特别适合需要频繁处理大规模数…

作者头像 李华
网站建设 2026/5/2 12:19:25

如何免费获得专业级音频体验:Windows系统均衡器终极指南

如何免费获得专业级音频体验&#xff1a;Windows系统均衡器终极指南 【免费下载链接】equalizerapo Equalizer APO mirror 项目地址: https://gitcode.com/gh_mirrors/eq/equalizerapo 你是否厌倦了Windows电脑平淡无奇的音频效果&#xff1f;想要在不花一分钱的情况下&…

作者头像 李华
网站建设 2026/5/2 12:15:41

【YOLOv11】080、YOLOv11与大数据平台集成:Spark、Flink流处理实录

一、从产线告警说起 上周三深夜,产线实时质检系统突然告警——视频流延迟从200ms飙升到12秒。运维同事紧急排查,发现不是摄像头故障,也不是模型推理卡顿,问题出在数据流处理层。我们当时用了一个简单的Kafka+Python多进程方案,当上游摄像头数量从30路增加到80路时,系统直…

作者头像 李华
网站建设 2026/5/2 12:10:25

效率革命:用快马生成定制化tabby工具,自动化你的开发工作流

最近在折腾终端工具时&#xff0c;发现一个很有意思的现象&#xff1a;我们每天在终端里重复输入的命令&#xff0c;其实80%都是高度相似的。比如切换项目目录、启动开发服务、运行测试这些操作&#xff0c;每次都要手动敲一遍&#xff0c;既容易出错又浪费时间。于是我用InsCo…

作者头像 李华