Kodokon kodokon.com

Интерактивность: кнопки, StatefulWidget и setState

Оживи свои экраны, реагируя на нажатия кнопок и обновляя изображение через setState.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Наши экраны красивые, но застывшие: пора реагировать на действия пользователя. Стандартная кнопка Material 3 - это ElevatedButton. Она принимает два ключевых параметра: child, её отображаемое содержимое (часто Text), и onPressed, функцию, которая выполняется при нажатии. Функция, переданная вот так в качестве параметра, называется колбэком: ты не вызываешь её сам, Flutter вызывает её за тебя в нужный момент. Синтаксис () { ... } создаёт анонимную функцию, без имени, написанную прямо на месте.

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

Widget buildHelloButton() {
  return ElevatedButton(
    onPressed: () {
      debugPrint("Button pressed!");
    },
    child: const Text("Tap here"),
  );
}
Кнопка, которая пишет в консоль при каждом нажатии

debugPrint пишет в консоль... но сам экран не меняется. Почему? Потому что StatelessWidget (виджет без состояния) застывший: однажды построенный, он больше не может измениться. Чтобы показывать меняющееся значение, нужен StatefulWidget (виджет с состоянием). Состояние - это набор данных, которые могут меняться в течение жизни виджета: счётчик, отмеченный флажок, введённый текст... StatefulWidget всегда идёт в паре с классом State, который хранит эти данные и содержит метод build. Вот великая классика первых шагов: счётчик.

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

void main() {
  runApp(const CounterApp());
}

class CounterApp extends StatelessWidget {
  const CounterApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const CounterPage(),
    );
  }
}

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() =>
      _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "Counter: $count",
              style: const TextStyle(fontSize: 32),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  count = count + 1;
                });
              },
              child: const Text("+1"),
            ),
          ],
        ),
      ),
    );
  }
}
Счётчик: полноценный StatefulWidget

Механизм состоит из трёх шагов. 1) Переменная count живёт в _CounterPageState: это и есть состояние. 2) При каждом нажатии колбэк вызывает setState: этот метод меняет состояние и сообщает Flutter, что что-то изменилось. 3) Тогда Flutter заново выполняет build, который перерисовывает экран с новым значением count. Запомни философию: ты никогда не меняешь экран напрямую, ты меняешь данные, а Flutter берёт на себя отображение.

Проверка знаний

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

  1. Для чего нужен параметр onPressed у ElevatedButton?
    • Чтобы задать текст, показанный на кнопке
    • Чтобы передать функцию, которая выполняется при нажатии на кнопку
    • Чтобы выбрать цвет кнопки
  2. Когда стоит использовать StatefulWidget вместо StatelessWidget?
    • Когда отображение зависит от данных, которые меняются со временем
    • Когда виджет содержит больше трёх детей
    • Когда хочется, чтобы приложение запускалось быстрее
  3. Что произойдёт, если изменить count, не вызвав setState?
    • Приложение сразу упадёт
    • Переменная изменится в памяти, но экран не обновится
    • Flutter автоматически вызовет setState за тебя