Created
September 7, 2023 09:03
-
-
Save codebk1/b53d756106fba714988b8b0413ada632 to your computer and use it in GitHub Desktop.
Flutter Text Glitch Effect
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const GlitchApp()); | |
} | |
class GlitchApp extends StatelessWidget { | |
const GlitchApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp( | |
debugShowCheckedModeBanner: false, | |
home: Scaffold( | |
backgroundColor: Color.fromARGB(255, 0, 0, 0), | |
body: Center( | |
child: Glitch( | |
text: 'GLITCH', | |
textStyle: TextStyle( | |
color: Color.fromARGB(255, 255, 255, 255), | |
fontSize: 100, | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
class Glitch extends StatefulWidget { | |
const Glitch({ | |
super.key, | |
required this.text, | |
this.textStyle, | |
this.stacks = 3, | |
this.duration = const Duration(milliseconds: 100), | |
this.delay = const Duration(milliseconds: 1500), | |
this.glitchColor1 = const Color.fromARGB(255, 255, 0, 0), | |
this.glitchColor2 = const Color.fromARGB(255, 0, 0, 255), | |
}); | |
final String text; | |
final TextStyle? textStyle; | |
final int stacks; | |
final Duration duration; | |
final Duration delay; | |
final Color glitchColor1; | |
final Color glitchColor2; | |
@override | |
State<Glitch> createState() => _GlitchState(); | |
} | |
class _GlitchState extends State<Glitch> with SingleTickerProviderStateMixin { | |
@override | |
Widget build(BuildContext context) { | |
return _Glitch( | |
text: widget.text, | |
textStyle: widget.textStyle, | |
stacks: widget.stacks, | |
duration: widget.duration, | |
delay: widget.delay, | |
glitchColor1: widget.glitchColor1, | |
glitchColor2: widget.glitchColor2, | |
vsync: this, | |
); | |
} | |
} | |
class _Glitch extends LeafRenderObjectWidget { | |
const _Glitch({ | |
required this.text, | |
this.textStyle, | |
required this.stacks, | |
required this.duration, | |
required this.delay, | |
required this.glitchColor1, | |
required this.glitchColor2, | |
required this.vsync, | |
}); | |
final String text; | |
final TextStyle? textStyle; | |
final int stacks; | |
final Duration duration; | |
final Duration delay; | |
final Color glitchColor1; | |
final Color glitchColor2; | |
final TickerProvider vsync; | |
@override | |
RenderGlitch createRenderObject(BuildContext context) { | |
return RenderGlitch( | |
text: text, | |
textStyle: textStyle ?? DefaultTextStyle.of(context).style, | |
stacks: stacks, | |
duration: duration, | |
delay: delay, | |
glitchColor1: glitchColor1, | |
glitchColor2: glitchColor2, | |
vsync: vsync, | |
); | |
} | |
@override | |
void updateRenderObject( | |
BuildContext context, | |
RenderGlitch renderObject, | |
) { | |
renderObject | |
..text = text | |
..textStyle = textStyle ?? DefaultTextStyle.of(context).style | |
..stacks = stacks | |
..duration = duration | |
..delay = delay | |
..glitchColor1 = glitchColor1 | |
..glitchColor2 = glitchColor2 | |
..vsync = vsync; | |
} | |
} | |
class RenderGlitch extends RenderBox { | |
RenderGlitch({ | |
required String text, | |
required TextStyle textStyle, | |
required int stacks, | |
required Duration duration, | |
required Duration delay, | |
required Color glitchColor1, | |
required Color glitchColor2, | |
required TickerProvider vsync, | |
}) : assert(delay.inMilliseconds >= 2 * duration.inMilliseconds), | |
_text = text, | |
_textStyle = textStyle, | |
_stacks = stacks, | |
_duration = duration, | |
_delay = delay, | |
_glitchColor1 = glitchColor1, | |
_glitchColor2 = glitchColor2, | |
_vsync = vsync { | |
_controller = AnimationController( | |
vsync: _vsync, | |
duration: _duration, | |
) | |
..forward() | |
..addListener(() { | |
final value = _controller.value; | |
textPainter = TextPainter( | |
text: TextSpan( | |
text: _text, | |
style: _textStyle.merge( | |
TextStyle( | |
height: 1, | |
shadows: [ | |
Shadow( | |
color: _glitchColor1, | |
offset: Offset(value * -1, value * 2), | |
), | |
Shadow( | |
color: _glitchColor2, | |
offset: Offset(value * 1, value * -2), | |
), | |
], | |
), | |
), | |
), | |
textDirection: TextDirection.ltr, | |
); | |
if (_controller.isCompleted) { | |
_controller.reverse(); | |
Future.delayed(_delay, () { | |
_controller.forward(); | |
}); | |
} | |
}); | |
_animation = Tween<Offset>( | |
begin: Offset.zero, | |
end: const Offset(2, 0), | |
).animate( | |
CurvedAnimation( | |
parent: _controller, | |
curve: Curves.elasticOut, | |
), | |
); | |
_textPainter = TextPainter( | |
text: TextSpan( | |
text: _text, | |
style: _textStyle, | |
), | |
textDirection: TextDirection.ltr, | |
); | |
} | |
late final AnimationController _controller; | |
late final Animation _animation; | |
late TextPainter _textPainter; | |
TextPainter get textPainter => _textPainter; | |
set textPainter(TextPainter value) { | |
if (value == _textPainter) return; | |
_textPainter = value; | |
markNeedsLayout(); | |
} | |
late String _text; | |
String get text => _text; | |
set text(String value) { | |
if (value == _text) return; | |
_text = value; | |
markNeedsLayout(); | |
} | |
late TextStyle _textStyle; | |
TextStyle get textStyle => _textStyle; | |
set textStyle(TextStyle value) { | |
if (value == _textStyle) return; | |
_textStyle = value; | |
markNeedsPaint(); | |
} | |
int _stacks; | |
int get stacks => _stacks; | |
set stacks(int value) { | |
if (value == _stacks) return; | |
_stacks = value; | |
markNeedsPaint(); | |
} | |
Duration _duration; | |
Duration get duration => _duration; | |
set duration(Duration value) { | |
if (value == _duration) return; | |
_duration = value; | |
} | |
Duration _delay; | |
Duration get delay => _delay; | |
set delay(Duration value) { | |
if (value == _delay) return; | |
_delay = value; | |
} | |
Color _glitchColor1; | |
Color get glitchColor1 => _glitchColor1; | |
set glitchColor1(Color value) { | |
if (value == _glitchColor1) return; | |
_glitchColor1 = value; | |
} | |
Color _glitchColor2; | |
Color get glitchColor2 => _glitchColor2; | |
set glitchColor2(Color value) { | |
if (value == _glitchColor2) return; | |
_glitchColor2 = value; | |
} | |
TickerProvider _vsync; | |
TickerProvider get vsync => _vsync; | |
set vsync(TickerProvider value) { | |
if (value == _vsync) return; | |
_vsync = value; | |
_controller.resync(vsync); | |
} | |
@override | |
void detach() { | |
_controller.stop(); | |
super.detach(); | |
} | |
@override | |
void performLayout() { | |
_textPainter.layout(); | |
size = Size(_textPainter.width, _textPainter.height); | |
} | |
@override | |
bool get isRepaintBoundary => true; | |
@override | |
void paint(PaintingContext context, Offset offset) { | |
final canvas = context.canvas; | |
final height = _textPainter.height / _stacks; | |
for (var i = 0; i < _stacks; i++) { | |
canvas.save(); | |
canvas.clipRect( | |
Rect.fromLTRB(0, height * i - 0.5, size.width, height * (i + 1) + 0.5), | |
); | |
_textPainter.paint( | |
canvas, | |
i.isEven ? -_animation.value : _animation.value, | |
); | |
canvas.restore(); | |
} | |
} | |
} |
Author
codebk1
commented
Sep 7, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment