Flutter (Dart): Исключения, вызванные рендерингом / Переполнение RenderFlex

Вопрос или проблема

У меня проблема с переполнением пикселей RenderFlex в Flutter (Dart). Исключение из библиотеки рендеринга.

Как я могу управлять или применить возможность прокрутки к представлению страницы моего приложения и избежать исключений рендеринга Flutter с сообщениями, подобными:

RenderFlex переполнил на 28 пикселей снизу.

Если вам нужен полный журнал, чтобы помочь мне, он здесь:

введите описание изображения здесь

при горячей перезагрузке появляется желто-черная полоска внизу, согласно сообщению.

Могу ли я управлять этим с помощью прокручиваемого виджета? Или я могу объявить свои виджеты иначе, чтобы контролировать это?

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

   @override
  Widget build(BuildContext context) {
    return new DefaultTabController(
        length: 3,
        child: new Scaffold(
          appBar: new AppBar(
            bottom: new TabBar(
              tabs: [
                new Tab(text: "xxx",),
                new Tab(text: "xxx",),
                new Tab(text: "xxx",),
              ],
            ),
            title: new Text(data["xxx"]),
          ),
          body: new TabBarView(
            children: [
              new Column(
                children: <Widget>[
                  new Text(data["xxx"],
                        style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.blue,
                              fontSize: 16.0
                        ),),
                  new Text(data["xxx"],
                        style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.blue,
                              fontSize: 10.0
                        ),),
                  new Text(data["xxx"],
                        style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.blue,
                              fontSize: 16.0
                        ),),
                  new Text(data["xxx"],
                        style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.blue,
                              fontSize: 8.0
                        ),
                      ),
                  new Text(data["xxx"],
                        style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.blue,
                              fontSize: 8.0
                        ),),

                  new Row(
                    children: <Widget>[
                      new Expanded(
                        child: new Text("xxx"),
                      ),
                      new Expanded(
                        child: new Icon(Icons.file_download, color: Colors.green, size: 30.0,),
                      ),
                    ],
                  ),

                  new Divider(),
                  new Text("xxx", 
                            style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.red,
                              fontSize: 16.0
                      ),
                  ),
                ],
              ),

              new ListView.builder(
                itemBuilder: (BuildContext context, int index) => new EntryItem(_lstTiles[index]),
                itemCount: _lstTiles.length,
              ),

              new Column(
                children: <Widget>[
                  new Text(data["xxx"], 
                            style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.green[900],
                              fontSize: 16.0
                      ),
                  ),
                  new Text(data["xxx"], 
                            style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.green[900],
                              fontSize: 16.0
                      ),),
                  new Text(data["xxx"]),
                  new ListTile(title: new Text("xxx")),
                  new Text(data["xxx"]),
                  new ListTile(title: new Text("xxx")),
                  new Divider(),
                  new Text("xxx", 
                            style: new TextStyle(
                              fontStyle: FontStyle.italic,
                              color: Colors.red,
                              fontSize: 16.0
                      ),
                  ),
                ],
              ),
            ],
          ),
        ),
      );
  }

Это довольно распространенная проблема, с которой сталкиваются, особенно когда вы начинаете тестировать свое приложение на нескольких устройствах и в разных ориентациях. Галерея виджетов Flutter содержит раздел, посвященный различным прокручиваемым виджетам:

https://flutter.io/widgets/scrolling/

Я бы рекомендовал либо обернуть все свое содержимое в SingleChildScrollView, либо использовать прокручиваемый ListView.

ИЗМЕНЕНИЕ: Этот вопрос и ответ получили некоторое внимание, поэтому я хотел бы предоставить немного больше помощи для тех, кто попадает сюда.

Команда Flutter SDK прилагает много усилий для хорошей документации внутри самого кода SDK. Один из лучших ресурсов для понимания алгоритма, который виджеты Flex (как Row, так и Column являются подклассами Flex) используют для раскладки своих детей — это DartDoc, который прилагается к самому классу:

https://github.com/flutter/flutter/blob/e3005e6962cfefbc12e7aac56576597177cc966f/packages/flutter/lib/src/widgets/basic.dart#L3724

Сайт Flutter также содержит учебник по построению макетов и интерактивный кодлаб о том, как использовать виджеты Row и Column.

Предположим, у вас есть List из 100 Text виджетов, как этот:

final children = List<Widget>.generate(100, (i) => Text('Item $i')).toList();

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

  1. Используйте Column, обернутый в SingleChildScrollView

    SingleChildScrollView(
      child: Column(children: children),
    )
    
  2. Используйте ListView

    ListView(
      children: children
    )
    
  3. Используйте комбинацию Column и ListView (вы должны использовать Expanded/Flexible или задать фиксированную высоту для ListView при этом).

    Column(
      children: [
        ...children.take(2).toList(), // показать первые 2 дочерних элемента в Column
        Expanded(
          child: ListView(
            children: children.getRange(3, children.length).toList(),
          ), // И остальные в ListView
        ),
      ],
    )
    

Я также сталкивался с этой проблемой, попробуйте это ….

resizeToAvoidBottomPadding: false,

Перед частью тела

ИЗМЕНЕНИЕ – 1

resizeToAvoidBottomPadding устарел

resizeToAvoidBottomInset: false

Если вы используете Column, оберните свой внутренний виджет в expanded, как это

 Expanded( 
        child: Center(
          child: Image(
            image: AssetImage('assets/icons/${menuItem.iconData}'),
          ),
        ),
      ),

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

Оберните Column в SingleChildScrollView.

Если Column вас не устраивает, попробуйте Wrap:

// не проходит модульные тесты
return Column(
  mainAxisSize: MainAxisSize.min,
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Icon(
      icon ?? Icons.cancel_outlined,
      size: 64.0,
      color: theme.colorScheme.error,
    ),
    Text(
      errorTitle,
      style: theme.textTheme.displayLarge,
      textAlign: TextAlign.center,
    ),
    Text(
      errorDetails,
      style: theme.textTheme.titleLarge,
      textAlign: TextAlign.center,
    ),
    if (onPressedActionButton != null && textActionButton != null)
      ElevatedButton(
        onPressed: onPressedActionButton,
        child: Text(textActionButton!),
      ),
  ],
);

// проходит модульные тесты
return Wrap(
  direction: Axis.horizontal,
  alignment: WrapAlignment.center,
  runSpacing: 32,
  children: [
    Icon(
      icon ?? Icons.cancel_outlined,
      size: 64.0,
      color: theme.colorScheme.error,
    ),
    Text(
      errorTitle,
      style: theme.textTheme.displayLarge,
      textAlign: TextAlign.center,
    ),
    Text(
      errorDetails,
      style: theme.textTheme.titleLarge,
      textAlign: TextAlign.center,
    ),
    if (onPressedActionButton != null && textActionButton != null)
      ElevatedButton(
        onPressed: onPressedActionButton,
        child: Text(textActionButton!),
      ),
  ],
);

Вы можете обернуть текст в виджет PreferredSize:

 bottom: PreferredSize(
                    preferredSize: Size.fromHeight(120.0),
                    child: Column(
                      children: <Widget>[
                        Padding(
                          padding: EdgeInsets.only(
                              right: 5.0, left: 5.0, bottom: 2.0, top: 2.0),
                          child: Text(
                            hospitalName,
                            textAlign: TextAlign.left,
                            style: TextStyle(
                                fontWeight: FontWeight.bold,
                                color: Colors.white,
                                fontSize: 14.0),
                          ),
                        ),
                        Padding(
                          padding: EdgeInsets.symmetric(horizontal: 10.0),
                          child: Container(
                            height: 1.0,
                            width: MediaQuery.of(context).size.width,
                            color: Colors.white,
                          ),
                        ),
                        Padding(
                          padding: EdgeInsets.only(
                              top: 2.0, right: 5.0, left: 5.0, bottom: 2.0),
                          child: Text(
                            doctorName,
                            textAlign: TextAlign.left,
                            style: TextStyle(
                                fontWeight: FontWeight.bold,
                                color: Colors.white,
                                fontSize: 14.0),
                          ),
                        ),
                        Padding(
                          padding: EdgeInsets.symmetric(horizontal: 10.0),
                          child: Container(
                            height: 1.0,
                            width: MediaQuery.of(context).size.width,
                            color: Colors.white,
                          ),
                        ),

                        TabBar(
                          isScrollable: true,
                          tabs: [
                            Tab(
                              text: "Tab1",
                            ),
                            Tab(text: "Tab2"),
                            Tab(text: "Tab3"),
                          ],
                        ),
                      ],
                    )),

Для меня я добавил текст к одному из дочерних элементов строки, я получал данные из firebase, и они были null. Так что убедитесь, что данные поступают.

при увеличении размера текста, не желая увеличивать размер виджета, используйте FittedBox для идеальной подгонки

например

      Text(
          'Unread: $notificationCount',
          style: TextStyle(fontSize: 12, color: Colors.red),
        ),

        //вы можете сделать это, обернув виджет в FittedBox
   FittedBox(
          fit: BoxFit.scaleDown, // отрегулируйте свойство BoxFit по своему усмотрению
          child: Text(
            'Unread: $notificationCount',
            style: TextStyle(fontSize: 12, color: Colors.red),
          ),
        ),

Я столкнулся с этой проблемой при написании patrolWidgetTest для виджета Form.

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

Таким образом, код изменился с

Material(child: Form(
  key: formKey,
  child: formUnderTest,
));

на

Material(child: SingleChildScrollView(
   child: Form(
      key: formKey,
      child: formUnderTest,
    ),
 ))

где formUnderTest — это объект виджета формы, который мы хотим протестировать

Ошибка, возникшая при запуске теста, была следующей

Определенный RenderFlex, который вызывает вопросы: RenderFlex#b642a ПЕРЕПОЛНЯЕТСЯ:
  нуждается в компоновке
  создатель: Column ← SizedBox ← EjItemPicker ← Padding ← Stack ←
    NotificationListener<DraggableScrollableNotification> ← DefaultTextStyle ←
    AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#53d6d ink renderer] ←
    NotificationListener<LayoutChangedNotification> ← CustomPaint ← _ShapeBorderPaint ← ⋯
  parentData: <none> (можно использовать размер)
  ограничения: BoxConstraints(w=640.0, h=200.0)

Другой вариант, который сработал, заключался в увеличении размера во время теста, чтобы сделать вид, что у нас больший экран. Поскольку экран по умолчанию, используемый патрульным тестом виджетов, маленький constraints: BoxConstraints(w=640.0, h=200.0)

Таким образом, в тесте, перед инициализацией приложения

final dpi = $.tester.view.devicePixelRatio;
$.tester.view.physicalSize = Size(412 * dpi, 915 * dpi);

Ответ или решение

Обработка переполнения RenderFlex в Flutter

Ошибка переполнения RenderFlex в Flutter — это распространенная проблема, с которой сталкиваются разработчики, особенно когда работают с динамическим контентом, который может превышать размеры экрана. Когда вам выдается сообщение о том, что «RenderFlex переполнен на 28 пикселей снизу», это означает, что ваши виджеты не помещаются в отведенное для них пространство.

Причины возникновения ошибки

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

Решения проблемы с переполнением

  1. Использование SingleChildScrollView:
    Один из простых способов управления переполнением — обернуть ваш Column в SingleChildScrollView. Это позволяет вашим дочерним элементам прокручиваться, если они выходят за пределы видимой области.

    @override
    Widget build(BuildContext context) {
     return SingleChildScrollView(
       child: Column(
         children: [
           // ваши виджеты
         ],
       ),
     );
    }
  2. Замена на ListView:
    Если ваш контент лучше структурировать в виде списка, стоит рассмотреть использование ListView как альтернативу. Этот виджет по умолчанию поддерживает прокрутку.

    @override
    Widget build(BuildContext context) {
     return ListView(
       children: [
         // ваши виджеты
       ],
     );
    }
  3. Комбинация Column и ListView:
    При необходимости совместить статичный и динамичный контент, можно использовать несокращающий Column, содержащий ListView. Здесь важно использовать Expanded или фиксировать высоту ListView, чтобы избежать переполнения.

    @override
    Widget build(BuildContext context) {
     return Column(
       children: [
         // Статичный контент
         Expanded(
           child: ListView(
             children: [
               // Динамический контент
             ],
           ),
         ),
       ],
     );
    }
  4. Настройка resizeToAvoidBottomInset:
    Если ваша страница находится под клавиатурой, то имеет смысл использовать следующую настройку в Scaffold:

    Scaffold(
     resizeToAvoidBottomInset: true, // По умолчанию. Измените на false, если не требуется.
     body: // ваши виджеты
    );
  5. Использование Expanded:
    Внутри Column можно использовать Expanded, чтобы указать виджет, который должен занять свободное пространство.

    Column(
     children: [
       Text('Пример текста'),
       Expanded(
         child: ListView(
           children: [
             // ваши элементы списка
           ],
         ),
       ),
     ],
    );

Обработка ошибок

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

Заключение

Ошибки переполнения виджетов могут быть простыми в исправлении, если использовать подходящие виджеты для управления контентом. Используйте SingleChildScrollView, ListView, и комбинированные подходы для более гибкого контроля расположения виджетов. Правильные настройки и аккуратная работа с размерами помогут избежать этих распространенных проблем разработки в Flutter.

Адекватное обращение с вариантами размещения виджетов гарантирует плавную работу вашего приложения и улучшенное взаимодействие пользователей с интерфейсом.

Оцените материал
Добавить комментарий

Капча загружается...