Основы Flutter для начинающих (Часть VII)

от автора

Большинство мобильных приложений содержат различные картинки.

А как же без них? Картинки делают насыщенным и более понятным пользовательский интерфейс пользователя.

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

Ну что ж поехали!

Наш план
  • Часть 1 — введение в разработку, первое приложение, понятие состояния;

  • Часть 2 — файл pubspec.yaml и использование flutter в командной строке;

  • Часть 3 — BottomNavigationBar и Navigator;

  • Часть 4 — MVC. Мы будем использовать именно этот паттерн, как один из самых простых;

  • Часть 5 — http пакет. Создание Repository класса, первые запросы, вывод списка постов;

  • Часть 6 (текущая статья) — работа с формами, текстовые поля и создание поста.

  • Часть 7 — работа с картинками, вывод картинок в виде сетки, получение картинок из сети, добавление своих в приложение;

  • Часть 8 — создание своей темы, добавление кастомных шрифтов и анимации;

  • Часть 9 — немного о тестировании;

Добавление картинок в проект

Для начала попробуем добавить собственные картинки в проект.

Будьте осторожнее: картинки добавленные в ваш проект увеличивают размер приложения, так что не переусердствуйте!

Для добавления картинок нам нужно создать новую директорию images в корне проекта:

Затем нужно прописать директорию images в файле pubspec.yaml:

# блок зависимостей dependencies:   flutter:     sdk: flutter    # ...  # зависимости для разработки dev_dependencies: 	 	# ...  # в данной секции вы можете подключить шрифты и assets файлы flutter:    # указываем, что мы используем MaterialApp иконки и наше   # приложение соответствует Material Design   uses-material-design: true    # прописываем директорию images   # / указывает на то, что мы собираемся включить   # все файлы которые будут в директории images   assets:     - images/

Теперь добавим парочку изображений. Вы можете воспользоваться уже готовыми и взять их из Github’а:

Отредактируем нашу пустую страницу AlbumListPage:

 import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';  class AlbumListPage extends StatefulWidget {   @override   _AlbumListPageState createState() => _AlbumListPageState(); } class _AlbumListPageState extends State<AlbumListPage> {   // указываем список наших изображений   final fileImages = [     "applejack.png",     "fluttershy.png",     "rarity.png",     "starlight_glimmer.png",     "twillight_sparkle.png"   ];   @override   Widget build(BuildContext context) {     return Scaffold(         appBar: AppBar(           title: Text("Album List Page"),         ),         body: _buildContent()     );   }      // мы собираемся построить сетку из изображений   Widget _buildContent() {     // ранее мы уже подключали пакет flutter_staggered_grid_view     // который содержит StaggeredGridView     return StaggeredGridView.countBuilder(       // количество изображений       itemCount: fileImages.length,       // crossAxisCount задает количество колонок       // по которым будут выравнены изображения       crossAxisCount: 8,       // отступы по вертикали       mainAxisSpacing: 10,       // отступы по горизонтали       crossAxisSpacing: 10,       staggeredTileBuilder: (index) {         // каждое изображение будет 4 колонки в ширину (первый параметр)         // изображения на четных индексах будут в 2 раза меньше (второй параметр)         return StaggeredTile.count(4, index % 2 == 0 ? 4 : 8);       },       // строим изображение       itemBuilder: (context, index) {         return Container(           decoration: BoxDecoration(             border: Border.all(color: Colors.pinkAccent, width: 1)           ),           // Image.asset используется для отображения           // изображений которые мы прописали в pubspec.yaml           // помимо asset класс Image имеет другие методы           child: Image.asset("images/${fileImages[index]}"),         );       }, );    } }

Запускаем.

Вуаля! Получилось довольно забавно.

Картинки из сети

Пришло время воспользоваться REST API для получения картинок.

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

Мы сразу выведем все картинки.

Для начала создадим модель Photo:

// данная модель очень похоже Post class Photo {   final int _id;   final String _title;   final String _url;      Photo.fromJson(Map<String, dynamic> json) :       _id = json["id"],       _title = json["title"],       _url = json["url"]; }  class PhotoList {   final List<Photo> photos = [];      PhotoList.fromJson(List<dynamic> jsonItems) {     for (var jsonItem in jsonItems) {       photos.add(Photo.fromJson(jsonItem));     }   }    }  abstract class PhotoResult {}  class PhotoResultSuccess extends PhotoResult {   final PhotoList photoList;   PhotoResultSuccess(this.photoList); }  // произошла ошибка class PhotoResultFailure extends PhotoResult {   final String error;   PhotoResultFailure(this.error); }  // загрузка данных class PhotoResultLoading extends PhotoResult {   PhotoResultLoading(); }

Добавляем новый метод в Repository:

Future<PhotoList> fetchPhotos() async {   // сначала создаем URL, по которому   // мы будем делать запрос   final url = Uri.parse("$SERVER/photos");   // делаем GET запрос   final response = await http.get(url);    // проверяем статус ответа   if (response.statusCode == 200) {     // если все ок то возвращаем все картинки     // json.decode парсит ответ     return PhotoList.fromJson(json.decode(response.body));   } else {     // в противном случае вызываем исключение     throw Exception("failed request");   } }

Далее создаем новый контроллер AlbumController:

// AlbumController очень поход на PostController class AlbumController extends ControllerMVC {   final Repository repo = Repository();      // текущее состояние   PhotoResult currentState = PhotoResultLoading();      void init() async {     try {       // получение картинок       final photoList = await repo.fetchPhotos();       // успешно       setState(() => currentState = PhotoResultSuccess(photoList));     } catch (error) {       // произошла ошибка       setState(() => currentState = PhotoResultFailure("Нет интернета"));     }   }    }

Осталось только внести соответствующие изменения в AlbumListPage:

class AlbumListPage extends StatefulWidget {   @override   _AlbumListPageState createState() => _AlbumListPageState(); }  class _AlbumListPageState extends StateMVC {    // добавляем наш контроллер   // late указывает на отложенную инициализацию   late AlbumController _controller;    _AlbumListPageState() : super(AlbumController()){     _controller = controller as AlbumController;   }    @override   void initState() {     super.initState();     // получаем картинки из JSONPlaceholder     _controller.init();   }     @override   Widget build(BuildContext context) {     return Scaffold(         appBar: AppBar(           title: Text("Album List Page"),         ),         body: _buildContent()     );   }    Widget _buildContent() {     // получение текущего состояния     final state = _controller.currentState;     if (state is PhotoResultLoading) {       // загрузка       return Center(         child: CircularProgressIndicator(),       );     } else if (state is PhotoResultFailure) {       // ошибка       return Center(         child: Text(             state.error,             textAlign: TextAlign.center,             style: Theme.of(context).textTheme.headline4!.copyWith(color: Colors.red)         ),       );     } else {       final images = (state as PhotoResultSuccess).photoList.photos;       // мы используем StaggeredGridView для построения       // кастомной сетки из изображений       return StaggeredGridView.countBuilder(         // количество изображений         itemCount: images.length,         // crossAxisCount задает количество колонок         // по которым будут выравнены изображения         crossAxisCount: 8,         // отступы по вертикали         mainAxisSpacing: 10,         // отступы по горизонтали         crossAxisSpacing: 10,         staggeredTileBuilder: (index) {           // каждое изображение будет в ширину 4 колонки (первый параметр)           // изображения на четных индексах будут в 2 раза меньше (второй параметр)           return StaggeredTile.count(4, index % 2 == 0 ? 4 : 8);         },         // строим изображение         itemBuilder: (context, index) {           return Container(             decoration: BoxDecoration(                 border: Border.all(color: Colors.pinkAccent, width: 1)             ),             // мы используем метод Image.network для             // отображения картинок из сети             child:  Image.network(               images[index].url,               // указываем максимальную ширину и высоту               width: double.infinity,               height: double.infinity,               // указываем масштабирование изображения               fit: BoxFit.cover,               // при загрузки изображения                // будет показан текст Loading...               loadingBuilder: (context, widget, imageChunkEvent) {                 if (imageChunkEvent == null) {                   return widget;                 }                 return Center(child: Text("Loading..."));               },               // при возникновении ошибки                // вместо изображения будет текст Error!               errorBuilder: (context, obj, stacktrace) => Center(child: Text("Error!")),             ),           );         },        );     }   } }

Попробуем запустить.

Работает!

Заключение

Мы рассмотрели довольно базовые вещи, которые имеет Flutter для работы с картинками.

Image.network не является панацеей и поэтому в боевых проектах лучше использовать специальные библиотеки с большей функциональностью.

К одной из таких перспективных библиотек относится cached_network_image

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

Полезные ссылки:

Всем хорошего кода!

ссылка на оригинал статьи https://habr.com/ru/post/561614/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *