让我的 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,你的代码会立刻感觉更整洁、更智能,并且使用起来更有趣。

相关推荐
吹牛不交税4 分钟前
Axure RP Extension for Chrome插件安装使用
前端·chrome·axure
薛定谔的算法20 分钟前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架
拾光拾趣录1 小时前
Element Plus表格表头动态刷新难题:零闪动更新方案
前端·vue.js·element
Adolf_19932 小时前
React 中 props 的最常用用法精选+useContext
前端·javascript·react.js
前端小趴菜052 小时前
react - 根据路由生成菜单
前端·javascript·react.js
喝拿铁写前端2 小时前
`reduce` 究竟要不要用?到底什么时候才“值得”用?
前端·javascript·面试
鱼樱前端2 小时前
2025前端SSR框架之十分钟快速上手Nuxt3搭建项目
前端·vue.js
極光未晚2 小时前
React Hooks 中的时空穿梭:模拟 ComponentDidMount 的奇妙冒险
前端·react.js·源码
Codebee2 小时前
OneCode 3.0 自治UI 弹出菜单组件功能介绍
前端·人工智能·开源
ui设计兰亭妙微2 小时前
# 信息架构如何决定搜索效率?
前端