Google Flutter с нуля: Сетки, списки и источники данных
() translation by (you can also view the original English article)
Сегодня почти в каждом приложении есть списки. Это потому, что использование прокручиваемого списка чаще — самый простой способ отображения большого количества похожих элементов на маленьком экране.
Движок Flutter предлагает несколько виджетов, которые вы можете эффективно использовать для создания и отображения таких списков, при минимальном написании кода. В этом уроке, я покажу вам как использовать их с локальными и удалёнными источниками данных.
1. Отображение непрокручиваемых списков
Если вам нужно отобразить небольшое количество похожих элементов, и хоть вы уверены, что на экране пользователя они все поместятся за раз, то использование виджета Column
будет самым эффективным.
Для создания списка в вашем приложении на Flutter, вы можете создать класс List
, который предлагается языком программирования Dart. После создания списка, вы можете использовать его метод add()
, для добавления в него любого количества пунктов. Следующий код показывает как создать список, содержащий три виджета RaisedButton
:
1 |
// Create list
|
2 |
List<RaisedButton> myItems = new List(); |
3 |
|
4 |
// Add three button widgets to it
|
5 |
myItems.add(new RaisedButton( |
6 |
child: new Text("Twitter"), |
7 |
onPressed: (){} |
8 |
));
|
9 |
myItems.add(new RaisedButton( |
10 |
child: new Text("Facebook"), |
11 |
onPressed: (){} |
12 |
));
|
13 |
myItems.add(new RaisedButton( |
14 |
child: new Text("Reddit"), |
15 |
onPressed: (){} |
16 |
));
|
Заметьте, что у каждого пункта в списке есть пустой, связанный с ним, обработчик событий onPressed
, потому что без него данный пункт будет неактивным.
Теперь, когда список готов, чтобы отобразить его, вы можете назначить его непосредственно на свойство children
виджета Column
. Однако обычно, вам захочется определить где на экране будут располагаться элементы списка. Так как виджет Column
является плавающим, вы можете управлять расположением его элементов, а также его положением по основной оси и оси пересечения, используя свойства mainAxisAlignment
и crossAxisAlignment
. По умолчанию, основная ось виджета Column
— это вертикальная ось, а ось пересечения — горизонтальная ось.
Следующий код показывает вам, как расположить эти три кнопки на пересечении осей, так чтобы он и вертикально оказался посередине экрана.
1 |
Column column = new Column( |
2 |
mainAxisAlignment: MainAxisAlignment.center, |
3 |
crossAxisAlignment: CrossAxisAlignment.stretch, |
4 |
children: myItems |
5 |
);
|
Вот как теперь будет выглядеть column:



Важно отметить, что во время выполнения возникнет ошибка, если виджету Column
не удастся вместить все свои дочерние компоненты. Например, если в вашем списке более десятка виджетов RaisedButton
вместо трёх, вы увидите сообщение об ошибке, которое выглядит вот так:



2. Отображение простого прокручиваемого списка
Для немного бо́льших списков, содержимое которых, скорее всего выйдет за границы экрана, вам надо будет подумать об использовании виджета ListView
, потому что он поддерживает прокрутку.
Вы могли заметить, что код, который мы писали для создания списка на предыдущем шаге был длинным и повторяющимся. При создании бо́льшего списка, использование того же подхода может быть очень утомительным. Простой альтернативный подход — это использовать два списка: один для данных, а другой для виджетов.
Вот как вы можете быстро создать список, используя оператор []
, который сейчас содержит только несколько строк:
1 |
List<String> data = <String>["Twitter", "Reddit", "YouTube", "Facebook", |
2 |
"Vimeo", "GitHub", "GitLab", "BitBucket", "LinkedIn", "Medium", |
3 |
"Tumblr", "Instagram", "Pinterest"]; |
Чтобы преобразовать список строк выше в список виджетов RaisedButton
, вы можете использовать методы map()
и toList()
. С помощью метода map()
, вы можете использовать каждую строку для генерирования нового виджета RaisedButton
. А с помощью метода toList()
, вы можете преобразовать объекты Iterable
, возвращаемые методом map()
в объект List. Следующий код показывает как:
1 |
List<RaisedButton> myWidgets = data.map((item) { |
2 |
return new RaisedButton( |
3 |
child: new Text(item), |
4 |
onPressed: () async { |
5 |
String url = "https://${item}.com"; |
6 |
if(await canLaunch(url)) |
7 |
await launch(url); |
8 |
}
|
9 |
);
|
10 |
}).toList(); |
Для полноты картины, код выше, также показывает вам, как создать обработчик события onPressed
, который использует методы canLaunch()
и launch()
, предлагаемые пакетом url_launcher
для открытия веб-сайта, который выбрал пользователь в браузере по умолчанию.
Когда ваш список будет готов, вы можете передать его в свойство children
виджета ListView
для его отображения.
1 |
ListView myList = new ListView( |
2 |
children: myWidgets |
3 |
);
|
Если вы запустите приложение на данном этапе, вы должны иметь возможность прокручивать список и нажимать любую кнопку для запуска назначенного веб-сайта.



3. Создание сетки
Виджет ListView
позволяет вам разместить только один элемент по поперечной оси. Элемент по умолчанию, будет растянут, чтобы занять всё пространство по этой оси. Если вы хотите большую гибкость, вам стоит подумать об использовании виджета GridView
, который позволяет указать, сколько элементов расположить по поперечной оси.
В следующем коде для создания виджета GridView
, который отображает по два элемента в строку, используется конструктор GridView.count()
:
1 |
GridView myGrid = GridView.count( |
2 |
crossAxisCount: 2, |
3 |
children: myWidgets |
4 |
);
|
Вот как выглядит сетки:



4. Отображение больших списков
Для списков данных, которые содержат больше данных, чем десяток элементов, вы должны избегать создания списков виджетов вручную, так как вы делали это ранее. Почему? Потому что со здание виджета это ресурсоёмкая операция и большие списки виджетов могут потреблять много памяти.
Вместо этого, вы должны использовать функцию IndexedWidgetBuilder
, которая позволяет вам генерировать виджеты только когда пользователю нужно их увидеть. Таким образом виджеты генерируются «лениво», когда пользователь прокручивает ваш виджет ListView
.
Весьма маловероятно, что у вас будет большое количество данных, определяемых прямо в вашем приложении. Обычно, вы получаете эти данные с удалённого сервера. Таким образом, чтобы дать вам реальный пример, давайте покажу вам как получить 100 вопросов с Stack Overflow, используя Stack Exchange API и отображением их по требованию.
Начнём с создания подкласса класса StatefulWidget
, который будет действовать как контейнер для вашего виджета ListView
и переопределять его метод createState()
.
1 |
class VeryLargeListHolder extends StatefulWidget { |
2 |
@override
|
3 |
State<StatefulWidget> createState() { |
4 |
return new MyState(); |
5 |
}
|
6 |
}
|
Класс MyState
, упомянутый в коде выше пока не существует, поэтому создаём его и перезапишем его метод build()
.
1 |
class MyState extends State<VeryLargeListHolder> { |
2 |
@override
|
3 |
Widget build(BuildContext context) { |
4 |
// TODO
|
5 |
}
|
6 |
}
|
Далее, добавляем объект List
как переменную этого класса. Вы будете использовать его для хранения вопросов, которые загружаются с Stack Overflow. Дополнительно, добавляем конечную точку в качестве другой переменной.
1 |
List questions; |
2 |
|
3 |
String endpoint = "https://api.stackexchange.com/2.2/questions?" + |
4 |
"pagesize=100&order=desc&sort=activity&site=stackoverflow"; |
Если вы не хотите, чтобы пользователь нажал кнопку для загрузки вопросов, я рекомендую загружать их автоматически, при инициализации виджета. Соответственно, переопределяем метод initState()
и вызываем новый асинхронный метод loadData()
.
1 |
@override
|
2 |
void initState() { |
3 |
super.initState(); |
4 |
loadData(); |
5 |
}
|
6 |
|
7 |
void loadData() async { |
8 |
// More code here
|
9 |
}
|
Для загрузки вопросов, в методе loadData()
, вы можете использовать функцию get()
из http
пакета от Dart. API конечной точки возвращает JSON документ, который вы можете спарсить, используя функцию json.decode()
, доступную в пакете convert
от Dart'а. В следующем коде показано как:
1 |
String rawData = (await http.get(endpoint)).body; |
2 |
Map jsonData = json.decode(rawData); |
Как только документ JSON был сконвертирован в объект Map
, вы можете использовать значения, связанные с ключами своих items
, для инициализации переменной questions
. Однако, переменная, является частью состояния виджета. Таким образом, вам надо убедиться, что вы обновили её только в методе setState()
. Вот так:
1 |
setState(() { |
2 |
questions = jsonData["items"]; |
3 |
});
|
А теперь вы можете создать новый виджет ListView
с помощью конструктора ListView.builder()
, который ожидает функция IndexedWidgetBuilder
и item.count в качестве аргумента. Пока что, количество элементов — это не что иное, как размер списка questions
. Соответственно, добавляем следующий код в метод build()
класса MyState
:
1 |
ListView myList = ListView.builder( |
2 |
itemCount: questions == null ? 0 : questions.length, |
3 |
itemBuilder: (BuildContext context, int index) { |
4 |
// More code here
|
5 |
}
|
6 |
);
|
В функции сборщика, всё, что вам надо сделать — это создать маленькое дерево виджетов для отображения различных деталей о каждом вопросе, который вы загружаете. Пакет Flutter'а material
предлагает очень полезный виджет, называемый ListTile
, который позволяет быстро создать такое дерево придерживаясь правил Material Design.
Следующий код показывает вам, как отобразить заголовок и автора вопросов, используя свойства ваджета ListTile
: title
и subtitle
:
1 |
return new ListTile( |
2 |
title: Text(questions[index]["title"]), |
3 |
subtitle: Text("Asked by ${questions[index]["owner"]["display_name"]}") |
4 |
);
|
И последнее, создаём новый виджет Scaffold
, назначаем виджет ListView
на его свойство body
, и возвращаем в методе build()
, таким образом его можно использовать с виджетом MaterialApp
. Дополнительно, вы можете добавить виджет AppBar
к виджету Scaffold
.
1 |
return new Scaffold( |
2 |
appBar: new AppBar( |
3 |
title: new Text("LargeListDemo") |
4 |
),
|
5 |
body: myList |
6 |
);
|
Вот так будет выглядеть приложение после загрузки вопросов:



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