Анимация загрузки картинок во Flutter, или как сделать shimmer своими руками

Hola, Amigos! На связи Ярослав Цемко, Flutter dev продуктового агентства Amiga. Я участвую в разработке мобильного приложения Bravo — e-com проект по продаже карнавальной продукции, воздушных шаров и товаров для праздника. Это одна из крупнейших в России и в мире компаний в данной отрасли. 

Сегодня расскажу про решение, которое делает анимацию загрузки картинок вместо пустого экрана или троббера (вращающееся колечко).

Но вначале хочу поделиться с вами телеграм-каналом Flutter. Много, где мы с командой рассказываем интересные случаи из практики и обсуждаем с другими мобильными разработчиками свой опыт. Нас 1750, присоединяйтесь)

ebf12558edb8f401f6ac1df8cb47c1d1.png

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

На docs.flutter.dev есть пример реализации.

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

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

У него есть параметр shimmerGradient в котором мы задаем цвет градиента, например:

LinearGradient shimmerGradient = LinearGradient(
 colors: [
   Color(0xFFEBEBF4),
   Color(0xFFF4F4F4),
   Color(0xFFEBEBF4),
 ],
 stops: [
   0.1,
   0.3,
   0.4,
 ],
 begin: Alignment(-1.0, -0.3),
 end: Alignment(1.0, 0.3),
 tileMode: TileMode.clamp,
);

Затем можно использовать на каждом элементе, где нам нужно использовать shimmer c помощью ShimmerLoading. 

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

Существует также плагин cached_network_image.

Его можно использовать в связке с плагином cached_network_image, но не все так просто.

Если у нас cached_network_image, то отследить загрузку становится затруднительно, потому что у этого виджета нет параметра, где мы могли бы проверить статус загрузки. В данном конкретном случае это не подойдет, однако у cached_network_image есть параметр placeholder, который отображает заданный в этом параметре виджет, пока загружается изображение. Мы можем туда передать виджет ShimmerLoading, с параметром isLoading равном true.

return CachedNetworkImage(
 imageUrl: widget.imageUrl,
 placeholder: (context, url) {
   return ShimmerLoading(
       isLoading: true,
       child: Container(
         decoration: const BoxDecoration(
           color: Colors.white,
         ),
       ));
 },
);

Однако при запуске без изменений у нас выскакивает следующая ошибка

The following _TypeError was thrown building ShimmerLoading (dirty, state: _ShimmerLoadingState#b0084):

Null check operator used on a null value

if (!widget.isLoading) {
 return widget.child;
}


// Вот здесь выскакивает ошибка.


final offsetWithinShimmer = shimmer.getDescendantOffset(
 descendant: context.findRenderObject() as RenderBox,
);

Для того, чтобы все заработало, нужно добавить проверку на наличие виджета и renderObject в контексте

if (!widget.isLoading
   || context.findRenderObject() == null
   || Shimmer.of(context) == null) {
 return widget.child;
}

И тогда все начинает работать поскольку в случае если renderObject не находится, то просто возвращается дочерний виджет. Проблема в cached_network_image заключается в том, что в момент, когда изображение загружается, то когда происходит замена с placeholder на изображение, которое нужно загрузить, пропадает на секунду этот самый RenderObject и это вызывает ошибку. Тем не менее с помощью этого небольшого исправления все начинает работать.

Пример можно посмотреть по ссылке.

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

© Habrahabr.ru