StatefulWidget и состояние State

Последнее обновление: 05.02.2021

Класс StatefulWidget предназначен для создания виджетов, которые хранят состояние. При этом несмотря на то, что объекты класса StatefulWidget являются неизменяемыми (immutable), их состояние является изменяемым (mutable).

Что представляет собой состояние? Состояние - это некоторая информация, которая может быть считана синхронно при создании виджета и которая в процессе жизненного цикла виджета может измениться. Состояние может храниться в виде отдельных объектов State, либо в других объектах, на которые объект State подписывается.

Объекты State создаются с помощью метода createState, который вызывается фреймворком и который связывает их с виджетом StatefulWidget.

Например, определим простейший виджет StatefulWidget:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home:  Scaffold(
        body: Counter(),
        appBar: AppBar(title: Text("METANIT.COM")),)
  ));
}

class Counter extends StatefulWidget{
  Counter({ super.key});
  @override
  _CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter>{

  int value = 0;

  @override
  Widget build(BuildContext context) {

    return Text(
      "Value: $value", style: TextStyle(fontSize: 22),
    );
  }
}

Итак,здесь определен класс Counter, который наследуется от StatefulWidget.

Его конструктор содержит один параметр - key, хотя при необходимости мы можем определять какие-то другие параметры, исходя из наших потребностей.

Также класс виджета должен переопределить метод createState(), который должен возвращать состояние для этого виджета

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

В данном случае это объект класса _CounterState. (Классы или члены классов, которые предваряются знаком прочерка (_), являются приватными по отношению к классам из других библиотек.)

Сам класс состояния унаследован от класса State, который типизирован классом класса виджета.

class _CounterState extends State<Counter>{

В самом классе определена переменная value, которая условно представляет данные, которые хранит класс состояния.

int value = 0;

Также нам надо переопределить метод build(), который возвращает виджет:

Widget build(BuildContext context) {

return Text(
  "Value: $value", style: TextStyle(fontSize: 22),
);
}

Здесь мы просто возвращаем виджет Text, который выводит значение переменной value. Но естественно возвращаемая иерархия виджетов может быть более сложной.

Затем виджет Counter встравивается в приложение в элемент Scaffold:

void main() {
  runApp(MaterialApp(
      home:  Scaffold(
        body: Counter(),

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

StatefulWidget в Flutter

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

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home:  Scaffold(
        body: Counter(),
        appBar: AppBar(title: Text("METANIT.COM")),)
  ));
}
class Counter extends StatefulWidget{
  Counter({ super.key});
  @override
  _CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter>{

  int value = 0;

  @override
  Widget build(BuildContext context) {

    return ElevatedButton(
        child: Text("Value: $value", style: TextStyle(fontSize: 22)),
        onPressed:(){ setState(() {
          value++;
        });}
    );
  }
}

Теперь для простоты значение переменной value выводится в качестве текста на кнопку. При нажатии на кнопку будет вызываться функция, которая увеличит значение value на единицу. Чтобы указать, что состояние изменилось, виджет вызывает метод State.setState(). В этот метод передается функция, которая не принимает параметров и ничего не возвращает. И именно в ней мы можем изменить значение value.

В итоге по нажатию на кнопку увеличится значение переменной value:

StatefulWidget и изменение состояния State с помощью метода State.setState в Flutter

Передача данных в State

При необходимости в объект State можно передавать данные извне. В этом случае передача осуществляется через StatefulWidget. Например, изменим выше приведенный пример таким образом, чтобы State принимал начальные данные извне:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home:  Scaffold(
        body: Column(children:[
          Counter(value: 4, increment: 2),
          Counter(value:-1, increment: 1)
        ]),
        appBar: AppBar(title: Text("METANIT.COM")),)
  ));
}
class Counter extends StatefulWidget{

  int value = 0;
  int increment = 1;

  Counter({ super.key, required this.value, required this.increment});
  @override
  _CounterState createState() => _CounterState(this.value, this.increment);
}
class _CounterState extends State<Counter>{

  int value = 0;
  int increment = 1;

  _CounterState(this.value, this.increment);

  @override
  Widget build(BuildContext context) {

    return ElevatedButton(
        child: Text("Value: $value", style: TextStyle(fontSize: 22)),
        onPressed:(){ setState(() {
          value = value + increment;
        });}
    );
  }
}

В данном случае в State добавлена новая переменная increment, на которую увеличивается значение переменной value. Для получения извне значений для этих переменных определен конструктор _CounterState(this.value, this.increment)

При создании объекта State виджет Counter передает в конструктор соответствующие данные:

_CounterState createState() => _CounterState(this.value, this.increment);

При этом эти значения виджет Counter сам принимает извне с помощью своего конструктора:

Counter({ super.key, required this.value, required this.increment});

В итоге при применении виджета мы можем передавать в него различные данные:

Column(children:[
  Counter(value: 4, increment: 2),
  Counter(value:-1, increment: 1)
])
Передача данных в StatefulWidget и State и метод State.setState в Flutter

Получение виджета

С помощью свойства widget внутри State можно обращаться к виджету StatefulWidget, к которому привязан объект State:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home:  Scaffold(
        body: Column(children:[
          Counter(increment: 2),
          Counter(increment: 1),
        ]),
        appBar: AppBar(title: Text("METANIT.COM")),)
  ));
}
class Counter extends StatefulWidget{

  int increment = 1;

   Counter({ super.key, required this.increment});
  @override
  _CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter>{

  int value = 0;

  @override
  Widget build(BuildContext context) {

    return ElevatedButton(
        child: Text("Value: $value", style: TextStyle(fontSize: 22)),
        onPressed:(){ setState(() {
          value = value + widget.increment;
        });}
    );
  }
}

В данном случае переменная increment определена только внутри класса Counter, и чтобы к ней обратиться, применяется выражение widget.increment, где widget - это виджет Counter. Подобным образом можно обращаться к другим полям и методам из виджета при их наличии.

Вынос логики изменения состояния в метод

В примерах выше вся логика изменения состояния сосредоточена в функции из onPressed. Но все эти действия также можно вынести в отдельный метод:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home:  Scaffold(
        body: Column(children:[
          Counter(increment: 2),
          Counter(increment: 1),
        ]),
        appBar: AppBar(title: Text("METANIT.COM")),)
  ));
}
class Counter extends StatefulWidget{

  int increment = 1;

   Counter({ super.key, required this.increment});
  @override
  _CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter>{

  int value = 0;

  increaseValue(){
    setState(() {
      value = value + widget.increment;
    });
  }
  @override
  Widget build(BuildContext context) {

    return ElevatedButton(
        child: Text("Value: $value", style: TextStyle(fontSize: 22)),
        onPressed:(){ increaseValue();}
    );
  }
}

Если метод, в который вынесены все действия, ничего не возвращает и не принимает никаких параметров, то есть соответствует определению onPressed (как в данном случае), то можно напрямую присвоить функцию параметру onPressed:

ElevatedButton(
        child: Text("Value: $value", style: TextStyle(fontSize: 22)),
        onPressed: increaseValue
);
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850