Работа с виджетами Flutter | 1 часть

Работа с виджетами Flutter | 1 часть
Работа с виджетами Flutter | 1 часть

Привет, если вы на пути изучения Flutter/Dart или вам просто интересно почитать про путь изучения, подписывайтесь на мой канал в telegram, буду рад вас видеть! А сегодня поговорим про взаимодействие с виджетами во Flutter!

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

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

  • Что такое виджет Scaffold
  • Как реорганизовать виджеты для повышения производительности контекста сборки
  • Что такое виджеты без сохранения состояния и с сохранением состояния
  • Как создавать интерфейсы с использованием виджетов строк и столбцов

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

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

Создание Stateless виджета

Проблема

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

Решение

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

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

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const title = 'Stateless Widget demo'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: const Text(title), ), body: const MyTextWidget(), ), ); } } class MyTextWidget extends StatelessWidget { const MyTextWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const Center( child: Text('Hello'), ); } }

Обсуждение

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

В примере нет состояния для сохранения, как показано на рисунке 9-1. Работа без состояния значительно упрощает архитектуру вашего приложения, поскольку вам приходится учитывать гораздо меньше факторов. Если вы можете, постарайтесь реализовать как можно большую часть вашего приложения в виде проекта без сохранения состояния, чтобы снизить общую сложность при разработке кода.

Рисунок 9-1. Пример виджета без сохранения состояния
Рисунок 9-1. Пример виджета без сохранения состояния

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

Создание Stateful виджета

Проблема

Вы хотите сохранить состояние (т.е. значение), связанное с виджетом Flutter.

Решение

Используйте Flutter StatefulWidget для сохранения значения в приложении. Объявление виджета с отслеживанием состояния указывает на то, что значение должно быть сохранено.

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

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const title = 'Stateless Widget demo'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: const Text(title), ), body: const MyTextWidget(), ), ); } } class MyTextWidget extends StatefulWidget { const MyTextWidget({Key? key}) : super(key: key); @override _MyTextWidget createState() => _MyTextWidget(); } class _MyTextWidget extends State<MyTextWidget> { int count = 0; @override Widget build(BuildContext context){ return GestureDetector( onTap: () { setState((){ count++; }); }, child: Center(child: Text('Click Me: $count')), ); } }

Обсуждение

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

На рисунке 9-2 событие приложения запускается каждый раз, когда пользователь нажимает на текстовый виджет. Событие регистрирует касание и выполняет вызов setState, увеличивающий значение count на единицу. Изменение состояния автоматически инициирует обновление контекста сборки, что означает, что экранное представление будет обновлено и покажет новое значение для count.

Рисунок 9-2. Пример виджета с отслеживанием состояния
Рисунок 9-2. Пример виджета с отслеживанием состояния

Управление состоянием в Flutter обычно использует шаблон, показанный на рисунке 9-3. Виджет с отслеживанием состояния требует создания нескольких методов, которые используются для сохранения информации.

Рисунок 9-3. Взаимодействие с виджетами с отслеживанием состояния
Рисунок 9-3. Взаимодействие с виджетами с отслеживанием состояния

Класс MyTextWidget stateful widget реализует метод createState. Значение, возвращаемое этим методом, присваивается частной переменной, т.е. _MyTextWidget. В Flutter приватным переменным присваивается префикс с символом подчеркивания. Обратите внимание, как приватная переменная создается аналогично виджетам без сохранения состояния, рассмотренным ранее. Введение новой функции, setState, используется для хранения значения, основанного на событии onTap. В примере переменная count увеличивается каждый раз, когда запускается событие onTap.

Приватный класс _MyTextWidget затем используется для инициирования изменения состояния. На диаграмме мы можем видеть, что onTap() используется для увеличения переменной count. Теперь, когда пользователь приложения взаимодействует и нажимает кнопку, переменная будет увеличиваться, и изменение состояния будет отражено в приложении.

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

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

Рефакторинг виджетов во Flutter

Проблема

Вам нужен способ улучшить читаемость кода.

Решение

Используйте рефакторинг для повышения общей надежности вашего кода. Рефакторинг позволяет упростить код.

Вот пример кода Flutter перед применением рефакторинга к функции сборки:

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const title = 'Image Widget'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: const Text(title), ), body: Container( width: 200, height: 180, color: Colors.black, child: Column( children: [ Image.network('https://oreil.ly/O4PEn'), const Text( 'itemTitle', style: TextStyle(fontSize: 20, color: Colors.white), ), const Text( 'itemSubTitle', style: TextStyle(fontSize: 16, color: Colors.grey), ), ], ), ), ), ); } }

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

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const title = 'Image Widget'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: const Text(title), ), body: MyContainerWidget(), ), ); } } class DataItem { final String title; final String subtitle; final String url; const DataItem({ required this.title, required this.subtitle, required this.url, }); } class DataView { final DataItem item = const DataItem( title: 'Hello', subtitle: 'subtitle', url: 'https://oreil.ly/O4PEn'); } class MyContainerWidget extends StatelessWidget { MyContainerWidget({Key? key}) : super(key: key); final DataView data = DataView(); @override Widget build(BuildContext context) { return Container( width: 200, height: 180, color: Colors.black, child: Column( children: [ Image.network(data.item.url), Text( data.item.title, style: const TextStyle(fontSize: 20, color: Colors.white), ), Text( data.item.subtitle, style: const TextStyle(fontSize: 16, color: Colors.grey), ), ], ), ); } }

Обсуждение

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

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

Нашим первым шагом в рефакторинге является внедрение нового виджета без сохранения состояния. Виджет будет отвечать за раздел тела Scaffold. Добавление нового виджета без сохранения состояния обеспечивает разделение кода, а также предоставляет новый изолированный контекст сборки. В нашем исходном дереве виджетов теперь есть контекст сборки для каркаса и нового виджета. Такое разделение контекста сборки улучшит производительность рендеринга Flutter.

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

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

Использование Scaffold класса

Проблема

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

Решение

Используйте класс Scaffold, чтобы предоставить высокоуровневую структуру для вашего приложения. Scaffold реализует базовую структуру визуального макета Material Design. Интегрируя виджет Scaffold, вы получаете доступ к готовому богатому API для отображения boxes, панелей приложений, snack bars и нижней навигации.

Вот пример виджета Scaffold, который используется для отображения простого интерфейса для пользователя:

import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); static const String _title = 'Example'; @override Widget build(BuildContext context) { return const MaterialApp( title: _title, home: MyStatelessWidget(), ); } } class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Scaffold Example')), backgroundColor: Colors.blueGrey, bottomNavigationBar: const BottomAppBar( color: Colors.blueAccent, shape: CircularNotchedRectangle(), child: SizedBox( height: 300, child: Center(child: Text("bottomNavigationBar")), ), ), body: _buildCardWidget(), ); } Widget _buildCardWidget() { return const SizedBox( height: 200, child: Card( child: Center( child: Text('Top Level Card'), ), ), ); } }

Обсуждение

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

На рисунке 9-4 показаны основные элементы виджета Scaffold. Если вы реализуете элементы навигации AppBar, Body и Bottom, они будут корректно отображаться на экране без дополнительных усилий. Scaffold расширится, чтобы использовать доступное пространство на экране, то есть заполнит весь экран.

Рисунок 9-4. Виджет Scaffold
Рисунок 9-4. Виджет Scaffold

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

В общем, избегайте вложенности класса Scaffold, поскольку он разработан как контейнер верхнего уровня для MaterialApp. Scaffold предоставляет возможность расширять ваше приложение с помощью панели приложений AppBar и виджетов Drawer. Если вы хотите использовать Scaffold с кнопкой FloatingActionButton, убедитесь, что вы используете stateful виджет с отслеживанием состояния, чтобы сохранить состояние соответствующей кнопки.

Добавление заголовка AppBar

Проблема

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

Решение

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

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

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { final String titleAppBar = 'AppBar Demo'; const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: titleAppBar, home: Scaffold( appBar: MyAppBar(title:titleAppBar), ), ); } } class MyAppBar extends StatelessWidget implements PreferredSizeWidget { final String title; final double sizeAppBar = 200.0; const MyAppBar({Key? key, required this.title}) : super(key: key); @override Size get preferredSize => Size.fromHeight(sizeAppBar); @override build(BuildContext context) { return AppBar( title: Text(title), backgroundColor: Colors.black, elevation: 10.0, leading: IconButton(onPressed: (){}, icon: const Icon(Icons.menu)), actions: [IconButton(onPressed: (){}, icon: const Icon(Icons.settings)) ], ); } }

Обсуждение

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

На рисунке 9-5 элементы виджета AppBar обозначены стрелками. Свойство AppBar leading предоставляет пункт меню слева. Заголовок размещен в центре, а пункт меню actions - справа.

Рисунок 9-5. Пример виджета AppBar
Рисунок 9-5. Пример виджета AppBar

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

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

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

Построение при помощи Container

Проблема

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

Решение

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

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

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const title = 'Container Widget Demo'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: const Text(title), ), body: const MyContainerWidget(), ), ); } } class MyContainerWidget extends StatelessWidget { const MyContainerWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, height: 200, width: 200, color: Colors.red[300], child: Container( height: 100, width: 100, color: Colors.yellow, ), ); } }

Обсуждение

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

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

Рисунок 9-6. Пример виджета контейнера
Рисунок 9-6. Пример виджета контейнера

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

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

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

В этот момент вы, вероятно, думаете: "Ну, а что произойдет, если я не укажу размеры высоты и ширины для родительского виджета? Виджет-контейнер знает о своем окружении и будет принимать свои родительские размеры из существующего окна просмотра, то есть тела приложения. Таким образом, в этом сценарии вы увидите красный контейнер, занимающий большую часть экрана.

Контейнеры очень полезны; однако они полезны не в каждой ситуации. Если вы хотите предоставить пробелы в приложении, рекомендуемый подход - использовать SizedBox, а не виджет контейнера.

Продолжение следует...

Начать дискуссию