Flutter占位符展示插件mr_skeleton的使用

在Flutter开发中,占位符展示插件mr_skeleton可以帮助开发者快速实现加载状态的UI效果。通过使用该插件,可以轻松地为应用添加骨架屏(Skeleton Screen)效果,从而提升用户体验。

使用步骤

以下是一个完整的示例,展示如何使用mr_skeleton插件来创建一个带有动态调整宽度的侧边栏和顶部栏的骨架屏。


完整示例代码

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mr_skeleton/mr_skeleton.dart';
import 'package:resonant_ui/resonant_ui.dart';
import 'package:syncfusion_flutter_sliders/sliders.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const SkeletonPage(),
    );
  }
}

class SkeletonPage extends StatelessWidget {
  final bool isDebug = false;

  const SkeletonPage({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    Get.put(SkeletonDemoStateController(isDebug: isDebug));

    return Obx(() {
      final controller = SkeletonDemoStateController.to;
      final isLeftExpanded = controller.isLeftSideMenuExpanded();
      final isRightExpanded = controller.isRightSideMenuExpanded();

      final expandedLeftWidth = controller.expandedLeftWidth();
      final collapsedLeftWidth = controller.collapsedLeftWidth();
      final expandedRightWidth = controller.expandedRightWidth();

      final isDesktop = controller.isDesktop();

      return MRSkeleton(
        isLeftExpanded: isLeftExpanded,
        isRightExpanded: isRightExpanded,
        expandedLeftWidth: expandedLeftWidth,
        collapsedLeftWidth: collapsedLeftWidth,
        expandedRightWidth: expandedRightWidth,
        onDesktopBreakpointChanged: (isDesktop) {
          controller.isDesktop(isDesktop);
        },
        left: _LeftWidget(
          isCollapsed: !isLeftExpanded,
        ),
        top: const TopBar(
          children: [
            _CircularLetter(
              letter: '1',
              color: Colors.purple,
            ),
            _CircularLetter(
              letter: '2',
              color: Colors.orange,
            ),
            Spacer(),
            _CircularLetter(
              letter: '3',
              color: Colors.yellow,
            ),
          ],
        ),
        right: const _RightWidget(),
        body: _SkeletonDemoInteractionBox(
          isDesktop: isDesktop,
        ),
        mobileBuilder: (child) {
          return Scaffold(
            body: SafeArea(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: child,
              ),
            ),
            appBar: AppBar(
              elevation: 0,
              automaticallyImplyLeading: false,
              title: const Text(
                'App Bar',
                style: TextStyle(color: Colors.black),
              ),
              leading: const _CircularLetter(
                letter: 'W',
                color: Colors.pink,
              ),
              backgroundColor: Colors.transparent,
              actions: const [
                _CircularLetter(
                  letter: '1',
                  color: Colors.purple,
                  padding: EdgeInsets.all(8),
                ),
                _CircularLetter(
                  letter: '2',
                  color: Colors.orange,
                  padding: EdgeInsets.all(8),
                ),
              ],
            ),
            bottomNavigationBar: BottomNavigationBar(
              items: const [
                BottomNavigationBarItem(
                  icon: Icon(Icons.call),
                  label: 'Calls',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.camera),
                  label: 'Camera',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.chat),
                  label: 'Chats',
                ),
              ],
            ),
          );
        },
      );
    });
  }
}

class _SkeletonDemoInteractionBox extends StatelessWidget {
  final bool isDesktop;

  const _SkeletonDemoInteractionBox({Key? key, required this.isDesktop})
      : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    var children = !isDesktop
        ? [
            const Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                  'You are on a non-desktop version, expand your screen size or change to a web browser'),
            ),
          ]
        : [
            Obx(() {
              return SwitchListTile(
                title: const Text('Left expanded'),
                value: SkeletonDemoStateController.to.isLeftSideMenuExpanded(),
                onChanged: (newValue) {
                  SkeletonDemoStateController.to
                      .isLeftSideMenuExpanded(newValue);
                },
              );
            }),
            const Divider(),
            Obx(() {
              return SwitchListTile(
                title: const Text('Right expanded'),
                value: SkeletonDemoStateController.to.isRightSideMenuExpanded(),
                onChanged: (newValue) {
                  SkeletonDemoStateController.to
                      .isRightSideMenuExpanded(newValue);
                },
              );
            }),
            const Divider(),
            Obx(() {
              return Column(
                children: [
                  SwitchListTile(
                    title: const Text('Collapsed Left Width'),
                    value: SkeletonDemoStateController.to
                        .enableCollapsedLeftWidthEdition(),
                    onChanged: (newValue) {
                      SkeletonDemoStateController.to
                          .enableCollapsedLeftWidthEdition(newValue);
                    },
                  ),
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: SfSlider(
                      value: SkeletonDemoStateController.to.collapsedLeftWidth(),
                      min: 60,
                      max: 300,
                      interval: 40,
                      showTicks: true,
                      showLabels: true,
                      enableTooltip: true,
                      onChanged: SkeletonDemoStateController.to
                              .enableCollapsedLeftWidthEdition()
                          ? (newValue) {
                              SkeletonDemoStateController.to
                                  .collapsedLeftWidth(newValue);
                            }
                          : null,
                    ),
                  )
                ],
              );
            }),
            const Divider(),
            Obx(() {
              return Column(
                children: [
                  SwitchListTile(
                    title: const Text('Expanded Left Width'),
                    value: SkeletonDemoStateController.to
                        .enableExpandedLeftWidthEdition(),
                    onChanged: (newValue) {
                      SkeletonDemoStateController.to
                          .enableExpandedLeftWidthEdition(newValue);
                    },
                  ),
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: SfSlider(
                      value: SkeletonDemoStateController.to.expandedLeftWidth(),
                      min: 60,
                      max: 300,
                      interval: 40,
                      showTicks: true,
                      showLabels: true,
                      enableTooltip: true,
                      onChanged: SkeletonDemoStateController.to
                              .enableExpandedLeftWidthEdition()
                          ? (newValue) {
                              SkeletonDemoStateController.to
                                  .expandedLeftWidth(newValue);
                            }
                          : null,
                    ),
                  )
                ],
              );
            }),
            const Divider(),
            Obx(() {
              return Column(
                children: [
                  SwitchListTile(
                    title: const Text('Expanded Right Width'),
                    value: SkeletonDemoStateController.to
                        .enableExpandedRightWidthEdition(),
                    onChanged: (newValue) {
                      SkeletonDemoStateController.to
                          .enableExpandedRightWidthEdition(newValue);
                    },
                  ),
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: SfSlider(
                      value:
                          SkeletonDemoStateController.to.expandedRightWidth(),
                      min: 60,
                      max: 460,
                      interval: 40,
                      showTicks: true,
                      showLabels: true,
                      enableTooltip: true,
                      onChanged: SkeletonDemoStateController.to
                              .enableExpandedRightWidthEdition()
                          ? (newValue) {
                              SkeletonDemoStateController.to
                                  .expandedRightWidth(newValue);
                            }
                          : null,
                    ),
                  )
                ],
              );
            }),
          ];

    return Padding(
      padding: const EdgeInsets.all(16),
      child: SizedBox(
        width: double.infinity,
        height: double.infinity,
        child: Center(
          child: ConstrainedRoundedCard(
            title: 'Skeleton demo',
            children: children,
          ),
        ),
      ),
    );
  }
}

class TopBar extends StatelessWidget {
  final List<Widget> children;

  const TopBar({Key? key, required this.children}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ColorDebuggable(
      color: Colors.amber.shade300,
      child: Container(
        height: 60,
        decoration: BoxDecoration(
          border: Border(
            bottom: BorderSide(
              color: Colors.grey.shade300,
              width: 0.5,
            ),
          ),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: children,
        ),
      ),
    );
  }
}

class _LeftWidget extends StatelessWidget {
  final bool isCollapsed;

  const _LeftWidget({
    Key? key,
    this.isCollapsed = false,
  }) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ColorDebuggable.amber(
      child: Container(
        height: double.infinity,
        decoration: BoxDecoration(
          border: Border(
            right: BorderSide(
              color: Colors.grey.shade300,
              width: 0.5,
            ),
          ),
        ),
        child: Column(
          crossAxisAlignment: isCollapsed
              ? CrossAxisAlignment.center
              : CrossAxisAlignment.start,
          children: [
            Container(
              width: double.infinity,
              height: 60,
              decoration: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                    color: Colors.grey.shade300,
                    width: 0.5,
                  ),
                ),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const _CircularLetter(
                    letter: 'L',
                    color: Colors.cyanAccent,
                  ),
                  if (!isCollapsed) const Spacer(),
                ],
              ),
            ),
            const _CircularLetter(
              letter: 'A',
              color: Colors.red,
            ),
            const _CircularLetter(
              letter: 'B',
              color: Colors.green,
            ),
            const Spacer(),
            const _CircularLetter(
              letter: 'C',
              color: Colors.blue,
            ),
          ],
        ),
      ),
    );
  }
}

class _RightWidget extends StatelessWidget {
  const _RightWidget({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ColorDebuggable.amber(
      child: Container(
        height: double.infinity,
        decoration: BoxDecoration(
          border: Border(
            left: BorderSide(
              color: Colors.grey.shade300,
              width: 0.5,
            ),
          ),
        ),
        child: Column(
          children: [
            Container(
              width: double.infinity,
              height: 60,
              decoration: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                    color: Colors.grey.shade300,
                    width: 0.5,
                  ),
                ),
              ),
              child: Row(
                children: const [
                  _CircularLetter(
                    letter: 'W',
                    color: Colors.pink,
                  ),
                  Spacer(),
                ],
              ),
            ),
            const Expanded(
              child: Center(
                child: Text('Right'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _CircularLetter extends StatelessWidget {
  const _CircularLetter({
    Key? key,
    this.onTap,
    this.padding,
    required this.letter,
    this.color = Colors.transparent,
  }) : super(key: key);

  final Function()? onTap;
  final EdgeInsets? padding;
  final String letter;
  final Color? color;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ColorDebuggable.blue(
      child: Container(
        padding: padding ?? const EdgeInsets.all(6),
        child: ClipOval(
          child: Material(
            color: color, // Button color
            child: InkWell(
              splashColor: Colors.blueGrey.shade100, // Splash color
              onTap: onTap,
              child: SizedBox(
                width: 40,
                height: 40,
                child: Center(
                  child: Text(letter),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class SkeletonDemoStateController extends GetxController {
  final bool isDebug;

  SkeletonDemoStateController({
    this.isDebug = false,
  });

  var isLayoutDebug = false.obs;
  var enableCollapsedLeftWidthEdition = false.obs;
  var enableExpandedLeftWidthEdition = false.obs;
  var enableExpandedRightWidthEdition = false.obs;

  static SkeletonDemoStateController get to => Get.find();

  [@override](/user/override)
  void onReady() {
    isLayoutDebug(isDebug);
    super.onReady();
  }

  var currentIndex = 0.obs;

  var isLeftSideMenuExpanded = true.obs;
  var isRightSideMenuExpanded = true.obs;
  var isDesktop = true.obs;

  var collapsedLeftWidth = 60.0.obs;
  var expandedLeftWidth = 180.0.obs;
  var expandedRightWidth = 300.0.obs;

  Rx<Color> color = Colors.black.obs;

  String get currentRoute => Get.currentRoute;

  void toggleLeftSide() {
    isLeftSideMenuExpanded.toggle();
  }

  void toggleRightSide() {
    isRightSideMenuExpanded.toggle();
  }

  void reset(Color? newColor) {
    color(newColor ?? Colors.black);
    isLeftSideMenuExpanded(true);
    isRightSideMenuExpanded(true);
  }

  void changePage(int? index) {
    currentIndex.value = index ?? 0;
  }
}
1 回复

更多关于Flutter占位符展示插件mr_skeleton的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


mr_skeleton 是一个用于在 Flutter 应用中展示占位符(Skeleton)效果的插件。它可以帮助你在数据加载时展示一个骨架屏,提升用户体验。以下是如何使用 mr_skeleton 插件的详细步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 mr_skeleton 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  mr_skeleton: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来安装依赖。

2. 基本使用

mr_skeleton 提供了 SkeletonSkeletonList 两个主要的组件,分别用于单个占位符和列表占位符。

单个占位符 (Skeleton)

你可以使用 Skeleton 组件来创建一个简单的占位符:

import 'package:flutter/material.dart';
import 'package:mr_skeleton/mr_skeleton.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Skeleton Example'),
      ),
      body: Center(
        child: Skeleton(
          width: 200,
          height: 100,
          borderRadius: BorderRadius.circular(8),
        ),
      ),
    );
  }
}

列表占位符 (SkeletonList)

如果你需要展示一个列表的占位符,可以使用 SkeletonList 组件:

import 'package:flutter/material.dart';
import 'package:mr_skeleton/mr_skeleton.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Skeleton List Example'),
      ),
      body: SkeletonList(
        length: 5, // 列表项的数量
        builder: (context, index) => Skeleton(
          width: double.infinity,
          height: 100,
          borderRadius: BorderRadius.circular(8),
          margin: EdgeInsets.symmetric(vertical: 8),
        ),
      ),
    );
  }
}

3. 自定义占位符

你可以通过 Skeleton 组件的参数来自定义占位符的外观,例如颜色、圆角、边距等。

Skeleton(
  width: 200,
  height: 100,
  borderRadius: BorderRadius.circular(8),
  color: Colors.grey[300], // 占位符颜色
  margin: EdgeInsets.all(8), // 外边距
  padding: EdgeInsets.all(8), // 内边距
)

4. 控制占位符的显示与隐藏

通常,你会在数据加载时显示占位符,数据加载完成后隐藏占位符。你可以通过 Visibility 组件或条件渲染来控制占位符的显示与隐藏。

bool isLoading = true; // 假设这是一个加载状态

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Skeleton Example'),
    ),
    body: Center(
      child: isLoading
          ? Skeleton(
              width: 200,
              height: 100,
              borderRadius: BorderRadius.circular(8),
            )
          : Text('Data Loaded!'),
    ),
  );
}

5. 动画效果

mr_skeleton 默认会为占位符添加一个渐变动画效果,你可以通过 animationDurationanimationCurve 参数来自定义动画的持续时间和曲线。

Skeleton(
  width: 200,
  height: 100,
  borderRadius: BorderRadius.circular(8),
  animationDuration: Duration(seconds: 2), // 动画持续时间
  animationCurve: Curves.easeInOut, // 动画曲线
)
回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!