我正在构建一个复杂的Flutter输入表单,其中输入分布在多个选项卡视图中。我刚刚发现,在Flutter中,如果你切换标签视图,那么Form本质上会忘记前一个标签视图上的输入控件。有没有一种方法可以拦截选项卡/选项卡视图的更改,以便我可以在即将失去焦点的选项卡视图上的输入上注入表单验证?并且如果验证失败,则可以停止选项卡改变。我希望能找到一个事件处理器像。。
onTabChanging(int oldTabIndex, int newTabIndex)
有什么建议吗
nhjlsmyf1#
可以拦截它,在每个TabBar元素上放置一个GestureDetector将吸收事件,或者如果你只是想禁用 * 栏导航 *,你可以只使用IgnorePointer,然后调用TabController.animateTo来移动。这样做可能会随着时间的推移使代码变得过于复杂,还有其他解决方案,这里有一个使用InheritedWidget使数据在重建过程中持久化,这使得flutter “忘记”,当元件离开观察区域时(只有在元素没有被替换的情况下才是状态,即使元素被替换了,你也可以用一个键来保存示例),当重新输入时,它们会被再次构建。在这个例子中有一个on_change,它可以像你期望的那样工作,但它不能阻止标签中的更改,它只是通知你更改。
TabBar
GestureDetector
IgnorePointer
TabController.animateTo
InheritedWidget
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的方法,因为您可以从可以访问上下文的任何下游点访问它。
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'), ), ], ); } }
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'), ); } }
2条答案
按热度按时间nhjlsmyf1#
可以拦截它,在每个
TabBar
元素上放置一个GestureDetector
将吸收事件,或者如果你只是想禁用 * 栏导航 *,你可以只使用IgnorePointer
,然后调用TabController.animateTo
来移动。这样做可能会随着时间的推移使代码变得过于复杂,还有其他解决方案,这里有一个使用
InheritedWidget
使数据在重建过程中持久化,这使得flutter “忘记”,当元件离开观察区域时(只有在元素没有被替换的情况下才是状态,即使元素被替换了,你也可以用一个键来保存示例),当重新输入时,它们会被再次构建。在这个例子中有一个
on_change
,它可以像你期望的那样工作,但它不能阻止标签中的更改,它只是通知你更改。这两种形式非常简单,它们只是为了展示一种连接回
FormData
的方法,因为您可以从可以访问上下文的任何下游点访问它。pprl5pva2#
遇到了类似的问题,决定向每个表单传递一个VoidCallback函数,以便在每个表单中进行验证并更新父小部件(然后重新构建选项卡栏)。