Flutter Layout - Flex, FittedBox, Stack, Container Layout Tutorial [Супер подробный]

Flutter

макет

Гибкая компоновка

Макет Flex во Flutter похож на макет Flex в CSS для Интернета.

Существуют элементы управления Row, Column, Expanded, Flexible, Spacer, Flex для управления макетом Flex во Flutter.

горизонтальное расположение строк

Имя свойства Типы По умолчанию иллюстрировать
mainAxisAlignment MainAxisAlignment MainAxisAlignment.start Расположение шпинделей
mainAxisSize MainAxisSize MainAxisSize.max Размер пространства, занимаемого шпинделем
crossAxisAlignment CrossAxisAlignment CrossAxisAlignment.center Расположение вторичной оси
textDirection TextDirection null Определить порядок размещения детей в горизонтальном направлении.
verticalDirection VerticalDirection VerticalDirection.down Определить порядок размещения детей по вертикали.
textBaseline TextBaseline null выравнивание текста

Сначала создадим три контейнера разного размера.


class LyoutRowDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("水平布局"),
        ),
        body: Container(
          child: Row(
            children: <Widget>[
              Container(
                height: 100,
                width: 50,
                color: Colors.redAccent,
              ),
              Container(
                height: 50,
                width: 50,
                color: Colors.blueAccent,
              ),
              Container(
                color: Colors.black,
                height: 75,
                width: 75,
              )
            ],
          ),
        ));
  }
}

Расположение шпинделя

Расположение дочерних элементов определяется этими двумя свойствами, textDirection и verticalDirection. textDirection определяет горизонтальное расположениеTextDirection.ltrРасположите слева направо (используйте левую в качестве начальной позиции),TextDirection.rtlРасположите справа налево (используйте право как исходное положение). Горизонтальное расположение verticalDirection. Когда значение уменьшается (упорядочить вниз), начальная позиция находится вверху. Когда значение вверх (упорядочить вверх), начальная позиция находится внизу.

  • запуск (по умолчанию) Упорядочивает направление в соответствии со свойством textDirection.

    Дети, помещенные в начальную точку веретена

    Правильное изображение, когда значение атрибута textDirection равно rtl

  • end

    Упорядочивает направление в соответствии со свойством textDirection.

    поместите потомков в конец главной оси

    Правильное изображение, когда значение атрибута textDirection равно rtl

  • center

    Упорядочивает направление в соответствии со свойством textDirection.

    поместите детей в центр главной оси

    Правильное изображение, когда значение атрибута textDirection равно rtl

  • spaceBetween

    Упорядочивает направление в соответствии со свойством textDirection.

    Средняя пустая область в направлении основных осей, так что пустая область равна детям, ребенок близок к концу до конца, без разрыва

    Правильное изображение, когда значение атрибута textDirection равно rtl

  • spaceAround

    Упорядочивает направление в соответствии со свойством textDirection.

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

    Правильное изображение, когда значение атрибута textDirection равно rtl

  • spaceEvenly

    Упорядочить направление в соответствии со свойством textDirection

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

    Правильное изображение, когда значение атрибута textDirection равно rtl

Расположение поперечной оси crossAxisAlignment

оба сmainAxisAlignment: MainAxisAlignment.startНапример

  • start

    Выравнивание дочерних элементов по поперечной оси. Установите для verticalDirection значение VerticalDirection.up справа.

  • end

    Выровняйте дочерние элементы по концу поперечной оси. Установите для verticalDirection значение VerticalDirection.up справа.

  • center

    По центру дочерние элементы выравниваются по поперечной оси. Параметр verticalDirection не изменился.

  • strentch

    Растянуть дочерние элементы по поперечной оси

  • baseline

    Базовые линии применяются к тексту, мы сначала создаем начало текста. Используйте базовые показатели. Свойство textBaseline должно быть установлено. Текстовые базовые линии для разных текстов. В основном базовые линии буквенных символов (например, английского) и идеографических символов (например, китайского) различаются. Вы должны сообщить Flutter, какой текст вы используете.

    Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      textBaseline: TextBaseline.alphabetic,
      children: [
        Text(
          'Flutter',
          style: TextStyle(
            color: Colors.yellow,
            fontSize: 30.0
          ),
        ),
        Text(
          'Flutter',
          style: TextStyle(
              color: Colors.blue,
              fontSize: 20.0
          ),
        ),
      ],
    );
    

установить выравнивание по базовой линии

Пространство, занимаемое главной осью mainAxisSize

mainAxisSize имеет только два значения: одно минимальное, а другое максимальное. По умолчанию макс.

При значении мин. Шпиндель затянут до минимума.

Как показано на рисунке: даже тогда mainAxisAlignment: является MainAxisAlignment.spaceAround. Длина главного вала по-прежнему находится в минимальном состоянии

Вертикальное расположение столбцов

Свойства и методы вертикальной компоновки и горизонтальной компоновки одинаковы, единственное, на что стоит обратить внимание, это textDirection (горизонтальное расположение) и verticalDirection (вертикальное расположение)

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "垂直布局",
        home: Scaffold(
            appBar: AppBar(title: Text("垂直布局")),
            body: Center(
              child: Column(
			   mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  Text("1",style: TextStyle(fontSize: 100),),
                  Expanded(
                    child: Text("2"),
                  ),
                  Text("33333"),
                  Text("4")
                ],
              ),
            )));
  }
}

Для удобства просмотра мы расширили 1

Гибкая компоновка

Мы смотрим на следующий исходный код для легкого понимания

можно увидетьRowа такжеColumnкак унаследованные, так иFlex.

class Row extends Flex 
class Column extends Flex   

а такжеRowНеобязательные именованные параметры конструктора (т.е.{}Параметры пакета) есть 8.

передается родителюsuperКонструкторов 9, а есть и большеdirection: Axis.horizontal.

Row({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  }) : super(
    children: children,
    key: key,
    direction: Axis.horizontal,
    mainAxisAlignment: mainAxisAlignment,
    mainAxisSize: mainAxisSize,
    crossAxisAlignment: crossAxisAlignment,
    textDirection: textDirection,
    verticalDirection: verticalDirection,
    textBaseline: textBaseline,
  );
}

Свойства во FlexdirectionСобственно направление главной оси. и был@requiredвызывать. Требуется.

Flex({
    Key key,
    @required this.direction,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.mainAxisSize = MainAxisSize.max,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    List<Widget> children = const <Widget>[],
  }) : assert(direction != null),
       assert(mainAxisAlignment != null),
       assert(mainAxisSize != null),
       assert(crossAxisAlignment != null),
       assert(verticalDirection != null),
       assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
       super(key: key, children: children);

Expanded

В приведенном выше примере мы сталкиваемся с виджетом, которого раньше никогда не видели. Это легко увидеть. Расширенный игнорирует размер дочерних элементов и принудительно автоматически расширяет основную ось.ОтецДоступное пустое место. Expanded должен быть потомком Row, Column и Flex.

Следующие два примера предназначены для лучшего понимания:

//当子元素只有一个Expanded时
class LyoutExpanded extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Expanded"),
      ),
      body: Center(
        child: Container(
          child: Column(
            children: <Widget>[
              Expanded(
                child: Container(
                  color: Colors.blue,
                  width: 100,//这个被忽略掉了
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class LyoutExpanded extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Expanded"),
      ),
      body: Center(
        child: Container(
          height: 50,//填充使主轴扩展至父级高度
          child: Column(
            children: <Widget>[
           Expanded(
                child: Container(
                  color: Colors.blue,
                  height: 100,//
                  width: 100,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Expanden также обладает свойствомflexЗначение по умолчанию — 1. представляет вес. Когда дочерние элементы имеют несколько Expanden. Занять высоту родителя в соответствии со значением веса (ширина по горизонтали при строке)

<Widget>[
    Expanded(
        flex: 2,
        child: Container(
            color: Colors.blue,
            width: 100,
        ),
    ),
    Expanded(
        child: Container(
            color: Colors.red,
            width: 100,
        ),
    ),
    Expanded(
        child: Container(
            color: Colors.yellow,
            width: 100,
        ),
    ),
],

Мы видим, что синие блоки в два раза больше красных и желтых.

Flexible

Гибкие компоненты могут заставлять Row, Column, Flex и другие подкомпоненты заполнять доступное пространство в направлении главной оси.способность(например, строка горизонтальна, а столбец вертикальна), но, в отличие от расширенных компонентов, он не заставляет дочерние компоненты заполнять доступное пространство. Точно так же гибкие компоненты должны быть дочерними элементами таких компонентов, как Row, Column и Flex.

Гибкость имеет свойстваfitКогда значение свойства равно FlexFit.tight. Нет никакой разницы между гибким и расширенным.

Из исходного кода видно, что расширенное наследование и гибкое

И значение, переданное для соответствия конструктору верхнего уровня, — FlexFit.tight.

class Expanded extends Flexible
//省区其他源码
    

const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
    

Пройдите в детей Колонки. Подходящий атрибут: FlexFit.tight, заполните пустую область в соответствии с весом.

<Widget>[
    Flexible(
        fit: FlexFit.tight,
        child: Container(
            color: Colors.blue,
            height: 100,
            width: 100,
        ),
    ),
    Flexible(
        fit: FlexFit.tight,
        flex: 2,
        child: Container(
            color: Colors.yellow,
            height: 100,
            width: 100,
        ),
    ),
],

Как только значение fit равно FlexFit.loose (по умолчанию)Сначала определите размер главной оси в соответствии со значением flex, а затем отрегулируйте высоту элемента дочерним элементом. Нет принудительной растяжки

Например, первый — жесткое принудительное расширение. Второй сыпучий не форсирует расширение.

<Widget>[
    Flexible(
        fit: FlexFit.tight,
        child: Container(
            color: Colors.blue,
            height: 100,
            width: 100,
        ),
    ),
    Flexible(
        fit: FlexFit.loose,
        flex: 2,
        child: Container(
            color: Colors.yellow,
            height: 100,
            width: 100,
        ),
    ),
],

Поскольку второй контейнер свободен, высота желтого контейнера равна 100. Значение подгонки синего контейнера плотное, а его размер такой же, как и в предыдущем примере.

Spacer

Spacer создает регулируемое пустое пространство, которое можно использовать для настройки интервала между виджетами во гибком контейнере (строка или столбец).

После того, как дети содержат Spacer.mainAxisAlignmentСтоимость свойства будетне работает. Spacer занял все дополнительное пространство, поэтому места для перераспределения не осталось.

Row(
    mainAxisAlignment: MainAxisAlignment.end,//起不到作用
    children: <Widget>[
        Container(
            height: 100,
            width: 50,
            color: Colors.redAccent,
        ),
        Spacer(),
        Container(
            height: 50,
            width: 50,
            color: Colors.blueAccent,
        ),
        Container(
            color: Colors.black,
            height: 75,
            width: 75,
        )
    ],
),

Увеличить макет

Подавляющее большинство флаттер-виджетов представляют собой коробки. Их можно штабелировать, штабелировать, вкладывать друг в друга.

Мы можем иерархически вкладывать ящики, но если ящик не подходит (не подходит размер) другой ящик. Как решить?

Чтобы решить эту проблему, Flutter предоставляет FittedBox, аналогичный ImageView для мобильных устройств.

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

  • BoxFit.contain (по умолчанию) пропорционально расширяется или сжимается, но содержимое не выходит за рамки контейнера.

  • BoxFit.cover постепенно расширяется, чтобы пропорционально заполнить контейнер, и содержимое может выходить за пределы контейнера. Когда дочерний масштаб отличается от контейнера, либо высота переполняет контейнер, либо ширина переполняет контейнер.

  • BoxFit.fill не сохраняет пропорции и заставляет контейнер с наполнителем растягиваться (сжиматься).

  • BoxFit.fitHeight поддерживает пропорции, чтобы гарантировать полное отображение высоты в контейнере.

  • BoxFit.fitWidth поддерживает соотношение, чтобы гарантировать полное отображение ширины в контейнере.

  • BoxFit.none выравнивает дочерний элемент в целевом поле (при воспроизведении по умолчанию) и отбрасывает часть за пределами поля, исходный файл не увеличивается и не сжимается.

  • BoxFit.scaleDown выравнивает дочерний элемент в целевом поле (при воспроизведении по умолчанию), когда дочерний элемент больше контейнера, он согласуется с контейнером. Если дочерний элемент меньше контейнера, он не совместим ни с одним

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

class Lyoutfitdemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("data")),
      body: Container(
        height: 250,
        width: 250,
        color: Colors.indigo[200],
        child: FittedBox(
          child: Image.asset('images/fittedbox.png')),
      ),
    );
  }
}
  1. BoxFit.contain

  1. BoxFit.cover

  1. BoxFit.fill

  1. BoxFit.fitHeight

5.BoxFit.fitWidth

  1. BoxFit.none

  1. BoxFit.scaleDown

Примечание. Нет необходимости создавать FittedBox, изображение содержит атрибут fit. Эффект значения такой же, как у FittedBox.

Компоновка с накоплением

Стек похож на веб-модель компоновки абсолютного позиционирования. Стопки не выкладываются в ряды или столбцы, а складываются в определенном порядке и положении. можно использоватьPositionedа такжеAlignПозиционирование как стек.

Пример: реализация эффекта градиента изображения с использованием каскадного макета

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "渐变状态栏",
      home: Scaffold(
        body: Container(
          height: 400,
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Image.asset(
                'images/Stack.png',
                fit: BoxFit.cover,
              ),
          
              const DecoratedBox(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment(0.0, -1.0),
                    end: Alignment(0.0, -0.4),
                    colors: <Color>[Color(0x90000000), Color(0x00000000)],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

эффект градиента изображения

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

Выровнять макет

Align — компонент выравнивания, установленныйchildВыравнивание родителя (например, контейнера, стека и т. д.) и настройка его размера в соответствии с размером дочернего элемента.

Родительский контейнер этой статьи выбирает стек

Alignment

Среди них такие свойства

topLeft (вверху слева), topCenter (вверху в центре), topRight (вверху справа), centerLeft, center, centerRight, bottomLeft, bottomCenter, bottomRight

class LayoutAlignDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("align部件"),
        ),
        body: Stack(
          children: <Widget>[
            Align(
              alignment: Alignment.topLeft,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
          ],
        ));
  }
}

Alignment.lerp(Alignment a, Alignment b, double t)

Метод lerp имеет три параметра, первые два параметра относятся к типу Alignment, а третий параметр является десятичным.

Когда t равно 0, значение, возвращаемое этим методом, равно a.

Когда t равно 1, вернуть значение b.

Когда t равно 0,5, позиция виджета находится в середине позиции, указанной a и b.

Так что это t является смещением. Задается как смещение между a и b.

//在Stack children中添加一个align
Align(
  alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center,0.5),//位于Alignment.bottomCenter和Alignment.center的中央
  child: Container(
  width: 100,
  height: 100,
  color: Colors.yellow,
   ),
),

выравнивание со смещением

Пары смещений по отношению к их путям a и b описаны выше.

FractionalOffset — это еще один метод смещения, представляющий собой смещение относительно левого верхнего угла родительского виджета.

Создайте смещение FractionalOffset (dx,dy). Значения dx и dy равны 0~1. В верхнем левом положении dx и dy равны 0.

Align(
  alignment: FractionalOffset(0, 0.5),
  child: Container(
    width: 100,
    height: 100,
    color: Colors.red,
  ),
),

Также в исходном коде:

class FractionalOffset extends Alignment

FractionalOffsetУнаследовано от Alignment, поэтому можно использовать свойства Alignment, чтобы мы могли использовать виджет в верхнем левом углу.FractionalOffset.topLeft


class LayoutAlignDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("align部件"),
        ),
        body: Stack(
          children: <Widget>[
            Align(
              alignment: FractionalOffset(0, 0),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            Align(
              alignment: FractionalOffset(0, 0.5),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            Align(
              alignment: FractionalOffset(0, 1),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            Align(
              alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center,0.5),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.yellow,
              ),
            ),
            Align(
              alignment: FractionalOffset.topRight,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.yellow,
              ),
            ),
          ],
        ));
  }
}

Как и в случае с Alignment(dx, dy), источником системы координат является центр родительского контейнера. Диапазон значений dx составляет -1~1, а диапазон значений dy составляет -1~1. Устанавливает положение дочернего элемента.

Positioned

Позиционированные виджеты могут управлятьStackПоложение нейтронного элемента. Разница между Positioned и Align заключается в том, что Position должен быть дочерним элементом стека.

ПозицияДаtop,bottom,left,right,height,width.

top,bottom,left,rightЭти величины представляют собой расстояния между границей дочернего элемента и одной из границ родительского элемента.

То есть, когдаФиксированная высота и длинаКонтейнеры, как только мы определяем количество для левого или правого и количество для нижнего или верхнего, можно определить его положение.

часть кода

Stack(
    children: <Widget>[
        Positioned(
            top: 100,
            right: 100,
            width: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),
    ],
)

Анализ исходного кода

 const Positioned({
    Key key,
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    @required Widget child,
  }) : assert(left == null || right == null || width == null),
       assert(top == null || bottom == null || height == null),
       super(key: key, child: child);

В горизонтальном направлении, если assert(false) выдаст ошибку

В скобках указано значениеleft == null || right == null || width == null

Если хотя бы одно из значений left, right и width равно null, программа не сообщит об ошибке.

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

Stack(
    children: <Widget>[
        Positioned(
            top: 100,
            right: 100,
            left: 100,
            // width: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),
    ],
)

IndexedStack

IndexedStack наследуется от Stack

class IndexedStack extends Stack

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

IndexedStack(
    index: 1,//只显示第二个
    children: <Widget>[
        Positioned(
            top: 100,
            right: 100,
            left: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),  
        Positioned(
            top: 500,
            right: 100,
            left: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),
    ],
)

Компоновка контейнера

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

class LyoutContainerdemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Container容器"),
      ),
      body: Container(
        height: 200,
        width: 200,
        child: Text("这是一个Container容器"),
        decoration: BoxDecoration(
          color: Colors.red[200],
          // shape: BoxShape.circle, //形状
          border: Border.all(width: 10.0),
          boxShadow: [
            BoxShadow(
              offset: Offset(0.0, 100.0), //模糊偏移量
              color: Color.fromRGBO(16, 20, 188, 1.0), //颜色
              blurRadius: 10, //模糊
              spreadRadius: -9.0, //在应用模糊之前阴影的膨胀量
            ),
          ],
        ),
      ),
    );
  }
}

Чтобы продемонстрировать функцию, эффект этой картинки преувеличен.

Добавьте красивый эффект

Подобно макету поля Css, мы также можем установить поля для поля через Margin.

Container(
    margin: EdgeInsets.all(50.0),//外边距50.0
    height: 200,
    width: 200,
    decoration: BoxDecoration(
        color: Colors.red[600],
        border: Border.all(width: 2.0),
        boxShadow: [
            BoxShadow(
                offset: Offset(2.0, 9.0), //偏移量
                color: Colors.red[200], //颜色
                blurRadius: 10, //模糊
                spreadRadius: -1.0, //在应用模糊之前阴影的膨胀量
            ),
        ],
    ),
),

Макет заполнения

Используется для обработки расстояния между контейнером и дочерними элементами. Свойству Padding соответствует свойство margin. поля используются для обработки расстояний от других компонентов. Эффект компонента padding и свойства padding в контейнере на самом деле одинаков.Когда они появляются одновременно, реальным дополнением будет их сложение.

class LayoutPaddingDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Padding 布局"),
      ),
      body: Container(
        padding: EdgeInsets.all(20.0),//#1这两处的属性效果是一致的
        decoration: BoxDecoration(
            color: Colors.yellow,
            border: Border.all(color: Colors.white, width: 8.0)),//白色边框
        child: Padding(
          child: Container(
            decoration: BoxDecoration(
            color: Colors.red,
            border: Border.all(color: Colors.white, width: 8.0)),//白色边框
          ),
          padding: EdgeInsets.all(10.0),//#1这两处的属性效果是一致的
        ),
      ),
    );
  }
}

написать на обороте

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

Ссылка на ссылку

youtu.be/T4Uehk3_wlY

medium.com/Сколько этажей/Приложение…

api.flutter.dev/index.html

flutter.dev/docs