[Перевод] Организация данных на экране | Flutter

Организация данных на экране | Flutter

Организация данных на экране | Flutter

Привет, если вы на пути изучения Flutter/Dart или вам просто интересно почитать про путь изучения, подписывайтесь на мой канал в telegram, буду рад вас видеть! А сегодня поговорим про организацию данных на экране во Flutter!

Предыдущая статья:  Разработка интерфейса | Flutter

Содержание:
— Реализация вертикального ListView
— Реализация горизонтального ListView
— Добавление SliverAppBar
— Добавление SliverList
— Добавление GridView элементов
— Добавление SnackBar (всплывающее уведомление)

В этой статье мы переходим к изучению того, как определять данные на экране. К счастью, Flutter предоставляет несколько очень приятных элементов «из коробки», которые позволяют легко управлять представлением информации.

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

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

Тема охватывает множество вариантов использования, которые, безусловно, будут очень полезны при создании вашего приложения. Например, вертикальный список — один из наиболее распространенных шаблонов проектирования во Flutter, поэтому, научившись им пользоваться, в будущем сыграет вам на руку.

При создании более сложных приложений всегда старайтесь, чтобы тяжелую работу выполняли Dart и Flutter. Каждая итерация языка и фреймворка обеспечивает повышение эффективности. Включение этих новых функций (например, null safety) в каждый новый релиз поможет вам избежать определенных ошибок и будет способствовать улучшению практики кодирования.

Учитывая гибкость Flutter, не забывайте, что вы можете расширять виджеты платформы (например, текст, контейнеры и т.д.) настолько, насколько позволит ваше воображение.

Реализация вертикального ListView

Проблема

Вы хотите включить вертикальный список элементов в приложение Flutter.

Решение

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

Вот пример того, как добавить вертикальный ListView в приложение Flutter:

import 'package:flutter/material.dart';

class ListTileItem {
  final String monthItem;
  const ListTileItem({
    required this.monthItem,
  });
}

class ListDataItems {
  final List monthItems = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ];
  ListDataItems();
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    const title = 'MyAwesomeApp';
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(title),
        ),
        body: MyListView(),
      ),
    );
  }
}

class MyListView extends StatelessWidget {
  MyListView();
  final ListDataItems item = ListDataItems();
  
	@override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: item.monthItems.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text(item.monthItems[index]));
      },
    );
  }
}

Обсуждение

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

Рисунок 11-1. Пример вертикального просмотра списка

Рисунок 11–1. Пример вертикального просмотра списка

Класс ListDataItems определен для информации о месяце, которая в конечном итоге будет отображаться в нашем виджете ListView. Обратите внимание, как определение класса означает, что мы можем рассматривать класс ListDataItems изолированно. Хорошим подходом является возможность изменять структуры данных с минимальным воздействием на функциональность приложения. Например, если мы добавляем/удаляем элемент month, нам не нужно изменять какой-либо код, который обрабатывает класс ListDataItems.

В классе MyListView мы объявляем ListView builder. Builder используется для перебора структуры ListDataItems. Комбинация builder и ListView обеспечивает эффективный с точки зрения памяти метод управления большими объемами данных, отображаемых на экране. Хотя вы можете использовать ListView без builder, настоятельно рекомендуется использовать конструктор в ваших приложениях, поскольку этот подход в конечном счете более масштабируем для обработки списков данных.

Для каждого элемента, обрабатываемого ListView.builder, он вызывает класс ListTile и отображает текстовый виджет, который отображает название месяца. ListViews использует виджет ListTile для отображения информации, так что это еще одна комбинация, которую вы увидите при использовании ListView.

Реализация горизонтального ListView

Проблема

Вы хотите включить горизонтальный список элементов в приложение Flutter.

Решение

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

Вот пример того, как добавить горизонтальный ListView в приложение Flutter:

import 'package:flutter/material.dart';

class ListTileItem {
  final String monthItem;
  const ListTileItem({
    required this.monthItem,
  });
}

class ListDataItems {
  final List monthItems = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ];
  ListDataItems();
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    const title = 'MyAwesomeApp';
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(title),
        ),
        body: MyListView(),
      ),
    );
  }
}

class MyListView extends StatelessWidget {
  MyListView({Key? key}) : super(key: key);
  final ListDataItems item = ListDataItems();
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: item.monthItems.length,
      itemBuilder: (context, index) {
        return Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(item.monthItems[index]),
            const SizedBox(
              width: 10.0,
            ),
          ],
        );
      },
    );
  }
}

Обсуждение

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

Рисунок 11-2. Пример горизонтального просмотра списка

Рисунок 11–2. Пример горизонтального просмотра списка

Класс ListDataItems определен для информации о месяце, которая в конечном итоге будет отображаться в нашем виджете ListView. Обратите внимание, как определение класса означает, что мы можем рассматривать класс ListDataItems изолированно. Хорошим подходом является возможность изменять структуры данных с минимальным воздействием на функциональность приложения. Например, если мы добавляем/удаляем элемент month, нам не нужно изменять какой-либо код, который обрабатывает класс ListDataItems.

В классе MyListView мы объявляем ListView builder. Builder используется для перебора структуры ListDataItems. Комбинация builder и ListView обеспечивает эффективный с точки зрения памяти метод управления большими объемами данных, отображаемых на экране. Как уже упоминалось, вы можете использовать ListView без builder, но настоятельно рекомендуется использовать в ваших приложениях, поскольку этот подход в конечном счете более масштабируем для обработки списков данных.

Для каждого элемента, обрабатываемого ListView.builder, объявляется элемент виджета строки, который используется для отображения информации на экране. Как часть ListView.builder, мы также добавляем некоторую инициализацию для данных. Сначала мы сообщаем ListView, что хотим, чтобы данные отображались по горизонтальной оси, что означает, что нам нужен горизонтальный список. Помните, что по умолчанию для ListView используется вертикальный список. Далее мы добавляем некоторое форматирование в виджет Row, чтобы указать, где должны выводиться данные. Используйте свойство Row crossAxisAlignment, чтобы указать приложению, где вы хотите разместить элемент. Заключительной частью рендеринга является добавление SizedBox, чтобы применить разделитель к выводимой информации.

Добавление SliverAppBar

Проблема

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

Решение

Используйте виджет SliverAppBar, чтобы создать адаптивный заголовок для вашего приложения. Настройка по умолчанию для SliverAppBar заключается в том, чтобы панель приложений не отображалась до тех пор, пока не появится верхняя часть списка. Добавьте свойство SliverAppBar (floating: true,), чтобы панель приложений появлялась снова, когда пользователь прокручивает экран вверх.

Вот пример того, как добавить SliverAppBar в приложение Flutter:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SliverAppBar Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter and Dart'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;
  const MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[300],
      body: const CustomScrollView(
        slivers: [
          SliverAppBar(
            leading: Icon(Icons.menu),
            title: Text('Sliver App Bar'),
            expandedHeight: 300,
            collapsedHeight: 150,
            floating: false,
          ),
        ], // End
      ),
    );
  }
}

Обсуждение

Виджет SliverAppBar предоставляет адаптивную панель приложений, которая реагирует на контекст прокрутки приложения.

На рисунке 11–3 мы наблюдаем два специфических поведения виджета SliverAppBar. Когда пользователь прокручивает страницу в нижней части экрана, панель приложений реагирует уменьшением размера панели приложений. Результатом этого является предоставление большего пространства для просмотра элементов, отображаемых под областью заголовка. Когда пользователь прокручивает назад к верхней части экрана, действие меняется на обратное, и по мере приближения пользователя к верхней части панель приложений возвращается к исходному размеру. Следует отметить, что свойство Scaffold AppBar удалено в примере и заменено SliverAppBar в свойстве body.

Рисунок 11-3. Пример SliverAppBar

Рисунок 11–3. Пример SliverAppBar

Управление размерами SliverAppBar обеспечивается свойствами expandedHeight: 300 и collapsedHeight: 150. Оба свойства настраиваемы, что означает, что вы, как разработчик, управляете отображением на экране.

При использовании виджета SliverAppBar для обработки информации используйте SliverList, а не виджет ListView. SliverList предназначен для взаимодействия со SliverAppBar и обеспечит лучшую совместимость в вашем приложении.

Добавление SliverList

Проблема

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

Решение

Используйте SliverList при работе с заголовком SliverAppBar. SliverAppBar включает пользовательскую обработку данных, чтобы помочь с прокруткой заголовка и взаимодействием с данными.

Вот пример того, как добавить SliverList в приложение Flutter:

import 'package:flutter/material.dart';

class CarItem {
  final String title;
  final String subtitle;
  final String url;
  CarItem({
    required this.title,
    required this.subtitle,
    required this.url,
  });
}

class ListDataItems {
  final List carItems = [
    CarItem(
        title: '911 Cabriolet',
        subtitle: '911 Carrera Cabriolet Porsche',
        url: 'https://oreil.ly/m3OXC'),
    CarItem(
        title: '718 Spyder',
        subtitle: '718 Spyder Porsche',
        url: 'https://oreil.ly/hca-6'),
    CarItem(
        title: '718 Boxster T',
        subtitle: '718 Boxster T Porsche',
        url: 'https://oreil.ly/Ws4EX'),
    CarItem(
        title: 'Cayenne',
        subtitle: 'Cayenne S Porsche',
        url: 'https://oreil.ly/gwvnL'),
    CarItem(
        title: '911 Cabriolet',
        subtitle: '911 Carrera Cabriolet Porsche',
        url: 'https://oreil.ly/m3OXC'),
    CarItem(
        title: '718 Spyder',
        subtitle: '718 Spyder Porsche',
        url: 'https://oreil.ly/hca-6'),
    CarItem(
        title: '718 Boxster T',
        subtitle: '718 Boxster T Porsche',
        url: 'https://oreil.ly/Ws4EX'),
    CarItem(
        title: 'Cayenne',
        subtitle: 'Cayenne S Porsche',
        url: 'https://oreil.ly/gwvnL'),
    CarItem(
        title: '911 Cabriolet',
        subtitle: '911 Carrera Cabriolet Porsche',
        url: 'https://oreil.ly/m3OXC'),
    CarItem(
        title: '718 Spyder',
        subtitle: '718 Spyder Porsche',
        url: 'https://oreil.ly/hca-6'),
    CarItem(
        title: '718 Boxster T',
        subtitle: '718 Boxster T Porsche',
        url: 'https://oreil.ly/Ws4EX'),
    CarItem(
        title: 'Cayenne',
        subtitle: 'Cayenne S Porsche',
        url: 'https://oreil.ly/gwvnL'),
  ];

  ListDataItems();
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SliverList Widget Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter and Dart'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;
  const MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[300],
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            leading: Icon(Icons.menu),
            title: Text('MyAwesomeApp'),
            expandedHeight: 300,
            collapsedHeight: 150,
            floating: false,
          ),
          // Next, create a SliverList
          MySliverList(),
        ], // End
      ),
    );
  }
}

class MySliverList extends StatelessWidget {
  MySliverList({Key? key}) : super(key: key);
  final ListDataItems item = ListDataItems();
  
  @override
  Widget build(BuildContext context) {
    return // Next, create a SliverList
        SliverList(
      // Use a delegate to build items as they're scrolled on-screen.
      delegate: SliverChildBuilderDelegate(
        (context, index) => ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage(item.carItems[index].url),
          ),
          title: Text(item.carItems[index].title),
          subtitle: Text(item.carItems[index].subtitle),
        ),
        // Builds 1000 ListTiles
        childCount: item.carItems.length,
      ),
    );
  }
}

Обсуждение

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

На рисунке 11–4, когда пользователь прокручивает экран вниз, ему отображается больше элементов списка. Одновременно заголовок SliverAppBar сжимается и занимает меньше места на экране. Когда пользователь прокручивает экран вверх, панель SliverAppBar увеличивается по мере приближения к верхней части экрана.

Рисунок 11-4. Пример SliverList

Рисунок 11–4. Пример SliverList

SliverList передается количество элементов, и это используется для определения количества итераций, используемых виджетом. SliverChildBuilderDelegate используется для обработки информации, которая будет отображаться на экране. Механизм обработки аналогичен ListView.builder и предполагает структуру данных, основанную на списке. Убедитесь, что для свойства child Count задана длина обрабатываемого списка, чтобы отобразить всю связанную информацию. Помните, что списки предоставляют свойство длины, поэтому эта информация доступна нам без какой-либо дополнительной работы.

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

Добавление GridView элементов

Проблема

Вам нужен способ отображения информации в виде сетки элементов.

Решение

Используйте GridView, если вы хотите структурировать данные, используя как горизонтальную, так и вертикальную компоновку. GridView сочетает в себе горизонтальное и вертикальное распределение данных на основе доступных размеров экрана.

Вот пример того, как добавить GridView в приложение Flutter:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    const title = 'MyAwesomeApp';
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(title),
        ),
        body: const MyGridViewBuilderWidget(),
      ),
    );
  }
}

class MyGridViewBuilderWidget extends StatelessWidget {
  const MyGridViewBuilderWidget({Key? key}) : super(key: key);
  final gridItems = 10;
  
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
        itemCount: gridItems,
        gridDelegate:
            const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 5),
        itemBuilder: (context, index) {
          return Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                height: 50,
                width: 50,
                color: Colors.blue,
                child: Center(child: Text(index.toString())),
              ));
        });
  }
}

Обсуждение

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

На рисунке 11–5 обратите внимание, что GridView автоматически переносит содержимое, видимое на экране. Пример включает индекс, поэтому вы можете видеть, что направление, используемое GridView по умолчанию, горизонтальное. Элемент, на который переносится GridView, задается свойством crossAxisCount, которому в коде присвоено значение 5. Также обратите внимание, что, поскольку направление по умолчанию — горизонтальное, содержимое будет продолжать добавляться по горизонтали до тех пор, пока оно либо не завершит список, либо не потребуется перенос по вертикальной оси.

Рисунок 11-5. Пример представления сетки

Рисунок 11–5. Пример представления сетки

GridView является производным от CustomScrollView с одной SliverGrid. При добавлении элементов вы увидите, что он использует SliverGridDelegateWithFixedCrossAxisCount, позволяющий нам контролировать количество элементов на горизонтальной оси. Подобно другим методам построения, нам нужно указать, сколько элементов должно быть обработано с помощью свойства ItemCount. Если вы передаете список, обратите внимание, что ItemCount должно быть установлено в List.length.

Альтернативой использованию GridView.builder является использование GridView.count для ручной обработки элементов в сетке. На самом деле проще просто научиться использовать метод builder даже для самых простых случаев использования, поскольку он обеспечивает более гибкий подход. Обработка одного или двух элементов является не очень распространенным вариантом использования и требует от вас написания значительно большего количества кода для выполнения одной и той же задачи.

Добавление SnackBar (всплывающее уведомление)

Проблема

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

Решение

Используйте виджет SnackBar для отображения временного уведомления на экране. Уведомление будет связано с событием в вашем приложении, например, с нажатием пользователем элемента.

Вот пример того, как добавить виджет закусочной в приложение Flutter:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    const title = 'MyAwesomeApp';
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(title),
        ),
        body: MyListView(),
      ),
    );
  }
}

class ListViewData {
  final List monthItems = [
    'January',
    'February',
    'March',
  ];
}

class MyListView extends StatelessWidget {
  MyListView({Key? key}) : super(key: key);
  final ListViewData items = ListViewData();
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
        itemCount: items.monthItems.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(items.monthItems[index]),
            onTap: () {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('You selected ${items.monthItems[index]}'),
                ),
              );
            },
          );
        });
  }
}

Обсуждение

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

На рисунке 11–6, когда пользователь нажимает на месяц, отображаемый на экране, в нижней части экрана ненадолго появляется уведомление. Уведомление в примере связано с виджетом ListTile; следовательно, оно ответит сообщением, указывающим, какой месяц был выбран пользователем.

Рисунок 11-6. Пример виджета SnackBar

Рисунок 11–6. Пример виджета SnackBar

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

SnackBar(
 duration: const Duration(seconds: 10, milliseconds: 500),
 content: Text('You selected $listTitle'),
),

В дополнение к предоставлению обратной связи пользователю, SnackBar также может выполнять дополнительные действия. Расширьте определение SnackBar, включив в него label и action для инициирования дополнительного действия:

SnackBar(
	action: SnackBarAction(
		label: 'action',
		onPressed: () {},
 
	),
	duration: const Duration(seconds: 10, milliseconds: 500),
	content: Text('You selected $listTitle'),
),

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

Информация создана автором Richard Rose | Dart and Flutter Cookbook.

© Habrahabr.ru