Flutter - 使用 MediaQuery 时防止重建

Posted

技术标签:

【中文标题】Flutter - 使用 MediaQuery 时防止重建【英文标题】:Flutter - prevent rebuild when using MediaQuery 【发布时间】:2021-04-19 19:42:57 【问题描述】:

我想使用MediaQuery 来构建基于屏幕高度和宽度的小部件。在#26004 中也提到了这个问题,我只想查询一次尺寸数据,例如在initState 中。 MediaQuery 文档状态

使用 MediaQuery.of 查询当前媒体将导致您的小部件在 MediaQueryData 更改时自动重建(例如,如果用户旋转他们的设备)。

,但这会在我的应用程序中导致不必要的重建。具体来说,如果插入或填充发生变化(例如显示键盘时),它会导致小部件重建。

MediaQueryData 更改时,MediaQuery 是否有替代方案不会导致重建?

【问题讨论】:

【参考方案1】:

在我的例子中,问题的发生是因为我手动控制焦点:

onEditingComplete: () 
   FocusScope.of(context).nextFocus();

使用的context 是父级的context,它导致了重建。不知道为什么会这样,但是一旦我用Builder 包裹TextFormField 并开始使用它的context,它就停止了。

注意:我也正常使用MediaQuery.of(context).size.height(没有重建副作用)来设置小部件的父高度?

【讨论】:

【参考方案2】:

LayoutBuilder 似乎优于每次使用 MediaQuery 来调整视口(整个屏幕,或列中的剩余空间或其他布局)。

LayoutBuilder 还努力避免在大小不变且父级不必重新布局的情况下重建其子级。

builder函数在以下情况下被调用:

第一次布局小部件。 当父小部件传递不同的布局约束时。 当父小部件更新此小部件时。 当构建器函数订阅的依赖发生变化时。

如果父级通过,则布局期间不会调用构建器函数 重复相同的约束。

而且您不必再考虑“应用栏的高度”,因为您获得的是 left 空间,而不是屏幕上的总 空间 em>。

查看:https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html

【讨论】:

【参考方案3】:

我也遇到过这个问题,最初认为 MediaQuery 会导致不必要的重建,但如果您考虑一下,您确实希望小部件重建(在设备旋转、键盘弹出的情况下)以使应用程序具有响应设计。

你可以这样做:

import 'package:flutter/material.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Builder(builder: (context) 
        ResponsiveApp.setMq(context);
        return MyHomePage(title: 'Flutter Demo Home Page');
      ),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key key, this.title) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  int _counter = 0;

  void _incrementCounter() 
    setState(() 
      _counter++;
    );
  

  @override
  Widget build(BuildContext context) 
    
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Flex(
          direction:
              ResponsiveApp().mq.size.width > ResponsiveApp().mq.size.height
                  ? Axis.horizontal
                  : Axis.vertical,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  


class ResponsiveApp 
  static MediaQueryData _mediaQueryData;

  MediaQueryData get mq => _mediaQueryData;

  static void setMq(BuildContext context) 
    _mediaQueryData = MediaQuery.of(context);
  


我将 mediaQueryData 设置在 ResponsiveApp.setMq(context) 开头,我使用了 Builder,因为您只能在 MaterialApp 小部件下方使用一个 context 的 MediaQuery。设置_mediaQueryData 后,您可以随时根据屏幕尺寸构建小部件。

在这段代码中,我只是在设备旋转时更改轴方向,并且小部件需要重建以显示更改的方向。

你也可以有类似的东西:


    if (_mediaQueryData.size.shortestSide < 400)
         //phone layout
    else if(_mediaQueryData.size.shortestSide >= 400 && _mediaQueryData.size.shortestSide < 600)
          //tablet layout
    else
          //web layout

在网页中调整窗口大小将导致小部件多次重建并显示所需的布局。

但是如果你根本不想使用MediaQuery,你可以检查dart:ui的Window类。

【讨论】:

虽然Randal Schwartz 提出的解决方案有效,但我最终还是这样做了,因为将它集成到我现有的应用程序中更简单一些。谢谢!

以上是关于Flutter - 使用 MediaQuery 时防止重建的主要内容,如果未能解决你的问题,请参考以下文章

Flutter MediaQuery获取屏幕信息以及屏幕适配

错误记录Flutter 使用 MediaQuery 适配全面屏报错 ( No MediaQuery widget ancestor found. )

如何解决 Flutter 中的 MediaQuery 错误? [复制]

Flutter - 我如何使用 MediaQuery.of(context).copyWith(textScaleFactor)

Flutter iOS - MediaQuery.of(context).size.width 没有以像素为单位给出宽度

无法在 Flutter Web 中使用 MediaQuery