# Введение в виджеты

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

Flutter отличается от других фреймворков тем, что его пользовательский интерфейс встроен в код, а не (например) в файл XML или что-то подобное. Виджеты — это основные строительные блоки пользовательского интерфейса Flutter. По мере прохождения этой кодовой лаборатории вы узнаете, что почти все во Flutter — это виджеты. Виджет — это неизменяемый объект, описывающий определенную часть пользовательского интерфейса. Вы также узнаете, что виджеты Flutter являются составными, что означает, что вы можете комбинировать существующие виджеты для создания более сложных виджетов.

{% hint style="info" %}
Если вы хотите лучше познакомиться с Flutter, углубившись в код, ознакомьтесь с [кодовой лабораторией базовой компоновки](https://docs.flutter.dev/codelabs/layout-basics) , [созданием макетов](https://docs.flutter.dev/development/ui/layout) и [добавлением интерактивности в ваше приложение Flutter](https://docs.flutter.dev/development/ui/interactive).
{% endhint %}

## Hello World

Минимальное приложение Flutter просто вызывает [`runApp()`](https://api.flutter.dev/flutter/widgets/runApp.html) функцию с виджетом:

{% embed url="<https://codepen.io/andery-fadeev/pen/ExbxrBv>" %}

Функция `runApp()`берет данное [`Widget`](https://api.flutter.dev/flutter/widgets/Widget-class.html)и делает его корнем дерева виджетов. В этом примере дерево виджетов состоит из двух виджетов, самого [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html)виджета и его дочернего элемента, [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html)виджета. Фреймворк заставляет корневой виджет закрывать экран, что означает, что текст «Hello, world» оказывается по центру экрана. В этом случае необходимо указать направление текста; когда `MaterialApp`виджет используется, об этом позаботятся, как показано ниже.

При написании приложения вы обычно создаете новые виджеты, которые являются подклассами либо , [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html)либо [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html), в зависимости от того, управляет ли ваш виджет каким-либо состоянием. Основная задача виджета — реализовать [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html)функцию, которая описывает виджет с точки зрения других виджетов более низкого уровня. Фреймворк строит эти виджеты по очереди, пока процесс не достигнет нижнего предела в виджетах, представляющих базовый элемент [`RenderObject`](https://api.flutter.dev/flutter/rendering/RenderObject-class.html), который вычисляет и описывает геометрию виджета.

<br>

## Основные виджеты

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

[**`Text`**](https://api.flutter.dev/flutter/widgets/Text-class.html)Виджет `Text`позволяет вам создать серию стилизованного текста в вашем приложении.

[**`Row`**](https://api.flutter.dev/flutter/widgets/Row-class.html)**,**[**`Column`**](https://api.flutter.dev/flutter/widgets/Column-class.html)Эти гибкие виджеты позволяют создавать гибкие макеты как в горизонтальном ( `Row`), так и в вертикальном ( `Column`) направлениях. Дизайн этих объектов основан на веб-модели компоновки flexbox.

[**`Stack`**](https://api.flutter.dev/flutter/widgets/Stack-class.html)Вместо линейной ориентации (горизонтальной или вертикальной) `Stack`виджет позволяет размещать виджеты друг над другом в порядке рисования. Затем вы можете использовать [`Positioned`](https://api.flutter.dev/flutter/widgets/Positioned-class.html)виджет для дочерних элементов, `Stack`чтобы расположить их относительно верхнего, правого, нижнего или левого края стека. Стеки основаны на модели макета абсолютного позиционирования в Интернете.

[**`Container`**](https://api.flutter.dev/flutter/widgets/Container-class.html)Виджет `Container`позволяет создать прямоугольный визуальный элемент. Контейнер можно украсить [`BoxDecoration`](https://api.flutter.dev/flutter/painting/BoxDecoration-class.html), например фоном, рамкой или тенью. A `Container`также может иметь поля, отступы и ограничения, применяемые к его размеру. Кроме того, а `Container`можно преобразовать в трехмерное пространство с помощью матрицы.

Ниже приведены несколько простых виджетов, объединяющих эти и другие виджеты:

{% code title="Dart" %}

```dart
import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  const MyAppBar({required this.title, Key? key}) : super(key: key);

  // Fields in a Widget subclass are always marked "final".

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        // <Widget> is the type of items in the list.
        children: [
          const IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child
          // to fill the available space.
          Expanded(
            child: title,
          ),
          const IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  const MyScaffold({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Material is a conceptual piece
    // of paper on which the UI appears.
    return Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: [
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context) //
                  .primaryTextTheme
                  .headline6,
            ),
          ),
          const Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      title: 'My app', // used by the OS task switcher
      home: SafeArea(
        child: MyScaffold(),
      ),
    ),
  );
}
```

{% endcode %}

![Добавлены виджеты: AppBar, IconButton](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2F2aQOiLrdFj5BcsOmBVbf%2Fimage.png?alt=media\&token=74db3d4d-cb0d-406a-92da-ab5a112835b5)

Многие виджеты Material Design должны быть внутри [`MaterialApp`](https://api.flutter.dev/flutter/material/MaterialApp-class.html) для правильного отображения, чтобы наследовать данные темы. Поэтому запускайте приложение с расширением `MaterialApp`.

{% hint style="info" %}
**Примечание.** Материал — это один из двух дизайнов, поставляемых вместе с Flutter. Чтобы создать дизайн, ориентированный на iOS, см. пакет [компонентов из Купертино](https://docs.flutter.dev/development/ui/widgets/cupertino) , в котором есть собственные версии [`CupertinoApp`](https://api.flutter.dev/flutter/cupertino/CupertinoApp-class.html), и [`CupertinoNavigationBar`](https://api.flutter.dev/flutter/cupertino/CupertinoNavigationBar-class.html).
{% endhint %}

### Обработка жестов

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

```dart
import 'package:flutter/material.dart';

class MyButton extends StatelessWidget {
  const MyButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 50.0,
        padding: const EdgeInsets.all(8.0),
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(5.0),
          color: Colors.lightGreen[500],
        ),
        child: const Center(
          child: Text('Engage'),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyButton(),
        ),
      ),
    ),
  );
}
```

&#x20;  ![](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2FdTmB3tKIbczH2K6O17rk%2Fimage.png?alt=media\&token=07479c7f-9957-4b6f-889e-4f47efb84b03)   ![](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2ForRzvBuyLCs9pwZNA5VU%2Fimage.png?alt=media\&token=8f0b13b5-aa2e-4ffe-b186-18f5657e73a9)

Виджет [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)не имеет визуального представления, но вместо этого обнаруживает жесты, сделанные пользователем. Когда пользователь нажимает [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html), `GestureDetector`вызывается [`onTap()`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html#onTap)обратный вызов, в данном случае вывод сообщения на консоль. Вы можете использовать `GestureDetector`для обнаружения различных жестов ввода, включая нажатия, перетаскивания и масштабирования.

### Изменение виджетов в ответ на ввод <a href="#changing-widgets-in-response-to-input" id="changing-widgets-in-response-to-input"></a>

Чтобы создать более сложный опыт — например, более интересно реагировать на пользовательский ввод — приложения обычно содержат некоторое состояние. Flutter использует `StatefulWidgets`, чтобы уловить эту идею. `StatefulWidgets`— это специальные виджеты, умеющие генерировать `State`объекты, которые затем используются для хранения состояния. Рассмотрим этот базовый пример, используя [`ElevatedButton`](https://api.flutter.dev/flutter/material/ElevatedButton-class.html)упомянутое ранее:

```dart
import 'package:flutter/material.dart';

class Counter extends StatefulWidget {
  // This class is the configuration for the state.
  // It holds the values (in this case nothing) provided
  // by the parent and used by the build  method of the
  // State. Fields in a Widget subclass are always marked
  // "final".

  const Counter({Key? key}) : super(key: key);

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework
      // that something has changed in this State, which
      // causes it to rerun the build method below so that
      // the display can reflect the updated values. If you
      // change _counter without calling setState(), then
      // the build method won't be called again, and so
      // nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make
    // rerunning build methods fast, so that you can just
    // rebuild anything that needs updating rather than
    // having to individually changes instances of widgets.
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
        const SizedBox(width: 16),
        Text('Count: $_counter'),
      ],
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Counter(),
        ),
      ),
    ),
  );
}
```

![При нажатии на кнопку инкремент увеличивает число на 1](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2FWqRGwuH4AJkA6dLmhKMx%2Fimage.png?alt=media\&token=f17a3e2f-2eee-4f2d-9ae3-2b45cf980675)

### Объединяя все это

Далее следует более полный пример, объединяющий эти концепции: гипотетическое приложение для покупок отображает различные продукты, предлагаемые для продажи, и поддерживает корзину покупок для предполагаемых покупок. Начните с определения класса представления `ShoppingListItem`:

```dart
import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = Function(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({
    required this.product,
    required this.inCart,
    required this.onCartChanged,
  }) : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different
    // parts of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

    return inCart //
        ? Colors.black54
        : Theme.of(context).primaryColor;
  }

  TextStyle? _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return const TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: Colors.greenAccent,
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: ShoppingListItem(
            product: const Product(name: 'Chips'),
            inCart: true,
            onCartChanged: (product, inCart) {},
          ),
        ),
      ),
    ),
  );
}
```

![](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2FAOVVfd146MbjxB8IY7GM%2Fimage.png?alt=media\&token=4622a476-3e17-4f17-ac12-6d8407ea77e0)

Виджет `ShoppingListItem`следует общему шаблону для виджетов без состояния. Он сохраняет значения, полученные в своем конструкторе, в [`final`](https://dart.dev/guides/language/language-tour#final-and-const)переменных-членах, которые затем использует во время своей [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html)функции. Например, `inCart`логическое значение позволяет переключаться между двумя визуальными представлениями: одно использует основной цвет из текущей темы, а другое — серый.

Вот пример родительского виджета, в котором хранится изменяемое состояние:

```dart
import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = Function(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({
    required this.product,
    required this.inCart,
    required this.onCartChanged,
  }) : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different
    // parts of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

    return inCart //
        ? Colors.black54
        : Theme.of(context).primaryColor;
  }

  TextStyle? _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return const TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(
        product.name,
        style: _getTextStyle(context),
      ),
    );
  }
}

class ShoppingList extends StatefulWidget {
  const ShoppingList({required this.products, Key? key}) : super(key: key);

  final List<Product> products;

  // The framework calls createState the first time
  // a widget appears at a given location in the tree.
  // If the parent rebuilds and uses the same type of
  // widget (with the same key), the framework re-uses
  // the State object instead of creating a new State object.

  @override
  _ShoppingListState createState() => _ShoppingListState();
}

class _ShoppingListState extends State<ShoppingList> {
  final _shoppingCart = <Product>{};

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // When a user changes what's in the cart, you need
      // to change _shoppingCart inside a setState call to
      // trigger a rebuild.
      // The framework then calls build, below,
      // which updates the visual appearance of the app.

      if (!inCart) {
        _shoppingCart.add(product);
      } else {
        _shoppingCart.remove(product);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shopping List'),
      ),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((Product product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(
    title: 'Shopping App',
    home: ShoppingList(
      products: [
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}
```

![](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2FlmipVmdkgMmSqG5KfYyh%2Fimage.png?alt=media\&token=757beb01-9abc-447f-bba5-bd3c746ce06e)   ![](https://2633649980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9ecjHyY4QqSBETN2afV7%2Fuploads%2FbATppO1wW01D0ji9xeWG%2Fimage.png?alt=media\&token=56247d15-56bf-4c19-bfaf-2f228ff9a2a8)

Класс `ShoppingList`extends [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html)означает, что этот виджет хранит изменяемое состояние. Когда `ShoppingList`виджет впервые вставляется в дерево, платформа вызывает [`createState()`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#createState)функцию для создания нового экземпляра `_ShoppingListState`для связи с этим местоположением в дереве. (Обратите внимание, что подклассы [`State`](https://api.flutter.dev/flutter/widgets/State-class.html)обычно имеют начальные символы подчеркивания, чтобы указать, что они являются частными деталями реализации.) Когда родитель этого виджета перестраивается, родитель создает новый экземпляр `ShoppingList`, но инфраструктура повторно использует `_ShoppingListState` экземпляр, который уже находится в дереве, а не вызывает `createState`снова.

### Реагирование на события жизненного цикла виджета <a href="#responding-to-widget-lifecycle-events" id="responding-to-widget-lifecycle-events"></a>

После вызова [`createState()`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#createState)фреймворк `StatefulWidget`вставляет новый объект состояния в дерево, а затем вызывает [`initState()`](https://api.flutter.dev/flutter/widgets/State-class.html#initState)объект состояния. Подкласс [`State`](https://api.flutter.dev/flutter/widgets/State-class.html)может переопределить `initState`для выполнения работы, которая должна произойти только один раз. Например, переопределите `initState` настройку анимации или подписку на службы платформы. Реализации `initState`должны начинаться с вызова `super.initState`.

Когда объект состояния больше не нужен, платформа вызывает [`dispose()`](https://api.flutter.dev/flutter/widgets/State-class.html#dispose)объект состояния. Переопределите `dispose`функцию, чтобы выполнить очистку. Например, переопределить, `dispose`чтобы отменить таймеры или отказаться от подписки на службы платформы. Реализации `dispose`обычно заканчиваются вызовом `super.dispose`.

Для получения дополнительной информации см [`State`](https://api.flutter.dev/flutter/widgets/State-class.html).

<br>

<br>
