Flutter — готовые виджеты
Приветствую! Здесь вы наверняка найдете, что ищете. Примеры в лаборатории рассчитаны на то, что мы разбираем что-то конкретное.
Текущая статья посвящена примерам: Flutter на Dart с построчным разбором.
Поэтому за теорией по текущей теме вам — в энциклопедию. Если ещё не погружались, то маршрут прост:
- Основы
- Система и сеть
- Данные и разметка
- Код и разработка
- Языки
- Искусственный интеллект
- Проект
- Инфраструктура и безопасность
- Спин-офф
Обязательно пройдитесь.
А теперь приступим к нашему предмету.
Основы UI во Flutter
Flutter — фреймворк Google для приложений на Android, iOS, Windows, macOS, Linux и веб. Интерфейс собирают из виджетов — классов Dart, которые описывают, что рисовать на экране. Язык — Dart; без базового синтаксиса Dart начните с первой программы.
Теория фреймворка — Flutter.
Мобильный контекст — мобильные приложения.
Десктоп на Python — Tkinter, на Java — Swing, в браузере — React и Vue / Svelte.
HTTP — Fetch / axios.
Навигация по примерам
| Ищут в интернете | Раздел ниже |
|---|---|
| flutter hello world / первое приложение | Hello Flutter |
| flutter counter example setstate | Счётчик |
| flutter elevatedbutton onclick | Кнопка и SnackBar |
| flutter textfield controller example | Поле ввода и приветствие |
| flutter temperature converter | Конвертер °C → °F |
| flutter checkbox switch radiolisttile | Переключатели |
| flutter todo list listview builder | Список задач |
| flutter slider example | Ползунок |
| flutter login form textformfield | Форма входа |
| flutter drawer menu example | Боковое меню |
| flutter navigator push pop | Второй экран |
| flutter tabbar tabbarview | Вкладки |
| flutter alertdialog showdialog | Диалог |
| flutter http get futurebuilder | Загрузка с API |
| flutter create project run | Обязательный каркас |
| renderflex overflowed / setstate after dispose | Частые ошибки |
Как запустить пример за 2 минуты
- Установите Flutter SDK. В терминале:
flutter doctor— исправьте красные пункты (Android Studio / Xcode для мобильных платформ). flutter create my_app && cd my_app- Откройте
lib/main.dart, удалите шаблонный код, вставьте пример целиком (отimportдо последней}). - Запустите эмулятор или подключите телефон с USB-отладкой.
flutter run— через минуту приложение на устройстве.- Меняете код и жмёте Hot Reload (
rв терминале) — экран обновится без полного перезапуска.
| Где | Плюсы |
|---|---|
| Эмулятор Android / симулятор iOS | Как на реальном телефоне |
flutter run -d chrome |
Быстрая проверка без эмулятора (веб) |
| VS Code / Android Studio | Кнопка Run, подсветка ошибок |
Flutter не запускается — нужен компьютер с SDK.
Базовые термины
| Термин | Простыми словами |
|---|---|
| Виджет | Любой элемент UI — кнопка, текст, отступ, весь экран |
| StatelessWidget | Экран или блок без своей памяти; картинка не меняется сама |
| StatefulWidget | Блок с state — число счётчика, текст в поле, список задач |
| setState() | «Данные изменились — перерисуй экран» |
| build() | Метод, который описывает, как выглядит UI прямо сейчас |
| Scaffold | Каркас экрана: AppBar, body, кнопка FAB, drawer |
| MaterialApp | Корень приложения, тема, заголовок |
| Hot Reload | Правка кода → экран обновился за секунду, state часто сохраняется |
| pubspec.yaml | Файл зависимостей (как package.json у Node) |
Flutter, Tkinter и React — одна идея, разный синтаксис
| Задача | Tkinter (Python) | React (браузер) | Flutter (Dart) |
|---|---|---|---|
| Окно / корень | tk.Tk() |
<div id="root"> |
runApp(MaterialApp(..)) |
| Надпись | Label(text=..) |
<p>..</p> |
Text('..') |
| Кнопка | Button(command=fn) |
<button onClick={fn}> |
ElevatedButton(onPressed: fn) |
| Поле ввода | Entry + .get() |
value + onChange |
TextField + controller.text |
| Динамический текст | StringVar |
useState |
setState + поле в State |
| Цикл событий | mainloop() |
React перерисовывает DOM | Flutter пересобирает дерево виджетов |
Смысл: вы описываете интерфейс как функцию от данных. Данные изменились → фреймворк сам обновляет экран. В Tkinter часть этого делаете вручную через StringVar; во Flutter — через setState.
Словарь виджетов за 30 секунд
| Виджет | Зачем | Как читать / менять |
|---|---|---|
MaterialApp |
Корень, тема | home:, theme: |
Scaffold |
Каркас экрана | appBar, body, drawer, floatingActionButton |
AppBar |
Верхняя панель | title: Text(..) |
Text |
Текст | Text('строка') |
Center |
Центрировать child | child: .. |
Column / Row |
Столбец / строка | children: [..] |
Padding |
Отступы | padding: EdgeInsets.all(16) |
ElevatedButton |
Кнопка | onPressed: () { } |
TextField |
Ввод одной строки | controller.text |
TextFormField |
Поле + валидация | validator: |
ListView.builder |
Длинный список | itemCount, itemBuilder |
Navigator |
Экраны | push, pop |
SnackBar |
Строка снизу | через ScaffoldMessenger |
Компоновка: CSS во Flutter нет. Отступы — Padding, SizedBox; ширина на всю строку — crossAxisAlignment: CrossAxisAlignment.stretch в Column.
Как работает Flutter — цикл обновления
flowchart LR
A[Тап / ввод текста] --> B[setState или смена данных]
B --> C[build вызывается снова]
C --> D[Flutter сравнивает дерево виджетов]
D --> E[Перерисовка только изменившихся участков]
Пользователь нажал «+» → _count стал 5 → Flutter снова вызвал build → на экране обновилась только цифра в Text, кнопка не «мигала» целиком.
Обязательный каркас
Любой пример ниже — полный файл lib/main.dart. Запомните команды, как import tkinter и mainloop() в Tkinter.
Задача: создать проект и убедиться, что dev-сборка работает.
flutter create my_flutter_app
cd my_flutter_app
flutter run
Минимальный lib/main.dart — замените home: на код из примеров ниже:
Разбор по строкам.
| Строка | Смысл |
|---|---|
import 'package:flutter/material.dart' |
Material-виджеты: кнопки, поля, тема |
void main() |
Точка входа — первая функция, которую вызывает Dart |
runApp(..) |
«Включить» Flutter; без неё пустой экран |
StatelessWidget |
Виджет без своего изменяемого state |
const MyApp({super.key}) |
const + super.key — идиома Flutter 3 для оптимизации |
MaterialApp |
Обёртка: тема, локализация, навигация верхнего уровня |
debugShowCheckedModeBanner: false |
Убирает ленточку «DEBUG» в углу (удобно для скриншотов в отчёт) |
ThemeData(..) |
Цвета кнопок, шрифты — единый стиль |
home: |
Стартовый экран — ваш Scaffold или кастомный виджет |
build(BuildContext context) |
Flutter вызывает его, когда нужно нарисовать UI |
Типичные ошибки.
flutter: command not found— Flutter не вPATH; добавьтеflutter/binиз установки.- «No devices found» — запустите эмулятор в Android Studio или
flutter run -d chrome. - Красный экран RenderFlex overflowed — содержимое
Columnне помещается; см. ошибки. - Правите код, а экран не меняется — сохраните файл; при изменении
main()нужен Hot Restart (R), не Reload.
Попробуйте: замените Placeholder() на Scaffold(body: Center(child: Text('Hello'))).
Стартовые экраны
Простые целые main.dart — с них.
Hello Flutter
Задача. Минимальный экран: заголовок в AppBar и текст по центру — проверка, что SDK и эмулятор работают.
Разбор.
| Виджет / строка | Зачем |
|---|---|
HelloScreen extends StatelessWidget |
Отдельный экран без изменяемых данных |
Scaffold |
«Скелет» Material-экрана |
AppBar |
Системная верхняя полоска с заголовком |
body: |
Основная область под AppBar |
Center |
Размещает child по центру по горизонтали и вертикали |
Text(.., style: TextStyle(fontSize: 18)) |
Надпись и размер шрифта |
const перед виджетами |
Подсказка компилятору: параметры не меняются → меньше лишней работы |
Попробуйте сами. Добавьте backgroundColor: Colors.teal.shade50 в Scaffold. Поменяйте title в AppBar — изменится текст в верхней панели.
Счётчик
Задача. Число в памяти увеличивается по нажатию — классический flutter counter example (как шаблон flutter create).
Разбор — почему два класса CounterScreen и _CounterScreenState.
| Элемент | Смысл |
|---|---|
StatefulWidget |
«Оболочка» — сама не хранит _count, только создаёт State |
createState() |
Flutter вызывает один раз и получает объект _CounterScreenState |
_CounterScreenState |
Здесь живёт _count; подчёркивание = приватный класс в Dart |
int _count = 0 |
Начальное значение счётчика |
setState(() { _count++; }) |
Обязательно при изменении данных UI; без этого цифра на экране не обновится |
'Значение: $_count' |
Интерполяция строк — $ вставляет значение переменной |
Theme.of(context).textTheme.headlineMedium |
Стиль из темы приложения — единообразные заголовки |
FloatingActionButton |
Круглая кнопка «+» в углу — паттерн Material Design |
onPressed: _increment |
Передаём имя функции, не _increment() — иначе вызовется сразу при сборке |
Сравнение с React (см. React — счётчик):
| React | Flutter |
|---|---|
const [count, setCount] = useState(0) |
int _count = 0 в State |
setCount(count + 1) |
setState(() { _count++; }) |
{count} в JSX |
'$_count' в Text |
Типичные ошибки.
- Пишут
_count++безsetState— переменная меняется, экран стоит на месте. onPressed: _increment()— функция вызывается при каждой пересборке, не по клику.
Попробуйте сами. Кнопка «−» в body: второй ElevatedButton с setState(() { _count--; }).
Кнопка и SnackBar
Задача. По нажатию показать короткое сообщение снизу — аналог messagebox.showinfo в Tkinter.
Разбор.
| Строка | Смысл |
|---|---|
ElevatedButton |
Основная «объёмная» кнопка Material |
onPressed: () => _showMessage(context) |
Лямбда передаёт context в метод — он нужен для поиска Scaffold |
ScaffoldMessenger.of(context) |
Сервис, который показывает SnackBar поверх текущего Scaffold |
SnackBar(content: Text(..)) |
Чёрная/тёмная полоска внизу экрана |
duration: Duration(seconds: 2) |
Сколько секунд висит сообщение |
Попробуйте сами. Замените на SnackBar(action: SnackBarAction(label: 'OK', onPressed: () {})) — кнопка на полоске.
Для модального окна «OK / Отмена» см. диалог.
Поле ввода и приветствие
Задача. Прочитать имя из поля и показать приветствие — типичная форма на лабораторной.
Разбор.
| Элемент | Смысл |
|---|---|
TextEditingController |
«Мост» к тексту внутри TextField; читают через .text |
final _controller = .. |
Создаётся один раз в State, не в build |
dispose() + _controller.dispose() |
Освобождает ресурсы при закрытии экрана — обязательная привычка |
trim() |
Убирает пробелы по краям — пустое « » не считается именем |
InputDecoration |
Рамка, подпись labelText, подсказка hintText |
OutlineInputBorder() |
Прямоугольная рамка вокруг поля |
onSubmitted: (_) => _greet() |
Клавиша «Готово» / Enter на клавиатуре = та же логика, что у кнопки |
Column + crossAxisAlignment: stretch |
Кнопка на всю ширину экрана |
SizedBox(height: 12) |
Вертикальный зазор 12 логических пикселей |
Типичные ошибки.
- Создают
TextEditingController()внутриbuild— при каждой перерисовке теряется текст и течёт память. - Забывают
dispose()— предупреждения в консоли при Hot Reload.
Попробуйте сами. Второе поле «Фамилия» и вывод Здравствуй, $name $surname!.
Конвертер °C → °F
Задача. Классическая учебная программа: число → формула → результат на экране (часто встречается в заданиях вместе с Tkinter-конвертером).
Разбор формулы и проверок.
| Строка | Смысл |
|---|---|
replaceAll(',', '.') |
«25,5» → «25.5» для double.tryParse |
double.tryParse(raw) |
Вернёт null, если введены буквы — без try/catch |
celsius * 9 / 5 + 32 |
Формула Фаренгейта: $F = C \times \frac{9}{5} + 32$ |
toStringAsFixed(1) |
Один знак после запятой в выводе |
keyboardType: .. decimal: true |
На телефоне — цифровая клавиатура с точкой |
OutlinedButton |
Вторичная кнопка «Очистить» — визуально легче основной |
Попробуйте сами. Обратный перевод °F → °C: $C = (F - 32) \times \frac{5}{9}$.
Флажок, переключатель и радио
Задача. Несколько настроек «вкл/выкл» и выбор одной роли — как Checkbutton и Radiobutton в Tkinter.
Разбор.
| Виджет | Когда использовать |
|---|---|
SwitchListTile |
Одна строка «название + переключатель» |
RadioListTile<String> |
Один вариант из группы; groupValue общий для всех |
value / onChanged у Switch |
onChanged: null — переключатель серый и неактивен |
ListView |
Прокрутка, если пунктов больше, чем высота экрана |
Попробуйте сами. Третья роль «Гость» с value: 'guest'.
Список задач
Задача. Добавлять строки и удалять их — flutter todo list, частый мини-проект для отчёта.
Разбор ListView.
| Элемент | Смысл |
|---|---|
List<String> _items |
Данные списка в state |
_items.add(text) |
Добавление в конец |
_items.removeAt(index) |
Удаление по индексу |
ListView.builder |
Строит только видимые строки — важно для длинных списков |
itemCount: _items.length |
Сколько элементов рисовать |
itemBuilder: (context, index) |
Виджет для строки index |
Expanded вокруг списка |
Список занимает всё место под полем ввода |
Условие _items.isEmpty ? .. : .. |
Пустое состояние — подсказка пользователю |
Типичные ошибки.
ListViewбезExpandedвнутриColumn— ошибка unbounded height.- Меняют
_items, но безsetState— UI не обновляется.
Попробуйте сами. Checkbox в ListTile для отметки «выполнено» (второй список или List<Map>).
Ползунок
Задача. Выбрать число в диапазоне — аналог Scale в Tkinter.
Разбор.
| Параметр | Смысл |
|---|---|
min / max |
Диапазон значений |
value: _volume |
Текущая позиция ползунка — должна быть между min и max |
onChanged |
Вызывается при перетаскивании; обязан обновить state |
divisions: 20 |
20 дискретных шагов (0, 5, 10, … 100) |
label |
Всплывающая цифра над ползунком при движении |
Форма входа
Задача. Логин и пароль с проверкой — типичная лабораторная «форма авторизации».
Разбор валидации.
| Элемент | Смысл |
|---|---|
Form + GlobalKey<FormState> |
Общая форма; ключ нужен, чтобы вызвать .validate() |
TextFormField |
Как TextField, но с validator |
validator: (v) => .. |
Верните null — поле OK; строку — текст ошибки под полем |
_formKey.currentState!.validate() |
Проверяет все поля; false, если хоть одно неверно |
obscureText: true |
Символы пароля скрыты точками |
FilledButton |
Акцентная кнопка Material 3 |
Боковое меню
Задача. Пункты «Главная», «Настройки» в выезжающей панели — иконка ☰ появляется в AppBar автоматически.
Разбор.
drawer:уScaffold— Flutter сам рисует кнопку-«гамбургер».Navigator.pop(context)закрывает drawer после выбора пункта.DrawerHeader— цветная шапка боковой панели.
Второй экран
Задача. Перейти на новый экран и вернуться — flutter navigator push.
Разбор навигации.
| Вызов | Что происходит |
|---|---|
Navigator.push(..) |
Новый экран поверх текущего; стек растёт |
MaterialPageRoute(builder: ..) |
Анимация «слайд справа» в стиле Android |
Navigator.pop(context) |
Снять верхний экран; вернуться назад |
| Системная кнопка «Назад» на Android | То же, что pop |
Смысл context: по нему Flutter находит ближайший Navigator в дереве виджетов. Поэтому context передают в методы навигации.
Вкладки
Задача. Два экрана в одном — переключение по табам или свайпу.
Разбор.
DefaultTabController(length: 2)— число вкладок должно совпадать с длинойTabBarиTabBarView.- Свайп влево/вправо на телефоне переключает вкладки без нажатия.
Диалог подтверждения
Задача. Спросить «Удалить?» перед действием — как askyesno в Tkinter.
Вызовите confirmDelete(context) из onPressed кнопки «Удалить» на любом экране.
Разбор async.
| Строка | Смысл |
|---|---|
async / await |
Ждём, пока пользователь нажмёт кнопку в диалоге |
showDialog<bool> |
Возвращает Future<bool?> — результат закрытия |
Navigator.pop(ctx, true) |
Закрыть диалог и вернуть true вызывающему коду |
context.mounted |
После await экран мог закрыться — проверка перед SnackBar |
Загрузка списка с API
Задача. GET-запрос и список имён — параллель Fetch / axios на Dart.
Шаг 1. В pubspec.yaml в секции dependencies::
dependencies:
flutter:
sdk: flutter
http: ^1.2.0
Терминал: flutter pub get.
Шаг 2. Полный lib/main.dart:
Разбор FutureBuilder.
Состояние snapshot |
Что показать |
|---|---|
ConnectionState.waiting |
Крутилка загрузки |
hasError |
Текст ошибки (нет сети, 404…) |
hasData |
ListView с результатом |
initState + _future = .. |
Запрос один раз при открытии экрана, не при каждом build |
Без интернета замените _loadUsers() на:
Future<List<String>> _loadUsers() async {
await Future.delayed(const Duration(seconds: 1));
return ['Алиса', 'Борис', 'Вика'];
}
Частые ошибки
| Симптом | Причина | Что сделать |
|---|---|---|
setState() called after dispose() |
После await обновили UI закрытого экрана |
if (!mounted) return; перед setState |
RenderFlex overflowed by … pixels |
Column/Row не влезает по высоте |
SingleChildScrollView или Expanded / Flexible |
Vertical viewport was given unbounded height |
ListView в Column без Expanded |
Оберните список в Expanded |
No Material widget found |
TextField вне MaterialApp |
Корень — MaterialApp → Scaffold |
| Цифра счётчика не меняется | Забыли setState |
Оберните изменение поля в setState(() { .. }) |
| Кнопка серая | onPressed: null |
Передайте функцию или уберите null |
| Hot Reload не помог | Меняли main() или static |
Hot Restart (R) |
MissingPluginException |
Плагин не подтянулся | flutter pub get, flutter clean, пересборка |
RenderFlex overflow — типичный случай
Плохо — длинный Column без прокрутки:
body: Column(
children: [
/* много виджетов — на маленьком экране красная ошибка */
],
),
Хорошо:
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [ /* те же виджеты — прокручиваются */ ],
),
),
Частые вопросы
Чем Flutter отличается от React?
React — UI в браузере через DOM; Flutter — свой движок рисования, одна кодовая база на телефон и десктоп. Идея state похожа: см. React — компоненты-рецепты.
Нужен ли Mac для Android?
Нет. Android-сборка работает на Windows/Linux/macOS. iOS-сборка — только на macOS с Xcode.
Можно ли без эмулятора?
flutter run -d chrome — быстрая проверка вёрстки. Для отчёта по «мобильной» лабораторной лучше скриншот с эмулятора.
Как сдать работу учителю?
ZIP проекта (без build/), скриншоты экранов, краткое описание виджетов в отчёте: «счётчик на StatefulWidget, список на ListView.builder».
Где полная теория?
Flutter, Dart — раздел.
Что изучить дальше
| Тема | Куда перейти |
|---|---|
| Архитектура, pub.dev, сборка APK | Flutter — энциклопедия |
| Async, Future, Stream | Async в Dart |
| Null safety, классы | ООП в Dart |
| Публикация в store | Мобильные приложения |
| Веб-аналог UI | React — рецепты |
| Десктоп на Python | Tkinter — виджеты |
В описании укажите три виджета, которые использовали: «`Scaffold` — каркас экрана, `TextField` + `TextEditingController` — ввод имени, `setState` — обновление надписи».
Одно точное имя API показывает, что вы понимаете код, а не только скопировали шаблон — как с тегами в HTML-страницах целиком.