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 StateShimmerEffect with SingleTickerProviderStateMixin { late AnimationController _controller; late Animationdouble _animation; override void initState() { super.initState(); _controller AnimationController( vsync: this, duration: widget.duration, )..repeat(); // 无限循环 _animation Tweendouble (begin: -2, end: 2).animate( CurvedAnimation(parent: _controller, curve: Curves. easeInOutSine), ); } override void dispose() { _controller.dispose(); super.dispose(); } }动画原理 使用 AnimationController 控制动画repeat() 让动画无限循环Tween 定义动画范围-2到2Curves.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精确控制 高度可定制 - 颜色、尺寸、间距全面可控实际应用场景 列表数据加载卡片内容加载文章详情加载个人资料加载图片加载占位