flutter 如何拦截制表符更改以进行表单验证

g9icjywg  于 2023-06-30  发布在  Flutter
关注(0)|答案(2)|浏览(107)

我正在构建一个复杂的Flutter输入表单,其中输入分布在多个选项卡视图中。
我刚刚发现,在Flutter中,如果你切换标签视图,那么Form本质上会忘记前一个标签视图上的输入控件。
有没有一种方法可以拦截选项卡/选项卡视图的更改,以便我可以在即将失去焦点的选项卡视图上的输入上注入表单验证?并且如果验证失败,则可以停止选项卡改变。
我希望能找到一个事件处理器像。。

onTabChanging(int oldTabIndex, int newTabIndex)

有什么建议吗

nhjlsmyf

nhjlsmyf1#

可以拦截它,在每个TabBar元素上放置一个GestureDetector将吸收事件,或者如果你只是想禁用 * 栏导航 *,你可以只使用IgnorePointer,然后调用TabController.animateTo来移动。
这样做可能会随着时间的推移使代码变得过于复杂,还有其他解决方案,这里有一个使用InheritedWidget使数据在重建过程中持久化,这使得flutter “忘记”,当元件离开观察区域时(只有在元素没有被替换的情况下才是状态,即使元素被替换了,你也可以用一个键来保存示例),当重新输入时,它们会被再次构建。
在这个例子中有一个on_change,它可以像你期望的那样工作,但它不能阻止标签中的更改,它只是通知你更改。

import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  App({super.key});

  @override
  Widget build(BuildContext _) {
    return MaterialApp(home: FormTabs());
  }
}

class FormDataState {
  String text_shared = '';
  String text_a = '';
  String text_b = '';
}

class FormData extends InheritedWidget {
  // This calls is "non-mutable" class, if
  // for some reason you need to change the data on a
  // rebuild, updateShouldNotify will give you the old widget
  final state = FormDataState();
  FormData({super.key, required super.child});

  static FormData of(BuildContext ctx) {
    final FormData? result = ctx.dependOnInheritedWidgetOfExactType<FormData>();
    assert(result != null, 'Ho No');
    return result!;
  }

  @override
  bool updateShouldNotify(FormData _) => false;
}

class FormTabs extends StatefulWidget {
  final forms = {
    'A': FormA(), // Not ideal but it is simple
    'B': FormB(), // Each gets rebuilt on a tab switch
  };

  FormTabs({super.key});

  @override
  State<FormTabs> createState() => _FormTabs();
}

class _FormTabs extends State<FormTabs> with TickerProviderStateMixin {
  late TabController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TabController(
      length: widget.forms.length,
      vsync: this,
    );
    _controller.addListener(
      () => _controller.indexIsChanging == false
          ? on_change(_controller.previousIndex, _controller.index)
          : null,
    );
  }

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

  void on_change(int from, int to) {
    print('Tab change from $from to $to');
  }

  @override
  Widget build(BuildContext _) {
    return FormData(
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            controller: _controller,
            tabs: List<Widget>.generate(
              widget.forms.length,
              (i) => Text(widget.forms.keys.elementAt(i)),
            ),
          ),
        ),
        body: TabBarView(
          controller: _controller,
          children: List<Widget>.generate(
            widget.forms.length,
            (i) => widget.forms.values.elementAt(i),
          ),
        ),
      ),
    );
  }
}

这两种形式非常简单,它们只是为了展示一种连接回FormData的方法,因为您可以从可以访问上下文的任何下游点访问它。

class FormA extends StatefulWidget {
  FormA({super.key});

  @override
  State<FormA> createState() => _FormA();
}

class _FormA extends State<FormA> {
  final _tec_temporal = TextEditingController();
  final _tec_shared = TextEditingController();
  final _tec_a = TextEditingController();

  @override
  void dispose() {
    _tec_temporal.dispose();
    _tec_shared.dispose();
    _tec_a.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext ctx) {
    var ref = FormData.of(ctx).state;
    _tec_a.text = ref.text_a;
    _tec_shared.text = ref.text_shared;

    return Column(
      children: [
        TextField(
          controller: _tec_shared,
          onChanged: (value) => ref.text_shared = _tec_shared.text,
          decoration: InputDecoration(labelText: 'Field Shared'),
        ),
        TextField(
          controller: _tec_a,
          onChanged: (value) => ref.text_a = _tec_a.text,
          decoration: InputDecoration(labelText: 'Field A'),
        ),
        TextField(
          controller: _tec_temporal,
          decoration: InputDecoration(labelText: 'Field Temporal'),
        ),
      ],
    );
  }
}

class FormB extends StatefulWidget {
  FormB({super.key});

  @override
  State<FormB> createState() => _FormB();
}

class _FormB extends State<FormB> {
  final _tec_shared = TextEditingController();
  final _tec_b = TextEditingController();

  @override
  void dispose() {
    _tec_shared.dispose();
    _tec_b.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext ctx) {
    var ref = FormData.of(ctx).state;
    _tec_b.text = ref.text_b;
    _tec_shared.text = ref.text_shared;

    return Column(
      children: [
        TextField(
          controller: _tec_shared,
          onChanged: (value) => ref.text_shared = _tec_shared.text,
          decoration: InputDecoration(labelText: 'Field Shared'),
        ),
        TextField(
          controller: _tec_b,
          onChanged: (value) => ref.text_b = _tec_b.text,
          decoration: InputDecoration(labelText: 'Field B'),
        ),
      ],
    );
  }
}
pprl5pva

pprl5pva2#

遇到了类似的问题,决定向每个表单传递一个VoidCallback函数,以便在每个表单中进行验证并更新父小部件(然后重新构建选项卡栏)。

class OuterWidget extends StatefulWidget {
  @override
  _OuterWidgetState createState() => _OuterWidgetState();
}

class _OuterWidgetState extends State<OuterWidget> {

  void _updateValidation() {
    setState(() {
      // Update tabbar
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        InnerWidget(onValidate: _updateValidation),
      ],
    );
  }
}

class InnerWidget extends StatelessWidget {
  final VoidCallback onValidate;

  InnerWidget({required this.onValidate});

  void _validate() {
    // Do validation
    onValidate();
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _validate,
      child: Text('Validate'),
    );
  }
}

相关问题