Предисловие.
В этой статье я расскажу об основных виджетах в Flutter. Данной информации вам будет достаточно, чтобы создать простой интерфейс для вашей программы.
Виджет Scaffold.
Первым, у нас будет виджет Scaffold, который, позволяет создавать экраны с полоской сверху и кнопку сбоку.

Вот его код.
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Tutorial"),
),
body: Center(
child: Text("Hello"),
),
),
),
);
}
Рассмотрим параметры Scaffold:
- appBar — полоска сверху
- body — основной контент.
- floatingActionButton — весящая кнопка сбоку (не добавлял)
Таким образом, мы создали экран с полоской сверху, с надписью Tutorial, и внутри, по центру, расположили надпись Hello.
Теперь, создайте новый StatelessWidget (о котором, я говорил ранее), и измените функцию main.
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: NewWidget(),
),
),
);
}
Далее, разберем макеты, чтобы было удобней располагать элементы.
Макет Row.
Виджет Row, позволяет располагать вложенные в него элементы, друг за другом, по горизонтали.

Элементы помещаются в массив children
Widget build(BuildContext context) {
return Row(
children: [
Text("elem"),
Text("elem"),
Text("elem"),
Text("elem"),
],
);
}
Макет Column.
В виджете Column, элементы располагаются друг над другом.

Widget build(BuildContext context) {
return Column(
children: [
Text("elem"),
Text("elem"),
Text("elem"),
Text("elem"),
],
);
}
Container.
Виджет Container, это аналог <div> из html, он позволяет менять свойства вложенных элементов, такие как:
- color — цвет фона, внутри контейнера
- margin — отступ за пределами контейнера
- padding — отступ внутри контейнера
- и другие

Widget build(BuildContext context) {
return Container(
child: Text("123"),
color: Colors.red,
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(30),
);
}
С помощью EdgeInsets.all(), задается отступ по всем сторонам.

Виджеты кнопок (TextButton, ElevatedButton, OutlinedButton, IconButton).
При нажатии на TextButton, выполняется метод переданный в onPressed. В нашем случае, была передана пустая функция «() {}». В child, вкладываются виджеты, тем самым меняя вид кнопки.
Widget build(BuildContext context) {
return Column(
children: [
TextButton(onPressed: () {}, child: Text("TextButton")),
ElevatedButton(onPressed: () {}, child: Text("ElevatedButton")),
OutlinedButton(onPressed: () {}, child: Text("OutlinedButton")),
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
),
],
);
}
Кнопки ElevatedButton и OutlinedButton, отличаются только видом. Поэтому, их рассматривать не буду.

Кнопка IconButton, принимает иконку. Иконки, это векторные изображения, это значит, что их можно бесконечно увеличивать без потери качества.
У Flutter, есть стандартный набор иконок, который находится в объекте Icons.
Выводим изображение (Image).
Виджет Image, может подгрузить изображение из интернета, с помощью NetworkImage.
Widget build(BuildContext context) {
return Column(
children: [
Image(
image: NetworkImage("https://under-prog.ru/favicon.png"),
),
],
);
}

Если хотите подгрузить свою картинку то, создайте папку images, в папке с проектом, и поместите туда любую png картинку.

И пропишите путь до папки images в pubspec.yaml, таким образом.

Сохраните pubspec.yaml, тем самым, вы импортируйте ресурсы в проект.
Запись такого типа
- images/
Позволяет импортировать все ресурсы в папке images.
Далее, в AssetImage, передайте путь до картинки.
Image(
image: AssetImage("images/coffe_cup_2.png"),
)
И запустите, перезагрузив проект. В итоге, картинка загрузилась из локального хранилища.
Делаем прокручиваемый список (ListView).
В параметр children виджета ListView, передайте массив виджетов. Тем самым вы получите прокручиваемый список.
Widget build(BuildContext context) {
return ListView(
children: [
Text("1"),
Text("2"),
Text("3"),
Text("4"),
Text("1"),
Text("2"),
],
);
}

Теперь, забудьте об этом способе, т.к. он не эффективный. Т.к. список, целиком, загружается в оперативную память, что может привести к тормозам, при работе с большими списками.
ListView.builder и бесконечный список.
Спасет нас ListView.builder, который, строит элементы списка на ходу, т.е. подгружает лишь те элементы, что видны на экране.
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Text("Number $index");
},
);
}
У ListView.builder, в itemBuilder, надо передать функцию, которая будет возвращать виджет элемента списка. В неё передается 2 аргумента.
- context — нужен при работе с окнами (об этом позже)
- index — индекс текущего элемента списка.
В этом примере, я вывел индекс текущего элемента, и завернул его, в виджет Text.
return Text("Number $index");
Чтобы вывести значение переменной в строке, используется знак $ перед названием переменной. Когда используется что-то кроме названия переменной (например нужно получить элемент массива, или вызвать функцию), используется запись с фигурными скобками. Например так:
"${array[12]} some text ${randObj.randVar}"

По итогу, получился бесконечный список, который отображает номер индекса.
Передача массива в ListView.
Теперь, попробуем занести массив в этот список.
Widget build(BuildContext context) {
final _array = ["Under-Prog", "Flutter", "Tutorials"];
return ListView.builder(
itemCount: _array.length,
itemBuilder: (context, index) {
return Text("${_array[index]}");
},
);
}
Этой строкой.
final _array = ["Under-Prog", "Flutter", "Tutorials"];
Я создал неизменяемый (final) массив, и сразу же, задал ему значения.
Нижнее подчеркивание перед названием переменной, означает, что переменная недоступна из вне (аналог private из Java и C++).
Далее, в параметре itemCount
itemCount: _array.length,
Задали размер списка (_array.length), чтобы индекс, не выходил за пределы массива.
И вывели элемент массива на экран.
return Text("${_array[index]}");

Украшаем список виджетом ListTile.
Виджет ListTile, это легкий способ сделать хорошо выглядящий элемент списка.
Widget build(BuildContext context) {
final _array = ["Under-Prog", "Flutter", "Tutorials"];
return ListView.builder(
itemCount: _array.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("${_array[index]}"),
leading: Text("$index"),
);
},
);
}
- titile, отвечает за содержание
- leading, за левую сноску.

Делаем свой ListTile.
Попробуем сделать свой ListTile. В этом деле, нам понадобится виджет Expanded, который растягивает переданный ему виджет, на все доступное пространство.
Widget build(BuildContext context) {
final _array = ["Under-Prog", "Flutter", "Tutorials"];
return ListView.builder(
itemCount: _array.length,
itemBuilder: (context, index) {
return Row(
children: [
Image(
image: NetworkImage("https://under-prog.ru/favicon.png"),
),
Expanded(
child: Center(
child: Text("${_array[index]}"),
),
),
],
);
},
);
}
Таким образом, картинка занимает, только, необходимое ей место, когда текст, заполняет всю остальную область.

Заключение.
В этой статье, вы узнали о базовых виджетах в Flutter, это лишь малая часть от всего многообразия виджетов. Разумеется, всех их учить не надо (да и не все они потребуются), просто, скачайте себе на телефон Flutter Catalog, от Maxing, и используйте приглянувшиеся вам виджеты. В этом каталоге есть большое количество примеров использования виджетов с приложенными исходниками.
В следующей статье, я расскажу о создании StatefullWidget, который позволит вам, перерисовывать виджеты на ходу.
Итоговый результат.
