Skip to content

Mixin

概述

Mixin 是 FluCli 项目模板中的重要设计模式,它通过代码复用的方式,将通用功能抽象为可混入的类,从而避免了多重继承的复杂性,同时保持了代码的模块化和可维护性。本文档将详细介绍 FluCli 项目中的 Mixin 设计和使用方法。

Mixin 基础

什么是 Mixin

Mixin 是 Dart 语言提供的一种代码复用机制,它允许类在不使用继承的情况下复用其他类的代码。在 Flutter 开发中,Mixin 特别适用于:

  • 横切关注点:如日志记录、权限检查、生命周期管理
  • 功能组合:将多个独立功能组合到一个类中
  • 避免深层继承:减少继承层次,提高代码可维护性

设计原则

我们的 Mixin 设计遵循以下原则:

  1. 单一职责:每个 Mixin 只负责一个特定功能
  2. 可组合性:多个 Mixin 可以自由组合使用
  3. 可配置性:提供丰富的配置选项满足不同需求
  4. 向后兼容:保持 API 稳定性

核心 Mixin 类

1. HzyScaffolMixin - 脚手架混入

功能说明:提供 Scaffold 脚手架的统一管理,包括是否需要脚手架、背景色配置、导航栏配置等。

设计目的

  • 统一管理页面的基础结构
  • 提供灵活的脚手架配置选项
  • 支持自定义背景色和键盘弹起行为
  • 支持自定义 AppBar 配置
dart
mixin HzyScaffolMixin {

  /// 创建脚手架
  createScaffolWidget({
    required BuildContext context,
    BoxConstraints? constraints,
  }) {
    Widget body = configIsNeedScaffol()
        ? Scaffold(
            appBar: createAppBar(context: context),
            backgroundColor: configScallBackgroundColor(),
            resizeToAvoidBottomInset: configResizeToAvoidBottomInset(),
            body: createScallBody(
              context: context,
              constraints: constraints,
            ),
          )
        : createScallBody(
            context: context,
            constraints: constraints,
          );
    return body;
  }
    /// 创建导航栏
  @protected
  PreferredSizeWidget? createAppBar({
    required BuildContext context,
  });

  /// 创建界面body
  @protected
  Widget createScallBody({
    required BuildContext context,
    BoxConstraints? constraints,
  });
 
}

使用示例

dart
class MyPage extends StatefulWidget {}

class _MyPageState extends State<MyPage> with HzyScaffolMixin {
  @override
  bool configIsNeedScaffol() => true;
  
  @override
  Color? configScallBackgroundColor() => Colors.white;
}

扩展优势

  • 新增脚手架配置时,只需在 Mixin 中添加方法
  • 所有使用该 Mixin 的页面自动获得新功能
  • 可以根据业务需求灵活开启或关闭脚手架

2. HzyAppBarMixin - 导航栏混入

功能说明:统一管理 AppBar 的创建和配置,包括标题、颜色、按钮等。

设计目的

  • 标准化 AppBar 的创建流程
  • 提供丰富的自定义选项
  • 支持动态配置导航栏样式(如标题、颜色、按钮等)
dart
mixin HzyAppBarMixin {
  /// 创建导航栏
  PreferredSizeWidget? createAppBar({
    required BuildContext context,
  })
  /// 创建导航栏标题
  String? createAppBarTitleStr() => null;
  
  /// 配置导航栏背景色
  Color? createAppBarNavBackColor() => null;
  
  /// 配置右侧按钮
  List<Widget>? createAppBaractions() => null;
  
  /// 是否显示返回按钮
  bool configShowBack() => true;

  /// 配置导航栏标题颜色
  Color? createAppBarTitleColor() => null;
}

通过对比 HzyScaffolMixinHzyAppBarMixin,我们可以发现:

  • HzyScaffolMixin 实现了 createScaffolWidget方法,并提供了脚手架的能力。
  • HzyAppBarMixin 实现了 createAppBar方法,并提供了自定义导航栏的能力。这样我们就可以利用方法重写的方法,就实现了AppBar功能,如果你想自定义,也只需要重写createAppBar方法,其他的方法,你可以根据自己的需求,来定制化。

使用示例

dart
class _MyPageState extends State<MyPage> 
    with HzyScaffolMixin, HzyAppBarMixin {
  
  @override
  String? createAppBarTitleStr() => "我的页面";
  
  @override
  Color? createAppBarNavBackColor() => Colors.blue;
  
  @override
  List<Widget>? createAppBaractions() => [
    IconButton(
      icon: Icon(Icons.settings),
      onPressed: () => print("设置"),
    ),
  ];
}

扩展优势

  • 支持完全自定义 AppBar 的每个部分
  • 可以统一修改全局 AppBar 样式
  • 新增功能时不影响现有页面

3. HzyBodyMixin - 页面主体混入

HzyBodyMixin 是页面主体内容的核心管理者,负责处理页面的主要显示逻辑,包括安全区域处理、页面状态管理、占位图显示等功能。

核心功能

  • 🛡️ 安全区域管理:自动处理刘海屏、底部指示器等安全区域适配
  • 📊 页面状态管理:统一管理加载、成功、失败、空数据等页面状态
  • 🎭 占位图系统:提供加载动画、错误提示、空状态等占位组件
  • 🔧 高度可配置:丰富的配置选项满足不同场景需求

基本使用

dart
class _MyPageState extends State<MyPage> 
    with HzyScaffolMixin, HzyAppBarMixin, HzyBodyMixin {
  
  PageState _pageState = PageState.loadingState;
  
  // 配置方法
  @override
  bool configIsNeedSafeArea() => true;
  
  @override
  PageState configPageState() => _pageState;
  
  // 实现页面内容
  @override
  Widget createBody({required BuildContext context, BoxConstraints? constraints}) {
    return ListView(
      children: [
        // 您的页面内容
      ],
    );
  }
}

设计优势

  • 减少重复代码:统一的页面结构和状态管理
  • 提升开发效率:专注业务逻辑,无需关心基础架构
  • 保证用户体验:全局一致的加载、错误、空状态样式
  • 便于维护扩展:集中管理页面状态逻辑

详细了解

想要深入了解 HzyBodyMixin 的完整功能、界面构建流程、配置选项和高级用法,请查看:

📖 HzyBodyMixin 详细文档

包含完整的流程图、方法调用链、可视化层级结构、配置说明、使用示例和最佳实践。

4. HzyLayoutMixin - 布局混入

功能说明:提供 LayoutBuilder 的封装,用于响应式布局设计。

设计目的

  • 支持响应式布局开发
  • 提供屏幕尺寸适配能力
  • 简化 LayoutBuilder 的使用
dart
mixin HzyLayoutMixin {
  /// 是否需要 Layout
  bool configIsNeedLayout() => true;
  
  /// 创建布局子组件
  Widget createLayoutChileWidget({
    BoxConstraints? constraints,
    required BuildContext context,
  });
}

使用示例

dart
class _MyPageState extends State<MyPage> with HzyLayoutMixin {
  @override
  Widget createLayoutChileWidget({
    BoxConstraints? constraints,
    required BuildContext context,
  }) {
    // 根据屏幕宽度调整布局
    if (constraints!.maxWidth > 600) {
      return _buildTabletLayout();
    } else {
      return _buildPhoneLayout();
    }
  }
}

5. HzyAbsAttribute - 属性抽象混入

功能说明:定义页面的通用属性配置,如导航栏显示、安全区域、背景色等。

设计目的

  • 提供统一的属性配置接口
  • 减少重复的属性定义
  • 支持默认值和自定义配置
dart
mixin class HzyAbsAttribute {
  /// 是否显示导航栏
  final bool isShowAppBar = true;
  
  /// 导航栏标题
  final String? appBarTitle = null;
  
  /// 安全区域配置
  final bool safeAreaTop = true;
  final bool safeAreaBottom = true;
  
  /// 背景颜色
  final Color? scallBackGroundColor = null;
}

6. HzyNormalLifeCycleAbs - 生命周期混入

功能说明:提供应用生命周期的统一管理,包括前台、后台切换等。

设计目的

  • 统一应用生命周期处理
  • 提供生命周期回调钩子
  • 支持主题和语言变更监听
dart
mixin class HzyNormalLifeCycleAbs {
  /// 应用进入前台
  configAppLifeCycleResumed() {}
  
  /// 应用进入后台
  configAppLifeCyclePaused() {}
  
  /// 主题变更回调
  configPlatformBrightness() {}
  
  /// 语言变更回调
  configChangeLocales(List<Locale>? locales) {}
}

7. HzyAbstractNetWork - 网络请求混入

功能说明:提供网络请求的统一接口和处理机制。

设计目的

  • 标准化网络请求流程
  • 提供统一的错误处理
  • 支持请求参数的灵活配置
dart
mixin class HzyAbstractNetWork {
  /// 网络请求统一入口
  @protected
  getNetWorkData({
    int? type,
    Map<String, dynamic>? info,
  }) {
    // 实现网络请求逻辑
  }
}

INFO

想要查看完整的Mixin代码,可以查看hzy_normal_tool中的lib/hzy_normal_abstract文件夹.

实际应用示例

完整的页面实现

dart
/// 完整的页面实现示例
class MyComplexPage extends StatefulWidget {
  @override
  _MyComplexPageState createState() => _MyComplexPageState();
}

class _MyComplexPageState extends State<MyComplexPage>
    with
        HzyScaffolMixin,
        HzyAppBarMixin,
        HzyBodyMixin,
        HzyLayoutMixin,
        HzyAbsAttribute,
        HzyAbstractNetWork,
        HzyNormalLifeCycleAbs,
        WidgetsBindingObserver {
  
  PageState _pageState = PageState.initializedState;
  List<String> _dataList = [];
  
  @override
  void initState() {
    super.initState();
    _loadData();
  }
  
  /// 网络请求实现
  @override
  getNetWorkData({int? type, Map<String, dynamic>? info}) {
    // 模拟网络请求
    Future.delayed(Duration(seconds: 2), () {
      setState(() {
        _dataList = ["数据1", "数据2", "数据3"];
        _pageState = PageState.dataState;
      });
    });
  }
  
  void _loadData() {
    setState(() {
      _pageState = PageState.loadingState;
    });
    getNetWorkData(type: 1);
  }
  
  /// AppBar 配置
  @override
  String? createAppBarTitleStr() => "复杂页面示例";
  
  @override
  List<Widget>? createAppBaractions() => [
    IconButton(
      icon: Icon(Icons.refresh),
      onPressed: _loadData,
    ),
  ];
  
  /// 页面状态配置
  @override
  PageState configPageState() => _pageState;
  
  @override
  Widget? createEmptyWidget() => Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.inbox, size: 64, color: Colors.grey),
        SizedBox(height: 16),
        Text("暂无数据", style: TextStyle(color: Colors.grey)),
        SizedBox(height: 16),
        ElevatedButton(
          onPressed: _loadData,
          child: Text("重新加载"),
        ),
      ],
    ),
  );
  
  @override
  Widget? createLoadingWidget() => Center(
    child: CircularProgressIndicator(),
  );
  
  /// 主体内容
  @override
  Widget createBody({required BuildContext context, BoxConstraints? constraints}) {
    return ListView.builder(
      itemCount: _dataList.length,
      itemBuilder: (context, index) => ListTile(
        leading: CircleAvatar(child: Text('${index + 1}')),
        title: Text(_dataList[index]),
        trailing: Icon(Icons.arrow_forward_ios),
        onTap: () => print("点击了 ${_dataList[index]}"),
      ),
    );
  }
  
  /// 生命周期回调
  @override
  configAppLifeCycleResumed() {
    print("页面回到前台,可以刷新数据");
  }
  
  @override
  Widget build(BuildContext context) {
    return createBuild(context: context);
  }
}

设计优势

1. 模块化设计

每个 Mixin 都专注于特定功能,可以独立开发、测试和维护:

  • HzyScaffolMixin:专注脚手架管理
  • HzyAppBarMixin:专注导航栏配置
  • HzyBodyMixin:专注页面状态管理
  • HzyLayoutMixin:专注响应式布局

2. 灵活组合

开发者可以根据页面需求选择性地混入不同的 Mixin:

dart
// 简单页面:只需要基础功能
class SimplePage extends StatefulWidget {}
class _SimplePageState extends State<SimplePage> 
    with HzyScaffolMixin, HzyAppBarMixin {
  // 实现简单页面
}

// 复杂页面:需要完整功能
class ComplexPage extends StatefulWidget {}
class _ComplexPageState extends State<ComplexPage> 
    with HzyScaffolMixin, HzyAppBarMixin, HzyBodyMixin, 
         HzyLayoutMixin, HzyAbsAttribute {
  // 实现复杂页面
}

3. 易于扩展

当需要新增功能时,只需要:

  1. 在对应 Mixin 中添加新方法
  2. 提供默认实现
  3. 在需要的页面中重写方法
dart
// 在 HzyAppBarMixin 中新增功能
mixin HzyAppBarMixin {
  // 现有方法...
  
  /// 新增:配置导航栏阴影
  double? createAppBarElevation() => null;
  
  /// 新增:配置导航栏形状
  ShapeBorder? createAppBarShape() => null;
}

4. 统一标准

通过 Mixin 统一了页面开发标准:

  • 命名规范:所有配置方法以 config 开头,创建方法以 create 开头
  • 参数传递:统一的参数传递方式
  • 生命周期:标准化的生命周期管理
  • 状态管理:统一的页面状态定义

5. 向后兼容

新增功能时保持向后兼容:

  • 默认实现:新方法提供合理的默认值
  • 可选重写:开发者可选择性地重写需要的方法
  • 渐进升级:可以逐步迁移到新功能

最佳实践

1. 合理选择 Mixin

根据页面复杂度选择合适的 Mixin 组合:

dart
// ✅ 好的做法:根据需求选择
class SimpleListPage extends StatefulWidget {}
class _SimpleListPageState extends State<SimpleListPage> 
    with HzyScaffolMixin, HzyAppBarMixin, HzyBodyMixin {
  // 只混入需要的功能
}

// ❌ 避免:不必要的 Mixin
class SimpleListPage extends StatefulWidget {}
class _SimpleListPageState extends State<SimpleListPage> 
    with HzyScaffolMixin, HzyAppBarMixin, HzyBodyMixin, 
         HzyLayoutMixin, HzyAbsAttribute, HzyAbstractNetWork {
  // 混入了过多不需要的功能
}

2. 重写关键方法

重写 Mixin 中的关键方法来定制页面行为:

dart
class _MyPageState extends State<MyPage> with HzyBodyMixin {
  @override
  bool configIsNeedSafeArea() => false; // 不需要安全区域
  
  @override
  PageState configPageState() => _currentState; // 动态状态
  
  @override
  Widget? createEmptyWidget() => CustomEmptyWidget(); // 自定义空状态
}

3. 保持方法简洁

Mixin 中的方法应该保持简洁,复杂逻辑放在具体实现中:

dart
// ✅ 好的做法
@override
String? createAppBarTitleStr() => "我的页面";

@override
Color? createAppBarNavBackColor() => Theme.of(context).primaryColor;

// ❌ 避免:在 Mixin 方法中写复杂逻辑
@override
Widget? createEmptyWidget() {
  // 避免在这里写大量业务逻辑
  return _buildComplexEmptyWidget();
}

Widget _buildComplexEmptyWidget() {
  // 复杂逻辑放在单独方法中
  return Container(/* 复杂的空状态组件 */);
}

4. 利用组合优势

充分利用 Mixin 的组合特性,创建可复用的页面模板:

dart
/// 基础页面模板
abstract class BasePageState<T extends StatefulWidget> extends State<T>
    with HzyScaffolMixin, HzyAppBarMixin, HzyBodyMixin {
  
  @override
  bool configIsNeedScaffol() => true;
  
  @override
  Color? configScallBackgroundColor() => Colors.white;
}

/// 列表页面模板
abstract class BaseListPageState<T extends StatefulWidget> extends BasePageState<T>
    with HzyAbstractNetWork {
  
  @override
  Widget? createLoadingWidget() => Center(child: CircularProgressIndicator());
}

/// 具体页面实现
class _UserListPageState extends BaseListPageState<UserListPage> {
  @override
  String? createAppBarTitleStr() => "用户列表";
  
  @override
  Widget createBody({required BuildContext context, BoxConstraints? constraints}) {
    return ListView.builder(/* 用户列表实现 */);
  }
}

总结

Mixin 设计模式在 Flutter 开发中提供了强大的代码复用能力。通过合理的 Mixin 设计,我们实现了:

  1. 代码复用:避免重复编写相同的页面结构代码
  2. 模块化:每个 Mixin 专注特定功能,易于维护
  3. 灵活组合:根据需求自由组合不同功能
  4. 统一标准:建立了统一的页面开发规范
  5. 易于扩展:新增功能时不影响现有代码

这种设计方式特别适合大型项目的开发,能够显著提高开发效率和代码质量。

根据 MIT 许可发布。