
Один из наиболее популярных способов сделать элемент UI выразительным и аутентичным состоит в том, чтобы заполнить его картинкой, градиентом или анимированной гифкой.
Ниже приведен пример реализации данных эффектов на Flutter. Представленный подход будет работать с любым виджетом на всех поддерживаемых платформах. В качестве примера мы будем заполнять графикой Text.
План
Реализация будет идти по сценарию, предложенному командой разработчиков Flutter в серии видео "Widget of the Week".
Основные шаги для заполнения текста графикой:
Создать TextChild виджет для отображения текста.
Создать Shader с нашей кастомной графикой.
Применить Shader к TextChild с помощью ShaderMask.
Widget buildBeautifulText() { // 1. Create text child final textChild = TextChild(); // 2. Create shader final shaderCallback = createShader(); // 3. Apply shader to text child return ShaderMask( blendMode: BlendMode.srcIn, shaderCallback: shaderCallback, child: textChild, ); }
Кейс 1. Шейдер градиента

Заполнить виджет градиентом достаточно легко, и при удачном применении данного эффекта можно получить весьма впечатляющие результаты. Для реализации нам потребуется описать желаемый градиент и попросить его создать шейдер. Полученный шейдер можно сразу применить к дочернему виджету, используя ShaderMask.
// Create gradient shader ShaderCallback gradientShader() { // Define linear gradient const gradient = LinearGradient( colors: [ Colors.red, Colors.blue, ], begin: Alignment.topLeft, end: Alignment.bottomRight, tileMode: TileMode.mirror, ); // Create shader final shaderCallback = gradient.createShader; return shaderCallback; } // Build text with gradient shader Widget buildBeautifulText() { // 1. Create text child final textChild = TextChild(); // 2. Create shader callback final shaderCallback = gradientShader(); // 3. Apply shader to text child return ShaderMask( blendMode: BlendMode.srcIn, shaderCallback: shaderCallback, child: textChild, ); }
В примере мы использовали линейный градиент от красного к синему. Процесс можно также повторить с градиентами любого типа (Linear, Radial, Sweep).
Полный код для данного кейса можно посмотреть и запустить через DartPad.
Кейс 2. Шейдер изображения

В Flutter есть класс ImageShader, который позволяет создавать шейдеры для изображений. Для этого необходимо передать ему данные изображения.
Шаг 1. Создать ImageProvider
Мы будем использовать ImageProvider, чтобы получить данные изображен��я из любого доступного источника (сеть, assets, файл, память). ImageProvider поддерживает форматы JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP и WBMP.
// 1. Create ImageProvider // JPEG const jpegProvider = NetworkImage( 'https://picsum.photos/1000', ); // Animated GIF const gifProvider = NetworkImage( 'https://media.giphy.com/media/5VKbvrjxpVJCM/giphy.gif', );
Шаг 2. Создать ImageShader для изображения
ImageProvider поставляет нам экземпляры класса Image из пакета dart:ui, которые мы можем передать в ImageShader, чтобы создать ShaderCallback. Данный колбэк создает шейдер с учетом границ поверхности, к которой он будет применен.
Мы заполним графикой весь прямоугольник. В этом процессе будем использовать преобразования матрицы Matrix4, чтобы изменить размер и отцентровать изображение.
Можно пойти альтернативным путем и вместо изменений матрицы изменить TileMode. Режимы mirror и repeat должны хорошо себя проявить при работе с повторяющимися узорами.
Код этого шага можно изменить, чтобы настроить сочетания Matrix4 и TileMode для реализации своего уникального дизайна.
// 2. Create image shader for given Rect size ShaderCallback createImageShader(ui.Image image) { shaderCalback(Rect bounds) { // Calculate scale for X and Y sides final scaleX = bounds.width / image.width; final scaleY = bounds.height / image.height; final scale = max(scaleX, scaleY); // Calculate offset to center resized image final scaledImageWidth = image.width * scale; final sacledImageHeight = image.height * scale; final offset = Offset( (scaledImageWidth - bounds.width) / 2, (sacledImageHeight - bounds.height) / 2, ); final matrix = Matrix4.identity() // Scale image ..scale(scale, scale) // Center horizontally and vertically ..leftTranslate( -offset.dx, -offset.dy, ); // Image shader return ImageShader( image, TileMode.decal, TileMode.decal, matrix.storage, ); }
Шаг 3. Подписаться на ImageStream
При использовании ImageProvider у нас есть возможность подписаться на ImageStream, который отслеживает текущий кадр изображения. Подписка на данный поток позволит нам реагировать на изменения изображения, которые происходят в результате анимаций или изменений исходного ресурса изображения.
Документация Flutter уже содержит код использования ImageStream в виджете. Нам остается лишь изменить его метод build и добавить поле child для дочернего виджета.
Добавляем поле child:
// See MyImage class from Flutter docs // https://api.flutter.dev/flutter/painting/ImageProvider-class.html class ImageShaderBuilder extends StatefulWidget { const ImageShaderBuilder({ super.key, required this.imageProvider, // Add child widget required this.child, }); // Add child widget final Widget child; final ImageProvider imageProvider; @override State<ImageShaderBuilder> createState() => _ImageShaderBuilderState(); }
Изменяем метод build:
// See MyImage class from Flutter docs // https://api.flutter.dev/flutter/painting/ImageProvider-class.html class _ImageShaderBuilderState extends State<ImageShaderBuilder> { // Keep the source code // Change only build method @override Widget build(BuildContext context) { final image = _imageInfo?.image; // No image for shader -> show child if (image == null) { return widget.child; } final shaderCallback = createImageShader(image); // Apply shader to the child return ShaderMask( blendMode: BlendMode.srcIn, shaderCallback: shaderCallback, child: widget.child, ); } }
Шаг 4. Использовать шейдер изображения
Теперь мы можем использовать ImageShaderBuilder для реализации ярких и запоминающихся пользовательских интерфейсов.
Widget buildBeautifulText() { // 1. Create text child const textChild = TextChild(); // 2. Create ImageProvider const imageProvider = NetworkImage( 'https://media.giphy.com/media/5VKbvrjxpVJCM/giphy.gif', ); // 3. Apply shader to text child return const ImageShaderBuilder( imageProvider: imageProvider, child: textChild, ); }
Полный код для данного кейса можно посмотреть и запустить через DartPad.
Заключение
Благодарю за чтение!
Не стесняйтесь писать внизу свои мысли, предложения и пожелания по поводу данной статьи. Я буду рад учесть их в своих следующих публикациях, а также отвечу на любые вопросы в комментариях.
Ставьте лайки, если статья оказалась полезна.
Об авторе