在Flutter build函数中声明MediaQuery引发重复重建的问题
Flutter中聚焦TextFormField触发StatelessWidget多次重建的问题解决
问题场景
- 创建普通StatelessWidget
- 在build函数内通过
MediaQuery.of(context).size获取屏幕尺寸 - 在build函数中添加日志
log('Building home screen'); - Scaffold的body中设置居中的Column,并添加TextFormField
现象:打开/聚焦TextFormField时,build函数会多次触发重建,原因是键盘弹出导致设备屏幕可用尺寸变化,而Widget依赖了MediaQuery的尺寸信息。
原代码示例
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: HomeScreen(), ); } }
import 'dart:developer'; import 'package:flutter/material.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { log('Building home screen'); // 获取设备屏幕尺寸 final Size size = MediaQuery.of(context).size; return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ TextFormField(), ], ), ); } }
问题原因
MediaQuery是Flutter中的InheritedWidget,当键盘弹出/收起时,系统会更新屏幕的可用尺寸,导致MediaQuery的信息发生变化。而你的HomeScreen在build函数中直接依赖了MediaQuery.of(context).size,StatelessWidget会在依赖的InheritedWidget更新时自动触发build重建,因此每次键盘状态变化都会导致多次重建。
解决办法
方案1:阻止页面随键盘调整尺寸
如果不需要页面内容随键盘上移,可以给Scaffold添加resizeToAvoidBottomInset: false,这样键盘弹出时不会改变屏幕的可用尺寸,MediaQuery的size不会更新,也就不会触发重建。
修改后的HomeScreen代码:
import 'dart:developer'; import 'package:flutter/material.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { log('Building home screen'); final Size size = MediaQuery.of(context).size; return Scaffold( // 阻止页面随键盘调整布局 resizeToAvoidBottomInset: false, body: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ TextFormField(), ], ), ); } }
方案2:避免在build中直接依赖MediaQuery
改为StatefulWidget,在初始化或专门的尺寸监听方法中获取屏幕尺寸,而非在build函数中直接依赖,这样即使MediaQuery变化,只要不更新状态,就不会触发build重建。
示例代码:
import 'dart:developer'; import 'package:flutter/material.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { late Size _screenSize; @override void initState() { super.initState(); // 页面渲染完成后获取初始尺寸 WidgetsBinding.instance.addPostFrameCallback((_) { _screenSize = MediaQuery.of(context).size; }); // 监听屏幕尺寸变化 WidgetsBinding.instance.addObserver(_metricsObserver); } @override void dispose() { // 移除监听,避免内存泄漏 WidgetsBinding.instance.removeObserver(_metricsObserver); super.dispose(); } void _metricsObserver() { final newSize = MediaQuery.of(context).size; if (_screenSize != newSize) { setState(() { _screenSize = newSize; }); } } @override Widget build(BuildContext context) { log('Building home screen'); return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ TextFormField(), ], ), ); } }
说明
- 方案1适合不需要页面随键盘上移的场景,实现简单;
- 方案2适合需要监听屏幕尺寸变化,但不想每次键盘状态改变都触发build的场景。
内容的提问来源于stack exchange,提问作者VISHNU PRABHAKARAN
相关产品推荐
相关产品推荐

