Flutter - 当使用MediaQuery时如何防止重建

7
我想使用 MediaQuery 根据屏幕高度和宽度构建小部件。问题在于,我只想查询一次大小数据,例如在 initState 中,这也在#26004中引用。 MediaQuery 文档说明如下:

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

但是这会在我的应用程序中导致不必要的重建。具体来说,如果存在插图或填充的更改(例如,在显示键盘时),它将导致小部件重建。
是否有替代 MediaQuery 的方法,不会在 MediaQueryData 更改时引起重建?
4个回答

4

我也遇到了这个问题,最初认为是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);
  }
}

我在一开始使用ResponsiveApp.setMq(context)设置了mediaQueryData,并且使用了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

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

但是,如果您不想使用MediaQuery,可以检查来自dart:ui的Window类。


虽然Randal Schwartz提出的解决方案可行,但由于这种方式更容易集成到我的现有应用程序中,所以我最终采用了这种方式。谢谢! - Benjamin Smrdelj
不必要的构建怎么办?这段代码解决了什么问题? - undefined

3

LayoutBuilder 似乎比使用 MediaQuery 更可取,用于调整视口大小(无论是整个屏幕还是列或其他布局中剩余的空间)。

如果尺寸不变且父组件没有重新布局,LayoutBuilder 也会尽力避免重建其子组件。

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

  • 首次布局小部件时。
  • 当父小部件传递不同的布局约束时。
  • 当父小部件更新此小部件时。
  • 当 builder 函数订阅的依赖项发生更改时。

如果父级重复传递相同的约束,则不会在布局期间调用 builder 函数。

而且,您再也不必考虑“应用栏的高度”了,因为您得到的是剩下的空间,而不是屏幕上的总空间。

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


0
在我的情况下,问题是因为我手动控制焦点导致的:
onEditingComplete: () {
   FocusScope.of(context).nextFocus();
}

使用的是父级context,导致了重建。不确定为什么会发生这种情况,但一旦我使用Builder包装TextFormField并开始使用其context,问题就停止了。

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


0

不是:

MediaQuery.of(context).size..

尝试使用:

MediaQuery.sizeOf(context)...

不要进行不必要的重建。

有什么区别? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接