PageView.builder内Form用GlobalKey致重复Key错误及输入框焦点问题
解决PageView嵌套Form+TextFormField的重复GlobalKey与焦点丢失问题
问题根源
- 重复GlobalKey错误:你给所有页面的
Form共用了同一个_formKey,而PageView默认会预加载相邻页面(比如当前在第0页时,第1页已经被构建),这就导致widget树中出现多个使用相同GlobalKey的Form组件,触发Duplicate GlobalKey detected错误。 - 焦点丢失问题:移除GlobalKey后,虽然解决了重复key的问题,但切换页面时,Flutter的焦点管理器不会自动将焦点切换到新页面的输入框,因此需要手动处理焦点聚焦逻辑。
修复方案
步骤1:为每个Form分配独立的GlobalKey
将单个_formKey改为GlobalKey列表,让每个页面的Form使用对应索引的key,避免重复冲突。
步骤2:手动管理输入框焦点
为每个TextFormField绑定独立的FocusNode,监听PageView的页面切换事件,在页面切换完成后主动请求当前页面输入框的焦点。
优化后的完整代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: PageViewTest(), ); } } class PageViewTest extends StatefulWidget { const PageViewTest({super.key}); @override State<PageViewTest> createState() => _PageViewTestState(); } class _PageViewTestState extends State<PageViewTest> { final PageController _pageController = PageController(initialPage: 0); late List<TextEditingController> _tecList; late List<GlobalKey<FormState>> _formKeys; late List<FocusNode> _focusNodes; final List<String> _signingHintText = [ 'type your domain', 'type your email', 'type your password', ]; final List<String> _signingInputLabel = [ 'Domain', 'Email', 'Password', ]; @override void initState() { super.initState(); // 初始化每个页面的控制器、FormKey和焦点节点 _tecList = List.generate(3, (index) => TextEditingController()); _formKeys = List.generate(3, (index) => GlobalKey<FormState>()); _focusNodes = List.generate(3, (index) => FocusNode()); // 监听页面切换,自动聚焦当前页面的输入框 _pageController.addListener(() { if (_pageController.page == _pageController.page?.round()) { final currentIndex = _pageController.page?.round() ?? 0; WidgetsBinding.instance.addPostFrameCallback((_) { _focusNodes[currentIndex].requestFocus(); }); } }); // 初始页面自动聚焦 WidgetsBinding.instance.addPostFrameCallback((_) { _focusNodes[0].requestFocus(); }); } @override void dispose() { // 销毁所有控制器和焦点节点,避免内存泄漏 for (var controller in _tecList) { controller.dispose(); } for (var node in _focusNodes) { node.dispose(); } _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Container( margin: const EdgeInsets.all(30), child: PageView.builder( controller: _pageController, itemCount: 3, itemBuilder: (context, index) { return Column( children: [ Form( autovalidateMode: AutovalidateMode.onUserInteraction, key: _formKeys[index], // 使用对应索引的FormKey child: TextFormField( focusNode: _focusNodes[index], // 关联焦点节点 textAlign: TextAlign.right, textInputAction: TextInputAction.next, style: const TextStyle(color: Color(0xff030303)), cursorColor: const Color(0xff5e6593), controller: _tecList[index], keyboardType: TextInputType.text, decoration: InputDecoration( hintText: _signingHintText[index], focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Color(0xff5e6593), ), ), contentPadding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10), labelText: _signingInputLabel[index], labelStyle: const TextStyle(color: Color(0xff5e6593)), floatingLabelBehavior: FloatingLabelBehavior.auto, border: const OutlineInputBorder(), alignLabelWithHint: true, hintStyle: const TextStyle( color: Color(0xff5e6593), fontSize: 15, fontWeight: FontWeight.normal, ), ), ), ), ElevatedButton( onPressed: () { if (index < 2) { _pageController.nextPage( duration: const Duration(milliseconds: 800), curve: Curves.ease, ); } }, child: const Text('Next'), ), ], ); }, ), ), ), ); } }
关键改动说明
- 移除全局变量:将原全局的
tecList、_formKey等移到State类内部,避免全局状态污染。 - 多GlobalKey管理:通过
List<GlobalKey<FormState>>为每个Form分配独立的key,彻底解决重复key错误。 - 焦点节点控制:用
List<FocusNode>给每个输入框绑定焦点节点,监听PageController的页面切换事件,在页面切换完成后自动请求当前页面输入框的焦点。 - 资源清理:在dispose方法中销毁所有控制器和焦点节点,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Saied Rahimi
相关产品推荐
相关产品推荐

