Flutter自定义着色器插件shader_presets的使用
Flutter自定义着色器插件shader_presets的使用
shader_presets
是一个Flutter包,它实现了一些现成可用的着色器,如来自 gl-transitions 的过渡效果和来自 ShaderToy 的效果。这个包使用了 shader_buffers
包来简化着色器的使用。目前有几个预设,并且可以很容易地添加更多。
入门指南
使用 shader_presets
插件非常简单,下面是一个基本的例子:
final presetController = ShaderPresetController();
ShaderPresetCube(
child1: child1, // children can be either the path to the asset image or a widget
child2: child2,
presetController: presetController,
progress: 0, // This is common to all gl-transitions
reflection: 1, // This is one parameter of this Cube shader
)
presetController
允许你获取/设置uniforms并获取着色器控制器。着色器控制器让你可以播放/暂停/倒带、添加条件操作以检查指针位置、获取其状态、交换纹理通道以及动画化自定义uniform值。
动画化自定义uniform
例如,你可以通过以下代码动画化一个名为 progress
的uniform:
presetController.getShaderController()!.animateUniform(
uniformName: 'progress',
begin: 0,
end: 1,
duration: const Duration(milliseconds: 600),
curve: Curves.decelerate,
onAnimationEnded: (ctrl, uniformValue) {
// handle animation end
},
);
可用预设及其参数
来自 ShaderToy | 参数 |
---|---|
ShaderPresetBarrel distortion |
|
ShaderPresetWater speed, frequency, amplitude |
|
ShaderPresetPageCurl radius |
来自 gl-transitions | 参数 |
---|---|
ShaderPresetCube progress, persp, unzoom, reflection, floating |
|
ShaderPresetPolkaDotsCurtain progress, dots, centerX, centerY |
|
ShaderPresetRadial progress, smoothness |
|
ShaderPresetFlyeye progress, size, zoom, colorSeparation |
示例Demo
这里提供了一个完整的示例demo,展示了如何在Flutter项目中使用 shader_presets
包。
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shader_buffers/shader_buffers.dart' show Uniforms;
import 'package:shader_preset_example/page1.dart';
import 'package:shader_preset_example/page2.dart';
import 'package:shader_presets/shader_presets.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shader Preset demo',
themeMode: ThemeMode.dark,
darkTheme: ThemeData.dark(),
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool useImages = false;
ValueNotifier<List<double>> floatUniforms = ValueNotifier([]);
ValueNotifier<ShaderPresetsEnum> preset = ValueNotifier(ShaderPresetsEnum.cube);
late ShaderPresetController presetController;
/// Get the preset min-max ranges and set its uniform to the starting range
late Uniforms uniforms;
@override
void initState() {
super.initState();
presetController = ShaderPresetController();
}
@override
Widget build(BuildContext context) {
final Widget page = Padding(
padding: const EdgeInsets.all(32),
child: ValueListenableBuilder(
valueListenable: preset,
builder: (_, p, __) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: createPreset(p),
),
const Spacer(),
const Divider(),
uniformSliders(),
Wrap(
spacing: 4,
runSpacing: 4,
children: List.generate(
ShaderPresetsEnum.values.length,
(index) {
return ElevatedButton(
onPressed: () {
preset.value = ShaderPresetsEnum.values[index];
},
child: Text(ShaderPresetsEnum.values[index].name),
);
},
),
),
const SizedBox(height: 8),
Row(
children: [
ElevatedButton(
onPressed: () {
if (!useImages) {
if (context.mounted) {
setState(() {
useImages = true;
});
}
}
},
child: const Text('use images'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: () {
if (useImages) {
if (context.mounted) {
setState(() {
useImages = false;
});
}
}
},
child: const Text('use widgets'),
),
],
),
],
);
},
),
);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Shader Presets demo'),
),
body: kIsWeb
? Center(
child: AspectRatio(
aspectRatio: 2 / 3,
child: page,
),
)
: page,
);
}
Widget createPreset(ShaderPresetsEnum p) {
uniforms = presetController.getDefaultUniforms(p);
final dynamic child1 = useImages ? 'assets/flutter.png' : const Page1();
final dynamic child2 = useImages ? 'assets/dash.png' : const Page2();
Widget ret;
switch (p) {
case ShaderPresetsEnum.water:
ret = ShaderPresetWater(
child: child1,
presetController: presetController,
);
case ShaderPresetsEnum.pageCurl:
ret = ShaderPresetPageCurl(
child1: child1,
child2: child2,
presetController: presetController,
);
case ShaderPresetsEnum.barrel:
ret = ShaderPresetBarrel(
presetController: presetController,
child: Page1(
onScrolling: (scrollingVelocity) {
presetController.setUniform(0, scrollingVelocity);
},
),
);
case ShaderPresetsEnum.cube:
ret = ShaderPresetCube(
child1: child1,
child2: child2,
presetController: presetController,
);
case ShaderPresetsEnum.polkaDotsCurtain:
ret = ShaderPresetPolkaDotsCurtain(
child1: child1,
child2: child2,
presetController: presetController,
);
case ShaderPresetsEnum.radial:
ret = ShaderPresetRadial(
child1: child1,
child2: child2,
presetController: presetController,
);
case ShaderPresetsEnum.flyeye:
ret = ShaderPresetFlyeye(
child1: child1,
child2: child2,
presetController: presetController,
);
}
return ret;
}
/// Creates a column with a slider for each preset uniforms
Widget uniformSliders() {
/// Build the uniforms list
floatUniforms = ValueNotifier(
List.generate(uniforms.uniforms.length, (index) {
return uniforms.uniforms[index].value;
}),
);
/// Build slider for each uniforms
return ValueListenableBuilder(
valueListenable: floatUniforms,
builder: (_, uniform, __) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
uniforms.uniforms.length,
(index) {
return Flex(
direction: Axis.horizontal,
children: [
Text('${uniforms.uniforms[index].name} \t\t'
'${uniform[index].toStringAsFixed(2)}'),
Expanded(
child: Slider(
value: uniform[index],
min: uniforms.uniforms[index].range.start,
max: uniforms.uniforms[index].range.end,
onChanged: (value) {
floatUniforms.value[index] = value;
floatUniforms.value = floatUniforms.value.toList();
presetController.setUniforms(floatUniforms.value);
},
),
),
/// Button to animate the uniform
IconButton(
onPressed: () {
presetController.getShaderController()!.animateUniform(
uniformName: uniforms.uniforms[index].name,
begin: uniforms.uniforms[index].range.start,
end: uniforms.uniforms[index].range.end,
onAnimationEnded: (ctrl, uniformValue) {
/// Just update sliders
Future.delayed(Duration.zero, () {
floatUniforms.value[index] = uniformValue;
floatUniforms.value =
floatUniforms.value.toList();
});
},
);
},
icon: const Icon(Icons.animation),
),
],
);
},
),
);
},
);
}
}
这个示例展示了如何创建一个包含多个着色器预设的应用程序,并允许用户通过滑块调整每个预设的uniform参数,还可以选择使用图像或小部件作为着色器的输入。
贡献
如果你想为 shader_presets
添加新的预设,可以参考文档中的“Adding a preset”部分进行操作。
更多关于Flutter自定义着色器插件shader_presets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter自定义着色器插件shader_presets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter中使用自定义着色器插件 shader_presets
的一个代码示例。这个示例将展示如何集成并使用该插件来创建一个简单的自定义着色器效果。
首先,确保你已经在 pubspec.yaml
文件中添加了 shader_presets
依赖:
dependencies:
flutter:
sdk: flutter
shader_presets: ^最新版本号 # 请替换为实际最新版本号
然后运行 flutter pub get
来安装依赖。
接下来,在你的 Flutter 项目中创建一个自定义着色器效果。以下是一个简单的示例,展示如何在 CustomPaint
小部件中使用 shader_presets
插件:
import 'package:flutter/material.dart';
import 'package:shader_presets/shader_presets.dart'; // 导入shader_presets包
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Shader Presets Example'),
),
body: Center(
child: CustomPaint(
size: Size(double.infinity, double.infinity),
painter: MyCustomShaderPainter(),
),
),
),
);
}
}
class MyCustomShaderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint();
final Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
// 使用预设着色器,例如简单的波浪着色器
final Shader shader = createWaveShader(
rect.size,
colors: [Colors.blue, Colors.lightBlueAccent],
waveHeight: 20.0,
waveLength: 100.0,
direction: Alignment.topLeft,
);
paint.shader = shader;
canvas.drawRect(rect, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false; // 在这个例子中,着色器不依赖于任何状态,因此不需要重绘
}
}
在这个示例中,我们创建了一个 CustomPaint
小部件,并在其中使用了一个自定义的 MyCustomShaderPainter
类。该类重写了 paint
方法,并在其中创建了一个简单的波浪着色器。
请注意,createWaveShader
函数是假设 shader_presets
插件提供的预设函数之一(实际使用时,请查阅 shader_presets
的文档以获取正确的函数名和参数)。如果 shader_presets
没有直接提供 createWaveShader
函数,你可能需要根据插件提供的API自己实现波浪效果。
由于 shader_presets
的实际API可能会有所不同,这里只是一个概念性的示例。你需要根据 shader_presets
的具体文档来调整代码。如果 shader_presets
插件提供了自定义着色器的创建方法,你通常会使用类似的方式来创建和应用着色器。
确保查阅 shader_presets
的最新文档和示例代码,以获取最准确和最新的使用指南。