Skip to content

Commit 4e86581

Browse files
committed
feat: add Distorted Motion shader and integrate into shader list
1 parent fa89608 commit 4e86581

File tree

5 files changed

+181
-0
lines changed

5 files changed

+181
-0
lines changed
1010 KB
Loading
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_shaders/flutter_shaders.dart';
4+
import 'dart:ui';
5+
import 'shader_builder.dart';
6+
import 'package:flutter/scheduler.dart';
7+
8+
class DistortedMotionShaderBuilder extends CustomShaderBuilder {
9+
const DistortedMotionShaderBuilder();
10+
11+
@override
12+
bool get requiresImageSampler => true;
13+
14+
@override
15+
Duration? get animationDuration => null; // Unbounded animation
16+
17+
@override
18+
void setUniforms(FragmentShader shader, Size size, double time) {
19+
shader
20+
..setFloat(0, size.width)
21+
..setFloat(1, size.height);
22+
}
23+
24+
@override
25+
Widget buildShader(
26+
ShaderMetadata metadata,
27+
FragmentShader shader,
28+
Size size,
29+
double time,
30+
Widget? child,
31+
) {
32+
return _ShaderWidget(shader: shader);
33+
}
34+
35+
@override
36+
Widget? childBuilder(BuildContext context) {
37+
return null;
38+
}
39+
}
40+
41+
class _ShaderWidget extends StatefulWidget {
42+
final FragmentShader shader;
43+
44+
const _ShaderWidget({required this.shader});
45+
46+
@override
47+
State<_ShaderWidget> createState() => _ShaderWidgetState();
48+
}
49+
50+
class _ShaderWidgetState extends State<_ShaderWidget> with TickerProviderStateMixin {
51+
final scrollController = ScrollController();
52+
final ValueNotifier<double> _velocity = ValueNotifier(0);
53+
Ticker? _ticker;
54+
double _lastPosition = 0.0;
55+
double _smoothedVelocity = 0.0;
56+
static const double alpha = 0.1; // Smoothing factor
57+
int _seed = 0;
58+
59+
@override
60+
void initState() {
61+
super.initState();
62+
_seed = DateTime.now().second;
63+
widget.shader.setFloat(3, 0.0); // Initial velocity y
64+
_ticker = createTicker((_) {
65+
if (scrollController.hasClients) {
66+
final newPosition = scrollController.position.pixels;
67+
final velocity = newPosition - _lastPosition;
68+
_smoothedVelocity = (alpha * velocity) + (1.0 - alpha) * _smoothedVelocity;
69+
_lastPosition = newPosition;
70+
_velocity.value = _smoothedVelocity;
71+
widget.shader.setFloat(3, _smoothedVelocity);
72+
}
73+
});
74+
_ticker?.start();
75+
}
76+
77+
@override
78+
void dispose() {
79+
_ticker?.dispose();
80+
scrollController.dispose();
81+
super.dispose();
82+
}
83+
84+
@override
85+
Widget build(BuildContext context) {
86+
var listView = NotificationListener<ScrollNotification>(
87+
onNotification: (notification) {
88+
if (notification is ScrollUpdateNotification) {
89+
// The velocity is calculated in the ticker for smoothness.
90+
}
91+
return true;
92+
},
93+
child: ListView.builder(
94+
controller: scrollController,
95+
padding: const EdgeInsets.symmetric(horizontal: 32),
96+
97+
itemBuilder: (context, index) {
98+
return Padding(
99+
padding: const EdgeInsets.symmetric(vertical: 8.0),
100+
child: Container(
101+
height: 200,
102+
width: double.infinity,
103+
decoration: BoxDecoration(
104+
color: index.isEven ? Colors.blue : Colors.red,
105+
borderRadius: BorderRadius.circular(8),
106+
image: DecorationImage(
107+
image: NetworkImage('https://picsum.photos/id/${index % 5 + _seed}/600/200'),
108+
fit: BoxFit.cover,
109+
),
110+
),
111+
// child: Image.network(
112+
// 'https://picsum.photos/id/${index % 5 + _seed}/600/200',
113+
// fit: BoxFit.cover,
114+
// ),
115+
),
116+
);
117+
},
118+
),
119+
);
120+
return Stack(
121+
children: [
122+
AnimatedSampler(
123+
(image, size, canvas) {
124+
widget.shader.setImageSampler(0, image);
125+
canvas.drawRect(
126+
Rect.fromLTWH(0, 0, size.width, size.height),
127+
Paint()..shader = widget.shader,
128+
);
129+
},
130+
child: listView,
131+
),
132+
Positioned(
133+
bottom: 16,
134+
left: 16,
135+
child: ValueListenableBuilder(
136+
valueListenable: _velocity,
137+
builder: (context, value, child) {
138+
return Text('Velocity: ${value.toStringAsFixed(2)}');
139+
},
140+
),
141+
),
142+
],
143+
);
144+
}
145+
}

lib/main.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:shaders/widgets/widgets.dart';
99
import 'package:shaders/meta/meta_tag_manager.dart';
1010

1111
import 'common_shader_builder.dart';
12+
import 'distorted_motion_shader_builder.dart';
1213
import 'shader_builder.dart';
1314
import 'crt_shader_builder.dart';
1415
import 'noise_overlay_shader_builder.dart';
@@ -160,6 +161,17 @@ final shaders = [
160161
backgroundColor: Colors.black,
161162
aspectRatio: 1,
162163
),
164+
// glitch.frag
165+
ShaderInfo(
166+
assetKey: 'shaders/distorted_motion.frag',
167+
name: 'Distorted Motion',
168+
description: 'Distorts the UI with a blur while scrolling',
169+
sourceUrl: 'https://fluttershaders.com/shaders/distorted-motion-blur/',
170+
author: '@raoufrahiche',
171+
dateAdded: DateTime(2025, 8, 21),
172+
builder: const DistortedMotionShaderBuilder(),
173+
path: 'distorted-motion',
174+
),
163175
];
164176

165177
// Helper function to create URL-safe shader names

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ flutter:
5252
- shaders/ai_assistant.frag
5353
- shaders/branded_ai_assistant.frag
5454
- shaders/breathing_point.frag
55+
- shaders/distorted_motion.frag

shaders/distorted_motion.frag

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# version 460 core
2+
# include <flutter/runtime_effect.glsl>
3+
4+
uniform vec2 u_size;
5+
uniform vec2 u_velocity;
6+
uniform sampler2D u_texture;
7+
8+
out vec4 frag_color;
9+
10+
float q = 24.0;
11+
12+
void main() {
13+
vec2 p = FlutterFragCoord().xy / u_size ;
14+
vec2 v = u_velocity/ u_size / 2;
15+
16+
float o = -pow(p.y * 2 - 1, 6) + 1;
17+
vec4 new_p = vec4(0);
18+
for (int i = 0; i < 24; i++) {
19+
new_p += texture(u_texture, vec2(p.x + (( (o - 1) * length(v.y) * 6 * ((p.x - 0.5))) ) + (v.x * i) / q, p.y + 0.01 + (v.y * i) / q));
20+
}
21+
new_p /= q;
22+
frag_color = new_p;
23+
}

0 commit comments

Comments
 (0)