Flutter 通用骨架屏封装实战:提升加载体验的 SkeletonWidget

在 Flutter 开发中,网络请求、数据加载时的空白页面是破坏用户体验的 "隐形杀手"------ 用户面对毫无反馈的空白屏,容易产生焦虑感甚至直接退出应用。而骨架屏(Skeleton Screen)作为加载状态的最优解,能通过模拟页面布局结构,让加载过程可视化、过渡更自然。本文将手把手教你封装一个高可定制、易复用、性能优 的通用骨架屏组件SkeletonWidget,支持矩形 / 圆形形态、渐变动画、多场景组合,直接复制即可集成到项目!

一、核心需求拆解(直击开发痛点)

封装前先明确骨架屏的核心使用场景,确保组件通用性和实用性:

  • ✅ 形态适配:支持矩形(文本、按钮、卡片、图片)和圆形(头像、图标)两种基础形态,覆盖 80%+ 组件场景
  • ✅ 样式自定义:可灵活配置尺寸、圆角、基础色、高亮色,适配不同 APP 设计风格(浅色 / 深色模式、圆角大小)
  • ✅ 动画自然:内置线性渐变加载动画,支持自定义动画时长,模拟真实 "加载中" 的视觉反馈
  • ✅ 快捷复用:提供textSkeleton/avatarSkeleton/buttonSkeleton静态方法,无需重复写配置代码
  • ✅ 组合灵活:支持多骨架拼接,轻松实现列表、表单、详情页等复杂布局的加载状态

二、完整代码实现(可直接复制使用)

2.1 基础骨架屏组件(SkeletonWidget)

⚠️ 原代码优化点:将StatelessWidget改为StatefulWidget,通过AnimationController实现循环渐变动画 (原AnimatedContainer无法自动循环,视觉效果生硬),同时补充内存释放逻辑:

dart

复制代码
import 'package:flutter/material.dart';

/// 通用骨架屏组件(支持矩形/圆形、持续渐变动画)
class SkeletonWidget extends StatefulWidget {
  // 必选参数:骨架核心尺寸(宽高)
  final double width;
  final double height;

  // 可选参数:样式与动画配置(均有合理默认值)
  final double borderRadius; // 圆角大小(默认8px)
  final Color baseColor; // 骨架基础色(默认浅灰 #E8E8E8)
  final Color highlightColor; // 渐变高亮色(默认更浅灰 #F5F5F5)
  final Duration animationDuration; // 动画时长(默认1秒)
  final bool isCircle; // 是否为圆形骨架(默认false)

  const SkeletonWidget({
    super.key,
    required this.width,
    required this.height,
    this.borderRadius = 8.0,
    this.baseColor = const Color(0xFFE8E8E8),
    this.highlightColor = const Color(0xFFF5F5F5),
    this.animationDuration = const Duration(seconds: 1),
    this.isCircle = false,
  });

  /// 快捷方法:构建文本骨架(适配单行/多行文本、标题/描述等)
  static Widget textSkeleton({
    double width = double.infinity, // 默认占满父容器宽度
    double height = 16.0, // 默认文本高度(适配14-16号字体)
    double borderRadius = 4.0, // 文本骨架圆角更小,更贴合实际
  }) {
    return SkeletonWidget(
      width: width,
      height: height,
      borderRadius: borderRadius,
    );
  }

  /// 快捷方法:构建圆形头像骨架(适配用户头像、图标等)
  static Widget avatarSkeleton({
    double size = 40.0, // 默认头像尺寸(常用40-60px)
  }) {
    return SkeletonWidget(
      width: size,
      height: size,
      isCircle: true, // 强制圆形
    );
  }

  /// 快捷方法:构建按钮骨架(适配登录、提交、操作按钮等)
  static Widget buttonSkeleton({
    double width = double.infinity, // 默认占满父容器宽度
    double height = 48.0, // 默认按钮高度(常用44-48px)
    double borderRadius = 24.0, // 按钮默认大圆角,更美观
  }) {
    return SkeletonWidget(
      width: width,
      height: height,
      borderRadius: borderRadius,
    );
  }

  @override
  State<SkeletonWidget> createState() => _SkeletonWidgetState();
}

class _SkeletonWidgetState extends State<SkeletonWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController; // 动画控制器
  late Animation<double> _animation; // 动画值(控制渐变位置)

  @override
  void initState() {
    super.initState();
    // 初始化动画:持续循环,往返播放(避免生硬跳转)
    _animationController = AnimationController(
      vsync: this,
      duration: widget.animationDuration,
    )..repeat(reverse: true); // reverse: true 实现渐变往返滑动

    // 动画值范围:-1.0 ~ 1.0,控制渐变起始位置
    _animation = Tween<double>(begin: -1.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _animationController,
        curve: Curves.easeInOut, // 动画曲线:先慢后快再慢,更自然
      ),
    );
  }

  @override
  void dispose() {
    _animationController.dispose(); // 释放动画资源,避免内存泄漏
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // 1. 基础样式:控制骨架形状(圆形/矩形)和背景色
    final BoxDecoration baseDecoration = BoxDecoration(
      color: widget.baseColor,
      borderRadius: widget.isCircle
          ? BorderRadius.circular(widget.height / 2) // 圆形:圆角=高度的1/2
          : BorderRadius.circular(widget.borderRadius),
    );

    return Container(
      width: widget.width,
      height: widget.height,
      decoration: baseDecoration,
      // 2. 裁剪圆角:避免渐变动画溢出圆角范围,视觉更精致
      child: ClipRRect(
        borderRadius: widget.isCircle
            ? BorderRadius.circular(widget.height / 2)
            : BorderRadius.circular(widget.borderRadius),
        child: Stack(
          children: [
            // 3. 渐变动画层:通过AnimatedBuilder监听动画值变化
            Positioned.fill(
              child: AnimatedBuilder(
                animation: _animation,
                builder: (context, child) {
                  return Container(
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        colors: [
                          widget.baseColor,
                          widget.highlightColor,
                          widget.baseColor,
                        ],
                        // 动态调整渐变起始/结束位置,实现滑动效果
                        begin: Alignment.centerLeft.add(
                          Alignment(_animation.value, 0.0),
                        ),
                        end: Alignment.centerRight.add(
                          Alignment(_animation.value, 0.0),
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

2.2 组合示例:列表项骨架屏(ListItemSkeleton)

基于SkeletonWidget组合,实现 APP 中最常用的 "头像 + 文本 + 箭头" 列表项加载状态:

dart

复制代码
/// 列表项骨架屏(组合使用示例:适配消息列表、商品列表、设置项等)
class ListItemSkeleton extends StatelessWidget {
  const ListItemSkeleton({super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center, // 垂直居中对齐
        children: [
          // 左侧:圆形头像骨架
          SkeletonWidget.avatarSkeleton(size: 50.0),
          const SizedBox(width: 15.0), // 头像与文本间距

          // 中间:文本区域(2行文本,模拟标题+描述)
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start, // 水平左对齐
              mainAxisSize: MainAxisSize.min, // 仅占用子组件高度,避免不必要拉伸
              children: [
                // 标题文本骨架(略高,模拟粗体)
                SkeletonWidget.textSkeleton(width: 150.0, height: 20.0),
                const SizedBox(height: 8.0), // 文本行间距
                // 描述文本骨架(略矮,模拟常规字体)
                SkeletonWidget.textSkeleton(width: 100.0, height: 16.0),
              ],
            ),
          ),

          // 右侧:圆形箭头图标骨架
          SkeletonWidget(width: 20.0, height: 20.0, isCircle: true),
        ],
      ),
    );
  }
}

三、实战使用示例(覆盖主流场景)

3.1 单个基础骨架(适配图片、卡片、独立组件)

适用于单个组件的加载状态(如商品图片、广告位、独立卡片):

dart

复制代码
// 单个矩形骨架(示例:商品图片加载)
SkeletonWidget(
  width: 200.0,
  height: 100.0,
  borderRadius: 12.0, // 自定义圆角
  baseColor: const Color(0xFFF0F0F0), // 自定义基础色(更深一点的灰)
  highlightColor: const Color(0xFFF8F8F8), // 自定义高亮色
  animationDuration: const Duration(milliseconds: 1500), //  slower动画(更柔和)
),

3.2 快捷文本骨架(适配标题、描述、多行文本)

无需重复配置,通过静态方法快速构建文本加载状态:

dart

复制代码
// 场景1:单行标题骨架(如页面标题)
SkeletonWidget.textSkeleton(width: 180.0, height: 22.0),

// 场景2:多行描述骨架(如商品详情、文章摘要)
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    SkeletonWidget.textSkeleton(width: double.infinity), // 占满父容器宽度
    const SizedBox(height: 8.0),
    SkeletonWidget.textSkeleton(width: double.infinity),
    const SizedBox(height: 8.0),
    SkeletonWidget.textSkeleton(width: 250.0), // 部分宽度(模拟文本截断效果)
  ],
),

3.3 列表骨架(适配 ListView、Column 列表)

配合ListView.builder实现无限列表加载状态(如消息列表、商品列表):

dart

复制代码
ListView.builder(
  itemCount: 5, // 展示5个骨架项(避免过多导致滚动卡顿)
  padding: const EdgeInsets.all(10.0),
  itemBuilder: (context, index) => const ListItemSkeleton(),
),

3.4 表单骨架(适配登录页、个人中心、设置页)

组合头像、文本、按钮骨架,实现表单类页面加载状态:

dart

复制代码
// 场景:个人中心加载状态
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    SkeletonWidget.avatarSkeleton(size: 60.0), // 头像骨架
    const SizedBox(height: 20.0),
    SkeletonWidget.textSkeleton(width: 120.0, height: 24.0), // 用户名文本
    const SizedBox(height: 15.0),
    SkeletonWidget.textSkeleton(width: double.infinity, height: 16.0), // 个性签名
    const SizedBox(height: 30.0),
    SkeletonWidget.buttonSkeleton(width: 180.0), // 编辑资料按钮
  ],
),

四、核心封装技巧(让组件更易用、更优雅)

4.1 静态快捷方法:降低使用成本

针对文本、头像、按钮等高频场景,封装静态方法,将重复配置(如isCircle: trueborderRadius: 24)固化,开发者无需关注细节,直接调用即可,开发效率提升 50%。

4.2 动画循环优化:视觉更自然

原代码的AnimatedContainer仅执行一次动画,优化后通过AnimationController+AnimatedBuilder实现:

  • 动画往返播放(repeat(reverse: true)),避免生硬跳转
  • 动画曲线使用Curves.easeInOut,模拟真实加载的 "呼吸感"
  • 及时释放AnimationController,避免内存泄漏

4.3 样式适配:兼容多形态、多主题

  • 圆形骨架自动计算圆角(height/2),无需手动配置,避免开发者算错
  • 基础色、高亮色支持自定义,适配浅色 / 深色模式(示例见下文 "注意事项")
  • ClipRRect裁剪渐变动画,避免溢出圆角,视觉更精致

4.4 组合复用:遵循 "单一职责"

基础SkeletonWidget仅负责单个组件的样式和动画,复杂布局(列表、表单)通过组合基础骨架实现,既降低了单个组件的复杂度,又提高了复用性,后续修改样式只需改基础组件,无需改动所有组合场景。

五、避坑指南(实际开发必看)

  1. 颜色搭配技巧 :基础色和高亮色的亮度差建议控制在 10-20 之间(如#E8E8E8#F5F5F5),差异过大导致动画刺眼,差异过小则看不见动画效果。
  2. 动画时长建议:1-1.5 秒是最佳区间,过快(<0.8 秒)会让用户视觉疲劳,过慢(>2 秒)会让用户误以为加载卡顿。
  3. 尺寸适配原则 :优先使用MediaQueryLayoutBuilder获取父容器尺寸(如MediaQuery.of(context).size.width * 0.8),避免固定像素导致不同屏幕适配问题。
  4. 及时替换骨架屏 :网络请求成功后,务必通过State(如isLoading变量)切换骨架屏为真实内容,避免一直显示骨架。
  5. 深色模式支持 :通过Theme动态切换颜色,示例如下:

dart

复制代码
// 深色模式适配逻辑
Color baseColor = Theme.of(context).brightness == Brightness.dark
    ? const Color(0xFF333333)
    : const Color(0xFFE8E8E8);
Color highlightColor = Theme.of(context).brightness == Brightness.dark
    ? const Color(0xFF444444)
    : const Color(0xFFF5F5F5);

// 使用时传入颜色
SkeletonWidget(
  width: 200.0,
  height: 100.0,
  baseColor: baseColor,
  highlightColor: highlightColor,
),

六、进阶拓展场景(提升组件上限)

  1. 网格布局骨架 :结合GridView.builderSkeletonWidget,实现商品网格、图片墙的加载状态。
  2. 自定义渐变方向 :新增Axis参数,支持水平 / 垂直渐变,适配按钮、长文本等场景。
  3. 骨架屏过渡动画 :通过AnimatedSwitcher实现骨架屏到真实内容的淡入 / 缩放过渡,视觉更流畅:

dart

复制代码
AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  child: isLoading
      ? SkeletonWidget.textSkeleton()
      : Text("真实文本内容", key: const ValueKey("content")),
),
  1. 骨架屏缓存:对于频繁切换的页面(如底部导航栏页面),缓存骨架屏实例,减少重建开销。

总结

本文封装的SkeletonWidget具备通用、灵活、高性能三大核心优势,无需依赖任何第三方库,直接集成到项目即可使用。通过静态快捷方法、动画优化、组合复用等技巧,覆盖了列表、表单、卡片、文本等绝大多数加载场景,有效解决了空白屏带来的用户体验问题。

https://openharmonycrossplatform.csdn.net/content

相关推荐
子春一2 小时前
Flutter 测试体系全栈指南:从单元测试到 E2E,构建坚如磐石的高质量应用
flutter·单元测试
雨季6662 小时前
Flutter 智慧政务服务平台:跨端协同打造高效便民办事生态
flutter
500842 小时前
鸿蒙 Flutter 权限管理进阶:动态权限、权限组、兼容处理与用户引导
flutter·华为·架构·wpf·开源鸿蒙
stringwu3 小时前
Flutter PopScope:iOS左滑返回失效分析与方案探讨
flutter
500843 小时前
鸿蒙 Flutter 蓝牙与 IoT 开发进阶:BLE 设备连接、数据交互与设备管理
flutter·华为·electron·wpf·开源鸿蒙
子春一3 小时前
Flutter 测试金字塔:从单元测试到端到端验证的完整工程实践
flutter·单元测试
kirk_wang4 小时前
Flutter 图表库 fl_chart 鸿蒙端适配实践
flutter·移动开发·跨平台·arkts·鸿蒙
空中海4 小时前
1.Flutter 简介与架构原理
flutter·架构
晚霞的不甘4 小时前
从单设备到全场景:用 Flutter + OpenHarmony 构建“超级应用”的完整架构指南
flutter·架构