Урок 2. StatelessWidget и StatefulWidget

  1. StatelessWidget
  2. StatefulWidget
  3. Итоги

В предыдущем уроке мы рассмотрели пример построения интерфейса приложения с помощью виджетов, создали свой виджет MyBody наследуемый от класса StatelessWidget.

Все собственные классы-виджеты рекомендуется создавать от двух основных: StatelessWidget и StatefulWidget.

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

1. StatelessWidget

StatelessWidget – рекомендуется для неизменяемых виджетов. Это такие виджеты которые не имеют внутреннего состояния, зависят только от конфигурационных параметров и от родительских виджетов.

Вот некоторые виджеты которые наследуются от StatelessWidget:

Самая простая конструкция для создания виджета от суперкласса StatelessWidget:

class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new Text('my text'); } }

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

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

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

Создадим класс NewsBox от суперкласса StatelessWidget и определим в нем поля:
_title – заголовок анонса новости,
_text – содержание новостного сообщения,
_imageurl – ссылка на изображение.

class NewsBox extends StatelessWidget { final String _title; final String _text; String _imageurl;

Разберем строку final String _title;

title – поле нашего класса, переменная в которой мы будем хранить заголовок новостного сообщения

_ (подчеркивание перед именем переменной) – в языке Dart говорит о том что данная переменная скрыта и доступна только внутри объекта нашего класса (аналог private в других языках программирования)

String – наша переменная строкового типа

final – данная переменная должна быть объявлена при создании экземпляра класса и не может быть изменена

Можно было бы просто объявить поле в классе с помощью var title; в перспективе это плохой вариант:

Добавим конструктор в наш класс:

NewsBox(this._title, this._text, {String imageurl}) { _imageurl = imageurl; }

NewsBox – название конструктора должно совпадать с именем нашего класса.

В круглых скобках () мы указываем, что при создании экземпляра ожидается два обязательных параметра значения которых будет присвоено скрытым полям _title и _text нашего класса.

В фигурных скобках {} указывается, что в конструкторе можно указать параметр imageurl со значением типа String.

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

  1. new NewsBox('заголовок', 'содержание новости', imageurl: 'http://url/image.jpg') // с параметром imageurl
  2. new NewsBox('заголовок', 'содержание новости') // без imageurl

А вот так вызывать наш конструктор нельзя:

  1. new NewsBox() // без параметров
  2. new NewsBox('заголовок') // только с одним параметром
  3. new NewsBox('заголовок', imageurl: 'http://url/image.jpg', 'содержание новости') // менять местами обязательные и не обязательные параметры по ключу

Приведем краткую структуру, которую мы хотим вывести:

  1. Если изображение задано через параметр imageurl то в функции build мы выведим такую структуру:
    if (_imageurl != null) return new Container( child: new Row( children: [ new Image.network(_imageurl), new Column(children: [new Text(_title),new Text(_text)]) ]) )
  2. Если изображения нет, то выведем без него:
    return new Container( child: new Row( children: [ new Column(children: [new Text(_title),new Text(_text)]) ]) )

Листинг программы

import 'package:flutter/material.dart'; class NewsBox extends StatelessWidget { final String _title; final String _text; String _imageurl; NewsBox(this._title, this._text, {String imageurl}) { _imageurl = imageurl; } @override Widget build(BuildContext context) { if (_imageurl != null && _imageurl != '') return new Container( color: Colors.black12, height: 100.0, child: new Row(children: [ new Image.network(_imageurl, width: 100.0, height: 100.0, fit: BoxFit.cover,), new Expanded(child: new Container(padding: new EdgeInsets.all(5.0), child: new Column(children: [ new Text(_title, style: new TextStyle(fontSize: 20.0), overflow: TextOverflow.ellipsis), new Expanded(child:new Text(_text, softWrap: true, textAlign: TextAlign.justify,)) ] )) ) ]) ); return new Container( color: Colors.black12, height: 100.0, child: new Row(children: [ new Expanded(child: new Container(padding: new EdgeInsets.all(5.0), child: new Column(children: [ new Text(_title, style: new TextStyle(fontSize: 20.0), overflow: TextOverflow.ellipsis), new Expanded(child:new Text(_text, softWrap: true, textAlign: TextAlign.justify,)) ] )) ) ]) ); } } void main() => runApp( new MaterialApp( debugShowCheckedModeBanner: false, home: new Scaffold( appBar: new AppBar(), body: new NewsBox('Новый урок по Flutter', '''В новом уроке рассказывается в каких случаях применять класс StatelessWidget и StatefulWidget. Приведены примеры их использования.''', imageurl: 'https://flutter.su/favicon.png') ) ) );

В полном листинге программы мы усложнили функцию build чтобы получить ожидаемый результат:

Структура виджета NewsBox

Если задан параметр imageurl

Структура виджета NewsBox Если задан параметр imageurl Структура виджета NewsBox Если задан параметр imageurl

Если параметр imageurl не задан

Структура виджета NewsBox Если параметр imageurl не задан Структура виджета NewsBox Если параметр imageurl не задан

2. StatefulWidget

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

Под изменяемым состоянием понимается изменение внутреннего состояния экземпляра класса в зависимости от какого-то события (по нажатию, времени и пр.) Для этого нужно создавать виджет от суперкласса StatefulWidget.

Вот некоторые виджеты, которые наследуются от StatefulWidget:

Самая простая конструкция для создания виджета от суперкласса StatefulWidget:

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

Возьмем наш предыдущий виджет для отображения новостных анонсов и добавим в него кнопку «избранное» и счетчик который будет указывать сколько людей добавило новость в избранное. Т.е. у нас будет два состояния: «избранное» и «не избранное».

Поскольку изменяться будет только кнопка и счетчик, то мы и обернем их в отдельный виджет class NewsBoxFavourit extends StatefulWidget.

В нашем классе NewsBoxFavourit мы объявили два скрытых поля _num и _like, значение которых устанавливается через конструктор NewsBoxFavourit(this._num, this._like), после они передается в класс состояния «избранное» createState() => new NewsBoxFavouritState(_num, _like).

В классе состояния class NewsBoxFavouritState extends State мы объявили одноименные поля только тут они публичные и изменяемые.

Поля num и like в классе состояния можно сделать приватными, но мы так сделали намеренно, чтобы на момент изучения данного урока не было путаницы – какие из объявленных переменных мы используем для проверки и изменения (те которые публичные, в классе состояния).

Конструктор NewsBoxFavouritState(this.num, this.like) инициализирует переменные в классе.

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

Так как переменные _num и _like в виджете NewsBoxFavourit не изменяемые, мы не можем использовать их напрямую, и должны переопределить в NewsBoxFavouritState таким образом:

void pressButton() { – мы вынесли реализацию функции нажатия на кнопку в класс, чтобы визуально разгрузить код программы, также это может позволить использовать одну функцию в нескольких виджетах или при разных событиях.

setState() – в качестве параметра принимает функцию, в которой мы сделали смену состояния, после выполнения переданной функции setState вызывает перестроение виджета через функцию build() в классе состояния.

Еще функцию нашей кнопки можно было описать так:

Результат работы был бы таким же.

like = !like – производит смену состояния, если у нас значение было false – то станет противоположным – true, и наоборот с true.

if(like) num++; else num--; – у нас сменилось состояние и теперь если мы добавили новость в избранное то количество точно увеличится на один, по этому мы прибавляем к счетчику единицу. И на оборот, если мы когда-то уже добавляли в избранное, а теперь убираем свой выбор – мы должны отнять единицу в переменной num.

В функции build класса NewsBoxFavouritState происходит вывод виджетов с учетом наших переменных num и like.

new Text('В избранном $num', textAlign: TextAlign.center) – виджет текста выводит в две строки сообщение ( – означает перевод строки)
$num – это вызов переменной счетчика в строке, можно еще написать так 'В избранном ${num}'

IconButton – виджет кнопки с иконкой, обязательные параметры icon и onPressed

icon: new Icon(like ? Icons.star : Icons.star_border, size: 30.0, color: Colors.blue[500]) – в виджете Icon первым параметром должен быть объект класса IconData,

like ? Icons.star : Icons.star_border – это выражение возвращает Icons.star если like == true, либо Icons.star_border если like == false

size: 30.0 – через параметр size можно увеличить или уменьшить размер иконки

color: Colors.blue[500] – устанавливает оттенок синего цвета для иконки

onPressed: pressButton – вызов функции pressButton при нажатии на кнопку из нашего класса NewsBoxFavouritState

Структура виджета NewsBoxFavourit

Структура виджета NewsBoxFavourit

Листинг программы

3. Итоги

StatelessWidget – нужен там где внутреннее состояние одно и оно сформировано параметрами и данными которые нам известны заранее.

StatefulWidget – нужен когда внутренних состояний больше одного и они могут сменять друг друга.

Виджеты от StatelessWidget и StatefulWidget могут включать друг друга.

Создатели Flutter SDK рекомендуют использовать StatelessWidget везде, где можно обойтись без StatefulWidget – связано это с тем что перестроение последнего стоит намного дороже. Так же рекомендуется сводить к миниму дочерних виджетов в StatefulWidget.