Во Flutter уже встроены несколько шейдеров, которые используются в классах LinearGradient, RadialGradient, SweepGradient, ImageShader и т.д. Данные шейдеры мы можем извлечь из этих объектов и использовать их в таких классах, как ShaderMask или CustomPaint. Но что если нужно создать уникальные градиенты, теневые эффекты или реалистичные анимации.
Можно создать собственный шейдер, сделав несколько манипуляций:
Чтобы создать шейдер, нам нужно написать код на GLSL. GLSL — это язык шейдеров высокого уровня, который используется для программирования графического процессора (GPU).
Давайте пропустим часть кодирования GLSL и выберем уже закодированный GLSL и изменим его, чтобы он работал с нашим приложением Flutter.
Создадим файл shader.frag и скопируем код:
https://www.shadertoy.com/view/fd33zn
Затем добавим в начало:
#include // импорт среды выполнения Flutter
uniform vec2 uSize; // универсальная переменная, в которой хранится размер визуализируемого объекта
uniform float iTime; // универсальная переменная, в которой хранится время, прошедшее с момента запуска шейдера
vec2 iResolution; // переменная, в которой хранится разрешение экрана
out vec4 fragColor; // выходная переменная, в которой хранится окончательный цвет визуализируемого объекта
Переименуем mainImage в main и заменим параметры на void.
Добавим две переменные внутри:
iResolution = uSize;
vec2 fragCoord = FlutterFragCoord();
Чтобы не писать много кода для шиммера я просто форкну flutter_shimmer. Создадим отдельный компонент, который будет принимать дочерний элемент и шейдер. Затем мы наложим полученный шейдер поверх него.
class ShimmerFromShader extends StatefulWidget {
final Widget child;
final FragmentShader shader;
Далее создаем SingleChildRenderObjectWidget для получения объекта рендеринга, принадлежащий дочернему виджету.
Сам рендер будет происходить в _ShimmerFilter, где мы создаем ShaderMaskLayer и на него будем накладывать наш шейдер. В настройках таймера можно поиграть со скоростью вращения.
class _ShimmerFilter extends RenderProxyBox {
FragmentShader shader;
double _time;
_ShimmerFilter(this.shader, this._time);
@override
ShaderMaskLayer? get layer => super.layer as ShaderMaskLayer?;
@override
bool get alwaysNeedsCompositing => child != null;
set time(double newValue) {
if (newValue == _time) {
return;
}
_time = newValue;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
assert(needsCompositing);
layer ??= ShaderMaskLayer();
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setFloat(2, _time);
layer!
..shader = shader
..maskRect = Offset.zero & size
..blendMode = BlendMode.srcIn;
context.pushLayer(layer!, super.paint, offset);
} else {
layer = null;
}
}
Добавьте шейдер в Flutter pubspec.yaml:
flutter:
shaders:
- shaders/shader.frag
Создаем объект шейдера:
Future loadMyShader() async {
final program = await FragmentProgram.fromAsset('shaders/shader.frag');
shader = program.fragmentShader();
return shader!;
}
И далее передаем в созданный ранее виджет, используя в качестве placeholderов виджеты из форкнутого пакета:
ShimmerFromShader.fromShader(
shader: snapshot.data!,
child: const SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
BannerPlaceholder(),
TitlePlaceholder(width: double.infinity),
SizedBox(height: 16.0),
ContentPlaceholder(
lineType: ContentLineType.threeLines,
),
SizedBox(height: 16.0),
TitlePlaceholder(width: 200.0),
SizedBox(height: 16.0),
ContentPlaceholder(
lineType: ContentLineType.twoLines,
),
SizedBox(height: 16.0),
TitlePlaceholder(width: 200.0),
SizedBox(height: 16.0),
ContentPlaceholder(
lineType: ContentLineType.twoLines,
),
],
),
));
Полный код можно посмотреть тут.
В мире Flutter шейдеры предоставляют захватывающие возможности для создания уникальных и впечатляющих пользовательских интерфейсов. Они могут стать мощным инструментом в ваших руках, наполняйте свои проекты индивидуальностью и выразительностью!
Переходите в наш телеграм-канал, там еще больше интересных и полезных новостей от нашей Flutter-команды.