Flutter - предотвратить перестроение при использовании MediaQuery

Я хочу использовать MediaQuery для создания виджетов на основе высоты и ширины экрана. Проблема, о которой также упоминается в # 26004, заключается в том, что я хочу запросить только размер данные один раз, например в initState. MediaQuery в документации указано

Запрос текущего мультимедиа с помощью MediaQuery.of приведет к автоматическому перестроению вашего виджета при изменении MediaQueryData (например, если пользователь поворачивает свое устройство).

, но это вызывает ненужные перестройки в моем приложении. В частности, он вызывает перестройку виджетов, если есть изменения во вставках или отступах (например, при отображении клавиатуры).

Есть ли альтернатива MediaQuery, которая не вызывала бы перестроек при MediaQueryData изменениях?


person Benjamin Smrdelj    schedule 14.01.2021    source источник


Ответы (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, потому что вы можете использовать только MediaQuery one context под виджетом MaterialApp. После установки _mediaQueryData вы можете получить его, когда захотите создавать виджеты на основе размера экрана.

В этом коде я просто меняю направление оси, когда устройство поворачивается, и виджету необходимо перестроить, чтобы показать измененное направление.

У вас также может быть что-то вроде:


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

а изменение размера окна в сети приведет к многократной перестройке виджетов и отображению желаемого макета.

Но если вы вообще не хотите использовать MediaQuery, вы можете проверить класс Window из dart:ui.

person Calvin Gonsalves    schedule 14.01.2021
comment
Хотя решение, предложенное Randal Schwartz, работает, в итоге я сделал это таким образом, поскольку было немного проще интегрировать его в мое существующее приложение. Спасибо! - person Benjamin Smrdelj; 15.01.2021

LayoutBuilder кажется предпочтительнее любого использования MediaQuery для изменения размера области просмотра (либо всего экрана, либо пространства, оставшегося в столбце или другом макете).

LayoutBuilder также прилагает все усилия, чтобы избежать перестройки своего дочернего элемента, если размер не изменился и родителям не пришлось менять макет.

Функция построителя вызывается в следующих ситуациях:

  • Первый раз виджет выкладывается.
  • Когда родительский виджет передает различные ограничения макета.
  • Когда родительский виджет обновляет этот виджет.
  • Когда зависимости, на которые подписывается функция построителя, изменяются.

Функция построителя не вызывается во время макета, если родительский элемент многократно передает одни и те же ограничения.

И вам больше не нужно думать о высоте панели приложений, потому что вы получаете пространство left, а не общее пространство на экране.

Проверьте это: https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html

person Randal Schwartz    schedule 14.01.2021

В моем случае проблема возникла из-за того, что я управлял фокусом вручную, используя:

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

Используемый context был context Родителя, и он вызывал перестройку. Не знаю, почему это произошло, но это прекратилось, как только я заключил TextFormField в Builder и начал использовать вместо этого context.

Примечание: я также обычно использую MediaQuery.of(context).size.height (без побочного эффекта перестройки), чтобы установить высоту родительского виджета ????

person Guilherme V.    schedule 24.05.2021