Flutter Hero动画跳转后TextField自动聚焦失效键盘收起问题
问题描述
两个页面分别放置尺寸不同的TextField组件,为两个组件配置Hero过渡动画后,期望路由跳转到第二个页面时,目标TextField自动获取焦点弹出软键盘,但实际跳转完成后软键盘会自动收起。
尝试在flightShuttleBuilder中为动画添加状态监听,在动画状态为AnimationStatus.completed时触发focusNode.requestFocus()请求焦点,方案未生效,问题效果如下:
原实现代码:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class Search extends ConsumerStatefulWidget { @override ConsumerState createState() => _SearchState(); } class _SearchState extends ConsumerState<Search> { FocusNode focusNode = FocusNode(); @override void initState() { super.initState(); } @override void dispose() { focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) => () => focusNode.requestFocus()); Size size = MediaQuery.of(context).size; focusNode.requestFocus(); return Scaffold( appBar: AppBar( title: Container( child: Hero( tag:'searchBar', flightShuttleBuilder: ((flightContext, animation, flightDirection, fromHeroContext, toHeroContext){ animation.addStatusListener((status) { if (status == AnimationStatus.completed) { focusNode.requestFocus(); } }); return toHeroContext.widget; }), child: Material( type: MaterialType.transparency, child: SizedBox( height:size.height * 0.05, child: TextField( focusNode: focusNode, decoration: InputDecoration( prefixIcon: const Icon(Icons.search, size: 25.0,), border: OutlineInputBorder( borderRadius: BorderRadius.circular(50.0), borderSide: const BorderSide( width: 0, style: BorderStyle.none, ), ), filled: true, hintStyle: TextStyle(color: Colors.grey[800]), hintText: 'Search a word', fillColor: Colors.white, contentPadding: EdgeInsets.all(0.0), ), ), ), ), ), ), ), body: Container(), ); } }
问题根因
现有写法不生效的核心问题有3个:
- Hero动画执行时,
flightShuttleBuilder中返回的toHeroContext.widget属于动画飞行层的临时组件,不是页面最终挂载的TextField实例。动画完成后飞行层会直接销毁,此时在动画监听里触发的焦点请求会被后续组件生命周期的焦点回收逻辑覆盖 - 在
build方法中直接调用focusNode.requestFocus()、重复注册addPostFrameCallback,会导致每次组件重绘都重复触发焦点请求,和Hero动画的执行时序冲突。动画收尾阶段框架会临时释放焦点做图层切换,会直接清掉提前请求的焦点 - 动画监听没有做去重判断,每次Hero重绘都会新增监听,导致多次触发无效的焦点请求,打乱正常的焦点分配逻辑
修复方案
按以下规则调整代码即可解决问题:
- 移除
flightShuttleBuilder中的焦点监听逻辑,不要在动画飞行层处理页面最终组件的焦点请求 - 移除
build方法中的所有焦点请求、addPostFrameCallback注册逻辑,避免重复触发 - 等待Hero动画完全执行完成、飞行层销毁后再触发焦点请求,Flutter默认Hero动画时长为300ms,加50ms的缓冲时间避开框架自动回收焦点的时机即可
修正后的完整代码:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class Search extends ConsumerStatefulWidget { @override ConsumerState createState() => _SearchState(); } class _SearchState extends ConsumerState<Search> { // 焦点节点只初始化一次 late final FocusNode focusNode = FocusNode(); @override void initState() { super.initState(); // 延迟350ms等Hero动画完全收尾、图层切换完成后再请求焦点 Future.delayed(const Duration(milliseconds: 350), () { // 判断组件是否仍在挂载,避免页面退出后触发异常 if (mounted) { focusNode.requestFocus(); } }); } @override void dispose() { focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; return Scaffold( appBar: AppBar( title: Container( child: Hero( tag: 'searchBar', // 无自定义飞行动画需求不需要实现flightShuttleBuilder child: Material( type: MaterialType.transparency, child: SizedBox( height: size.height * 0.05, child: TextField( focusNode: focusNode, decoration: InputDecoration( prefixIcon: const Icon(Icons.search, size: 25.0), border: OutlineInputBorder( borderRadius: BorderRadius.circular(50.0), borderSide: const BorderSide( width: 0, style: BorderStyle.none, ), ), filled: true, hintStyle: TextStyle(color: Colors.grey[800]), hintText: 'Search a word', fillColor: Colors.white, contentPadding: EdgeInsets.all(0.0), ), ), ), ), ), ), ), body: Container(), ); } }
如果你自定义了Hero动画时长,只需要把延迟的毫秒数调整为「自定义动画时长 + 50ms」即可,不需要额外做复杂的动画状态监听,该方案不会出现软键盘弹出后自动收起的问题。
内容的提问来源于stack exchange,提问作者CloudWave
相关产品推荐
相关产品推荐

