
Один из наиболее популярных способов сделать элемент 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.
Заключение
Благодарю за чтение!
Не стесняйтесь писать внизу свои мысли, предложения и пожелания по поводу данной статьи. Я буду рад учесть их в своих следующих публикациях, а также отвечу на любые вопросы в комментариях.
Ставьте лайки, если статья оказалась полезна.
Об авторе
