如何防止Flutter中已失活页面因键盘弹出而重建?
解决Flutter中MediaQuery导致后台页面重建的问题
问题根源
当键盘弹出时,Flutter的MediaQueryData会更新(如屏幕可用尺寸、视图内边距变化),而Navigator栈中所有依赖MediaQuery.of(context)的页面(包括后台被覆盖的页面)都会触发build方法——因为这些页面并未被销毁,只是处于不可见状态。
解决方案
1. 非监听模式获取MediaQuery数据
如果仅需一次性获取尺寸,不需要响应后续变化,使用listen: false参数获取MediaQuery,避免因全局MediaQuery更新触发重建:
// 示例:获取高度但不监听变化 final height = MediaQuery.of(context, listen: false).size.height * 0.1;
注意:此方法不适用需要响应键盘弹出的布局场景。
2. 监听页面可见性,按需更新状态
通过WidgetsBindingObserver监听窗口变化,仅在页面当前可见时更新依赖MediaQuery的布局:
class _APageState extends State<APage> with WidgetsBindingObserver { double _containerHeight = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _updateContainerHeight(); } @override void didChangeMetrics() { // 仅当前页面可见时更新 if (ModalRoute.of(context)?.isCurrent == true) { _updateContainerHeight(); } } void _updateContainerHeight() { setState(() { _containerHeight = MediaQuery.of(context).size.height * 0.1; }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { print('A Page'); return Scaffold( appBar: AppBar(), body: Column( children: [ Container( width: double.infinity, height: _containerHeight, color: Colors.green, ), ElevatedButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BPage(), )); }, child: const Text('BPage')), Container( color: Colors.amber, child: const TextField(), ) ], ), ); } }
3. 用RouteObserver跟踪页面可见性
通过RouteObserver监听页面路由状态,仅在页面可见时响应MediaQuery变化:
首先在App中注册路由观察者:
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: APage(), navigatorObservers: [routeObserver], ); } }
然后在页面State中混入RouteAware:
class _APageState extends State<APage> with RouteAware { double _containerHeight = 0; bool _isVisible = false; @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context)!); _updateContainerHeight(); } @override void didPush() { setState(() => _isVisible = true); } @override void didPopNext() { setState(() => _isVisible = true); _updateContainerHeight(); } @override void didPushNext() { setState(() => _isVisible = false); } @override void didPop() { routeObserver.unsubscribe(this); super.didPop(); } void _updateContainerHeight() { if (_isVisible) { setState(() { _containerHeight = MediaQuery.of(context).size.height * 0.1; }); } } @override Widget build(BuildContext context) { print('A Page'); return Scaffold( appBar: AppBar(), body: Column( children: [ Container( width: double.infinity, height: _containerHeight, color: Colors.green, ), ElevatedButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BPage(), )); }, child: const Text('BPage')), Container( color: Colors.amber, child: const TextField(), ) ], ), ); } }
4. 用LayoutBuilder替代MediaQuery
如果仅需父容器尺寸而非全屏尺寸,使用LayoutBuilder,它仅在父容器尺寸变化时重建,不受全局MediaQuery更新影响:
LayoutBuilder( builder: (context, constraints) { return Container( width: constraints.maxWidth, height: constraints.maxHeight * 0.1, color: Colors.green, ); }, )
内容的提问来源于stack exchange,提问作者Anson NG
相关产品推荐
相关产品推荐

