让我的 Flutter 代码整洁 10 倍的 5 种 Mixin

如果你曾在 Flutter 中使用过 SingleTickerProviderStateMixin 来制作动画,猜猜怎么着?你已经使用过 Mixin 了------恭喜你,你已经处于一段你甚至不知道的关系中。

Mixin 就像你代码中的秘密特工:隐形、高效,在幕后默默地承担着所有繁重的工作。它们让你可以在多个类之间重用逻辑,而无需陷入复杂的继承链的戏剧性。这意味着更少的 Bug,更少的样板代码,以及真正能激发愉悦感的 Widget。

让我们深入了解。

1.SingleAnimationMixin --- 快速设置单个 AnimationController。

Flutter 中的动画功能强大------但每次都设置一个 AnimationController?那就没那么有趣了。

这就是这个方便的 Mixin 的用武之地。只需几行代码,你就可以在任何 Widget 中重用一个完全可配置、可重复的动画设置------没有样板代码,没有麻烦。

dart 复制代码
mixin SingleRepeatAnimationMixin<T extends StatefulWidget> on State<T>, SingleTickerProviderStateMixin {
  late AnimationController animationController;
  Duration get animationDuration => const Duration(seconds: 1);

  bool get repeatInReverse => true;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      vsync: this,
      duration: animationDuration,
    )..repeat(reverse: repeatInReverse);
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }
}

使用:

dart 复制代码
class ExampleAnimation extends StatefulWidget {
  @override
  State<ExampleAnimation> createState() => _ExampleAnimationState();
}

class _ExampleAnimationState extends State<ExampleAnimation> with SingleRepeatAnimationMixin {
  @override
  Duration get animationDuration => const Duration(milliseconds: 800);

  @override
  bool get repeatInReverse => false;

  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: animationController,
      child: Container(width: 100, height: 100, color: Colors.purple),
    );
  }
}

2. FormFieldValidationMixin --- 停止重复编写基础验证器。

厌倦了编写相同的 if (value == null || value.isEmpty) 吗?这个 Mixin 为你提供了即插即用的验证器,适用于日常表单。

dart 复制代码
mixin FormFieldValidationMixin {
  String? isRequired(String? value, [String field = "This field"]) {
    if (value == null || value.trim().isEmpty) return "$field is required.";
    return null;
  }

  String? isEmail(String? value) {
    if (value == null || value.isEmpty) return "Email is required.";
    final regex = RegExp(r'^[\w-.]+@([\w-]+.)+[\w-]{2,4}$');
    if (!regex.hasMatch(value)) return "Enter a valid email.";
    return null;
  }
}

使用:

dart 复制代码
class LoginForm extends StatefulWidget {
  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> with FormFieldValidationMixin {
  final _formKey = GlobalKey<FormState>();
  final _email = TextEditingController();
  final _password = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _email,
            validator: isEmail,
          ),
          TextFormField(
            controller: _password,
            validator: (v) => isRequired(v, "Password"),
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {

              }
            },
            child: Text("Login"),
          )
        ],
      ),
    );
  }

3. DelayedInitMixin --- 在第一帧渲染运行代码。

需要在 Widget 完成构建后才触发某些操作吗------比如滚动跳转、动画,或者显示对话框? 这就是 DelayedInitMixin 的用武之地。 它让你可以在第一帧渲染后 安全地运行代码------没有取巧,没有 Future.delayed,只有干净的布局后逻辑。

dart 复制代码
mixin DelayedInitMixin<T extends StatefulWidget> on State<T> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => afterFirstLayout());
  }

  void afterFirstLayout();
}

使用:

dart 复制代码
class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with DelayedInitMixin {
  @override
  void afterFirstLayout() {
    print("Widget has rendered!");
  }

  @override
  Widget build(BuildContext context) => Container();
}

4. RebuildCounterMixin --- 像专业人士一样捕获不必要的重建。

你有没有想过你的 Widget 是否重建得比它应该的次数更多?

这个Mixin帮助你实时跟踪重建次数------非常适合性能调试、Widget 优化,或者仅仅是满足你的好奇心。

dart 复制代码
mixin RebuildMixin<T extends StatefulWidget> on State<T> {
  int _rebuilds = 0;

  @override
  Widget build(BuildContext context) {
    _rebuilds++;
    debugPrint('${widget.runtimeType} rebuilt $_rebuilds times');
    return buildWithCount(context, _rebuilds);
  }

  Widget buildWithCount(BuildContext context, int count);
}

使用:

dart 复制代码
class CounterExample extends StatefulWidget {
  const CounterExample({super.key});

  @override
  State<CounterExample> createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample>
    with RebuildMixin {
  int count = 0;

  @override
  Widget buildWithCount(BuildContext context, int rebuilds) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Rebuilds: $rebuilds'),
            ElevatedButton(
              onPressed: () => setState(() => count++),
              child: Text('Tap $count'),
            ),
          ],
        ),
      ),
    );
  }
}

5. RenderAwareMixin --- 了解你的 Widget 的大小和位置。

想在 Widget 渲染之后 获取它的大小或屏幕偏移量吗? 这个 Mixin 让你能够干净地访问 SizeOffset------没有布局上的取巧,也没有混乱的 GlobalKey 杂耍。

dart 复制代码
mixin RenderAwareMixin<T extends StatefulWidget> on State<T> {
  final renderKey = GlobalKey();

  Size? get widgetSize {
    final ctx = renderKey.currentContext;
    if (ctx == null) return null;
    final box = ctx.findRenderObject() as RenderBox?;
    return box?.size;
  }

  Offset? get widgetPosition {
    final ctx = renderKey.currentContext;
    if (ctx == null) return null;
    final box = ctx.findRenderObject() as RenderBox?;
    return box?.localToGlobal(Offset.zero);
  }
}

使用:

dart 复制代码
class MeasureBox extends StatefulWidget {
  const MeasureBox({super.key});

  @override
  State<MeasureBox> createState() => _MeasureBoxState();
}

class _MeasureBoxState extends State<MeasureBox> with RenderAwareMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          key: renderKey,
          onTap: () {
            final size = widgetSize;
            final pos = widgetPosition;
            debugPrint("Size: $size");
            debugPrint("Position: $pos");
          },
          child: Container(
            color: Colors.blue,
            width: 120,
            height: 80,
            alignment: Alignment.center,
            child: const Text('Tap Me', style: TextStyle(color: Colors.white)),
          ),
        ),
      ),
    );
  }
}

Mixin 是 Flutter 默默无闻的超能力------可重用、优雅,并且被严重低估。在你的工具包中添加一些 Mixin,你的代码会立刻感觉更整洁、更智能,并且使用起来更有趣。

相关推荐
quan263114 分钟前
Vue实践篇-02,AI生成代码
前端·javascript·vue.js
GIS之路14 分钟前
GDAL 读取影像元数据
前端
qb1 小时前
vue3.5.18源码-编译-入口
前端·vue.js·架构
小桥风满袖1 小时前
极简三分钟ES6 - 类与继承
前端·javascript
虫无涯1 小时前
【分享】基于百度脑图,并使用Vue二次开发的用例脑图编辑器组件
前端·vue.js·编辑器
子兮曰1 小时前
🚀99% 的前端把 reduce 用成了「高级 for 循环」—— 这 20 个骚操作让你一次看懂真正的「函数式折叠」
前端·javascript·typescript
wifi歪f1 小时前
📦 qiankun微前端接入实战
前端·javascript·面试
小桥风满袖1 小时前
极简三分钟ES6 - Symbol
前端·javascript
子兮曰1 小时前
🚀Map的20个神操作,90%的开发者浪费了它的潜力!最后的致命缺陷让你少熬3天夜!
前端·javascript·ecmascript 6
NewChapter °1 小时前
如何通过 Gitee API 上传文件到指定仓库
前端·vue.js·gitee·uni-app