- Уроки
- Урок 2. StatelessWidget и StatefulWidget
Урок 2. StatelessWidget и StatefulWidget
За последние 30 дней: 237 просмотров, 103 посетителя.
В предыдущем уроке мы рассмотрели пример построения интерфейса приложения с помощью виджетов, создали свой виджет MyBody наследуемый от класса StatelessWidget.
Все собственные классы-виджеты рекомендуется создавать от двух основных: StatelessWidget и StatefulWidget.
Отдельные части интерфейса удобно объединять в отдельные виджеты, созданные от этих двух суперклассов, но какой из них использовать – мы рассмотрим в этом уроке.
1. StatelessWidget
StatelessWidget – рекомендуется для неизменяемых виджетов. Это такие виджеты которые не имеют внутреннего состояния, зависят только от конфигурационных параметров и от родительских виджетов.
Вот некоторые виджеты которые наследуются от StatelessWidget:
- class Text()
- class FlatButton()
- class Container()
- и др.
Самая простая конструкция для создания виджета от суперкласса StatelessWidget:
Допустим, что мы хотим выводить список анонсов новостей, каждая новость представляет из себя блок в котором есть: картинка, заголовок и текст.
Получения списка новостей с сервера мы рассмотрим позже, сейчас нас интересует отдельная новость и блок который ее будет отображать.
Поскольку все данные для каждого блока нам известны заранее и не будут изменятся, то суперкласс StatelessWidget нам отлично подходит.
Создадим класс NewsBox от суперкласса StatelessWidget и определим в нем поля:
_title – заголовок анонса новости,
_text – содержание новостного сообщения,
_imageurl – ссылка на изображение.
Разберем строку final String _title;
title – поле нашего класса, переменная в которой мы будем хранить заголовок новостного сообщения
_ (подчеркивание перед именем переменной) – в языке Dart говорит о том что данная переменная скрыта и доступна только внутри объекта нашего класса (аналог private в других языках программирования)
String – наша переменная строкового типа
final – данная переменная должна быть объявлена при создании экземпляра класса и не может быть изменена
Можно было бы просто объявить поле в классе с помощью var title; в перспективе это плохой вариант:
- в нем нет типизации, а значит можно передать туда число или любой другой объект – в будущем такая конструкция может создать много проблем;
- открытый доступ к полю объекта в более сложном примере может неожиданно «сломать» работу вашего класса, если Вы будете менять его из вне и он будет не final, или же если нужно производить проверку входного значения;
- отсутствие final там где данные в объекте не меняются – может смутить другого разработчика, или даже Вас самих когда начнете забывать свой код, так же редактор Android рекомендует чтобы все поля от суперкласса StatelessWidget были final.
Добавим конструктор в наш класс:
NewsBox – название конструктора должно совпадать с именем нашего класса.
В круглых скобках () мы указываем, что при создании экземпляра ожидается два обязательных параметра значения которых будет присвоено скрытым полям _title и _text нашего класса.
В фигурных скобках {} указывается, что в конструкторе можно указать параметр imageurl со значением типа String.
Т.е. мы можем создать экземпляр нашего класса двумя способами:
- new NewsBox('заголовок', 'содержание новости', imageurl: 'http://url/image.jpg') // с параметром imageurl
- new NewsBox('заголовок', 'содержание новости') // без imageurl
А вот так вызывать наш конструктор нельзя:
- new NewsBox() // без параметров
- new NewsBox('заголовок') // только с одним параметром
- new NewsBox('заголовок', imageurl: 'http://url/image.jpg', 'содержание новости') // менять местами обязательные и не обязательные параметры по ключу
Приведем краткую структуру, которую мы хотим вывести:
- Если изображение задано через параметр 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)]) ]) )
- Если изображения нет, то выведем без него:
return new Container( child: new Row( children: [ new Column(children: [new Text(_title),new Text(_text)]) ]) )
Листинг программы
В полном листинге программы мы усложнили функцию build чтобы получить ожидаемый результат:
- if (_imageurl != null && _imageurl != '') – в условие мы добавили проверку не только на то что адрес изображения определен, но и что он не пустой
- new Container(color: Colors.black12, height: 100.0, – в виджете контейнера мы указали цвет фона блока и его высоту. ВНИМАНИЕ: параметр height ожидает значение типа double поэтому стоит 100.0 а не 100
- 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: – виджет Column, который содержит два текстовых виджета, мы обернули двумя виджетами:
- new Expanded – используется для ограничения размеров дочерних виджетов, без него виджет Column может иметь безграничные размеры и выходить за края экрана.
- new Container – со свойством padding создает внутри контейнера отступы, таким образом текст не будет впритык к изображению или краям экрана.
- new Text(_title, style: new TextStyle(fontSize: 20.0), overflow: TextOverflow.ellipsis) – для заголовка новости мы применили стиль с увеличенным размером и свойство overflow которое позволяет обрезать слишком длинный текст
- new Expanded(child:new Text(_text, softWrap: true, textAlign: TextAlign.justify)) – к виджету текста мы применили перенос текста с помощью параметра softWrap, добавили выравнивание текста по ширине, в случае слишком длинного текста, виджет Expanded обрежет все то что не войдет по высоте.
Структура виджета NewsBox
Если задан параметр imageurl
Если параметр imageurl не задан
2. StatefulWidget
StatefulWidget – рекомендуется для изменяемых виджетов, с изменяемым внутренним состоянием.
В примере выше мы создали класс, который может выглядеть по разному если в нем есть или нет изображения, о наличии изображения известно при создании экземпляра класса.
Под изменяемым состоянием понимается изменение внутреннего состояния экземпляра класса в зависимости от какого-то события (по нажатию, времени и пр.) Для этого нужно создавать виджет от суперкласса StatefulWidget.
Вот некоторые виджеты, которые наследуются от StatefulWidget:
- class Image()
- class Form()
- и др.
Самая простая конструкция для создания виджета от суперкласса StatefulWidget:
Конструкция изменяемого виджета отличается от той которую мы использовали раньше:
- MyWidget – наш класс должен иметь реализацию функции createState(), которая должна возвращать экземпляр класса от суперкласса State
- @override – аннотация переопределения указывает на то, что последующее описание функции заменяет функцию суперкласса от которого идет наследование.
Неиспользование данной аннотации не приводит к ошибкам, но желательно для понимания кода - createState() => new MyWidgetState(); – это краткая запись
createState() {return new MyWidgetState();} – так выглядит привычная для многих полная реализация функции (можно использовать и ее). - MyWidgetState – содержит знакомую функцию build которая аналогична тому как мы использовали раньше в виджете от суперкласса StatelessWidget
- setState(() {}); – теперь вызывая такую конструкцию при событии в нашем виджете или дочерних элементах, мы сможем вызывать перестроение нашего виджета
Возьмем наш предыдущий виджет для отображения новостных анонсов и добавим в него кнопку «избранное» и счетчик который будет указывать сколько людей добавило новость в избранное. Т.е. у нас будет два состояния: «избранное» и «не избранное».
Поскольку изменяться будет только кнопка и счетчик, то мы и обернем их в отдельный виджет 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
Листинг программы
3. Итоги
StatelessWidget – нужен там где внутреннее состояние одно и оно сформировано параметрами и данными которые нам известны заранее.
StatefulWidget – нужен когда внутренних состояний больше одного и они могут сменять друг друга.
Виджеты от StatelessWidget и StatefulWidget могут включать друг друга.
Создатели Flutter SDK рекомендуют использовать StatelessWidget везде, где можно обойтись без StatefulWidget – связано это с тем что перестроение последнего стоит намного дороже. Так же рекомендуется сводить к миниму дочерних виджетов в StatefulWidget.