Статьи

Google Flutter с нуля: сетки, списки и источники данных

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

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

Если вам нужно отобразить небольшое количество похожих элементов, и вы уверены, что экран пользователя сможет вместить все из них одновременно, использование виджета « Column — наиболее эффективная вещь.

Чтобы создать список в приложении Flutter, вы можете использовать класс List , предлагаемый языком программирования Dart. После создания списка вы можете использовать его метод add() чтобы добавить в него любое количество элементов. Следующий код показывает, как создать список, содержащий три виджета RaisedButton :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// Create list
List<RaisedButton> myItems = new List();
 
// Add three button widgets to it
myItems.add(new RaisedButton(
      child: new Text(«Twitter»),
  onPressed: (){}
));
myItems.add(new RaisedButton(
    child: new Text(«Facebook»),
    onPressed: (){}
));
myItems.add(new RaisedButton(
    child: new Text(«Reddit»),
    onPressed: (){}
));

Обратите внимание, что с каждым элементом в списке onPressed пустой onPressed события onPressed потому что без этого элемент был бы отключен.

Теперь, когда список готов, вы можете напрямую назначить его для children свойства отображаемого виджета « Column . Обычно, однако, вы также хотите указать, где элементы списка должны быть размещены на экране. Поскольку виджет « Column является гибким виджетом, вы можете управлять позициями элементов вдоль его главной оси и поперечной оси, используя свойства mainAxisAlignment и crossAxisAlignment . По умолчанию для виджета « Column главной осью является вертикальная ось, а поперечной осью — горизонтальная ось.

Следующий код показывает, как растянуть три кнопки по горизонтальной оси и разместить их в центре экрана по вертикали:

1
2
3
4
5
Column column = new Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: myItems
);

Вот как теперь будет выглядеть столбец:

Приложение отображает столбец с тремя кнопками

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

Приложение отображает ошибку переполнения

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

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

Вот как вы можете использовать оператор [] для быстрого создания списка данных, который на данный момент содержит только несколько строк:

1
2
3
List<String> data = <String>[«Twitter», «Reddit», «YouTube», «Facebook»,
         «Vimeo», «GitHub», «GitLab», «BitBucket», «LinkedIn», «Medium»,
         «Tumblr», «Instagram», «Pinterest»];

Чтобы преобразовать приведенный выше список строк в список виджетов RaisedButton , вы можете использовать методы map() и toList() . С помощью метода map() вы можете использовать каждую строку для генерации нового виджета RaisedButton . А с помощью toList() вы можете преобразовать объект Iterable возвращаемый методом map() в фактический объект List . Следующий код показывает вам, как:

01
02
03
04
05
06
07
08
09
10
List<RaisedButton> myWidgets = data.map((item) {
  return new RaisedButton(
    child: new Text(item),
    onPressed: () async {
      String url = «https://${item}.com»;
      if(await canLaunch(url))
        await launch(url);
    }
  );
}).toList();

Для полноты приведенный выше код также показывает, как создать onPressed события onPressed который использует canLaunch() и launch() предлагаемые пакетом url_launcher , для открытия веб-сайта, выбранного пользователем в браузере по умолчанию.

Когда ваш список готов, вы можете передать его в свойство children виджета ListView чтобы отобразить его.

1
2
3
ListView myList = new ListView(
  children: myWidgets
);

На этом этапе, если вы запустите приложение, вы сможете прокрутить список и нажать любую кнопку, чтобы запустить соответствующий веб-сайт.

Приложение, отображающее прокручиваемый список

Виджет ListView позволяет размещать только один элемент на его поперечной оси. По умолчанию элемент будет растянут, чтобы занимать все пространство, доступное на этой оси. Если вы хотите большей гибкости, вам следует рассмотреть возможность использования вместо этого виджета GridView , который позволяет указать, сколько элементов вы хотите на поперечной оси.

В следующем коде используется конструктор GridView.count() для создания виджета GridView который отображает два элемента в строке:

1
2
3
4
GridView myGrid = GridView.count(
    crossAxisCount: 2,
    children: myWidgets
);

Вот как выглядит сетка:

Приложение, отображающее сетку кнопок

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

Вместо этого вы должны использовать функцию IndexedWidgetBuilder , которая позволяет генерировать виджеты только тогда, когда пользователь должен их видеть. С его помощью вы можете лениво генерировать виджеты, когда пользователь прокручивает ваш виджет ListView .

Маловероятно, что вы будете иметь большие объемы данных, определенные прямо в ваших приложениях. Обычно вы получаете такие данные с удаленного сервера. Поэтому, чтобы дать вам реалистичный пример, позвольте мне теперь показать вам, как получить 100 вопросов из Переполнения стека с помощью API стека Exchange и отображать их по требованию.

Начните с создания подкласса класса StatefulWidget , который будет действовать как контейнер для вашего виджета ListView и переопределит его createState() .

1
2
3
4
5
6
class VeryLargeListHolder extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new MyState();
  }
}

Класс MyState упомянутый в приведенном выше коде, еще не существует, поэтому создайте его и переопределите его метод build() .

1
2
3
4
5
6
class MyState extends State<VeryLargeListHolder> {
  @override
  Widget build(BuildContext context) {
    // TODO
  }
}

Затем добавьте объект List качестве одной из переменных-членов класса. Вы будете использовать его для хранения вопросов, загруженных из Stack Overflow. Кроме того, добавьте конечную точку API в качестве другой переменной.

1
2
3
4
List questions;
 
String endpoint = «https://api.stackexchange.com/2.2/questions?»
  «pagesize=100&order=desc&sort=activity&site=stackoverflow»;

Если вы не хотите, чтобы ваш пользователь нажимал кнопку для загрузки вопросов, я предлагаю вам автоматически загружать их при инициализации виджета. Соответственно, переопределите метод initState() и вызовите новый асинхронный метод loadData() .

1
2
3
4
5
6
7
8
9
@override
void initState() {
    super.initState();
    loadData();
}
 
void loadData() async {
    // More code here
}

Внутри loadData() вы можете использовать функцию get() из http пакета Dart для загрузки вопросов. Конечная точка API возвращает документ JSON, который можно проанализировать с помощью функции json.decode() доступной в пакете convert Dart. Следующий код показывает вам, как:

1
2
String rawData = (await http.get(endpoint)).body;
Map jsonData = json.decode(rawData);

Как только документ JSON был преобразован в объект Map , вы можете использовать значение, связанное с его ключом items для инициализации переменной questions . Переменная, однако, является частью состояния виджета. Поэтому вы должны убедиться, что обновляете его только внутри setState() . Вот как:

1
2
3
setState(() {
    questions = jsonData[«items»];
});

На этом этапе вы можете создать новый виджет ListView с помощью конструктора ListView.builder() , который ожидает функцию IndexedWidgetBuilder и количество элементов в качестве аргументов. На данный момент количество элементов не больше, чем размер списка questions . Соответственно, добавьте следующий код в метод build() класса MyState :

1
2
3
4
5
6
ListView myList = ListView.builder(
    itemCount: questions == null ?
    itemBuilder: (BuildContext context, int index) {
        // More code here
    }
);

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

В следующем коде показано, как отобразить заголовок и автора вопроса, используя свойства title и subtitle виджета ListTile :

1
2
3
4
return new ListTile(
    title: Text(questions[index][«title»]),
    subtitle: Text(«Asked by ${questions[index][«owner»][«display_name»]}»)
);

Наконец, создайте новый виджет Scaffold , назначьте виджет ListView его свойству body и верните его из метода build() чтобы его можно было использовать с виджетом MaterialApp . При желании вы можете добавить виджет AppBar виджет Scaffold .

1
2
3
4
5
6
return new Scaffold(
    appBar: new AppBar(
        title: new Text(«LargeListDemo»)
    ),
    body: myList
);

Вот как приложение будет выглядеть после загрузки вопросов:

Приложение, отображающее данные переполнения стека

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