Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active August 4, 2021 12:52
Show Gist options
  • Save roipeker/1b405cafcd904318ec3dc1306ea43fd8 to your computer and use it in GitHub Desktop.
Save roipeker/1b405cafcd904318ec3dc1306ea43fd8 to your computer and use it in GitHub Desktop.
SliderText in the widget tree, shaders masks.
// roipeker 2020.
// Challenge from
// https://www.youtube.com/watch?v=HjJHO0NXI10&feature=share
//
// Demo screencast (macOS):
// https://giphy.com/gifs/WoG8316gEYY2EC1KRA
// Key code to use a shader mask in the widget tree:
//
// ShaderMask(
// shaderCallback: (bounds) => LinearGradient(
// colors: [color.withAlpha(0), color, color, color.withAlpha(0)],
// begin: Alignment.topCenter, end: Alignment.bottomCenter,
// ).createShader(bounds),
// child: ....
// Rest of the implementation is a simple Stack with internal widgets and a layout builder to take
// the available height to render the slideshow.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SampleSlideshow(),
);
}
}
final strings = <String>[
'Flutter is the best framework ever.',
'You should always push your code to Github',
'Shaders don\'t work on web, yet',
'Fluttery was awesome',
'SuperDeclarative! will be much better',
];
class SampleSlideshow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey.shade900,
body: Center(
child: Container(
height: 120,
color: Colors.lightBlue.withOpacity(.05),
child: TextSlideshow(
texts: strings,
fontSize: 20,
color: Colors.red[400].withOpacity(.8),
curve: Curves.easeInOut,
animationDuration: Duration(seconds: 1),
textInterval: Duration(milliseconds: 400),
),
),
),
);
}
}
class TextSlideshow extends StatelessWidget {
final List<String> texts;
final double fontSize;
final Color color;
final Curve curve;
final Duration animationDuration;
final Duration textInterval;
const TextSlideshow({
Key key,
@required this.texts,
this.fontSize = 15,
this.color = Colors.white,
this.curve = Curves.easeInOut,
this.animationDuration = const Duration(milliseconds: 800),
this.textInterval = const Duration(milliseconds: 300),
}) : super(key: key);
@override
Widget build(BuildContext context) {
final totalDuration = animationDuration + textInterval * 2;
final ratio = textInterval.inMilliseconds / totalDuration.inMilliseconds;
return LayoutBuilder(builder: (ctx, constrains) {
return ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
colors: [color.withAlpha(0), color, color, color.withAlpha(0)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
).createShader(bounds);
},
child: Container(
height: constrains.maxHeight,
width: double.infinity,
child: _AnimatedSlideshow(
texts: texts,
height: constrains.maxHeight,
animation: Interval(ratio, 1 - ratio, curve: curve),
totalDuration: totalDuration,
fontSize: fontSize,
color: color,
)),
);
});
}
}
class _AnimatedSlideshow extends StatefulWidget {
final double height;
final Interval animation;
final Duration totalDuration;
final double fontSize;
final Color color;
final List<String> texts;
const _AnimatedSlideshow(
{Key key,
@required this.texts,
this.height,
this.animation,
this.totalDuration,
this.fontSize,
this.color})
: super(key: key);
@override
_AnimatedSlideshowState createState() => _AnimatedSlideshowState();
}
class _AnimatedSlideshowState extends State<_AnimatedSlideshow>
with SingleTickerProviderStateMixin {
AnimationController _controller;
int currentTextIndex = 0;
String currentText;
String nextText;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: widget.totalDuration);
_controller.addListener(() {
setState(() {});
});
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_flipText();
_controller.forward(from: 0);
}
});
currentText = texts[0];
nextText = texts[1];
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double get height => widget.height;
List<String> get texts => widget.texts;
@override
Widget build(BuildContext context) {
final value = widget.animation.transform(_controller.value);
final pos1 = height * value;
final pos2 = pos1 - height;
return Stack(
fit: StackFit.loose,
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
children: [
_PositionedText(currentText,
top: pos1, height: height, fontSize: widget.fontSize),
_PositionedText(nextText,
top: pos2, height: height, fontSize: widget.fontSize),
],
);
}
void _flipText() {
if (++currentTextIndex >= texts.length) currentTextIndex = 0;
currentText = nextText ?? "";
nextText = texts[currentTextIndex];
}
}
class _PositionedText extends StatelessWidget {
final double top;
final double height;
final String text;
final double fontSize;
const _PositionedText(this.text,
{Key key, this.top, this.height, this.fontSize})
: super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
top: top,
child: Center(
child: Container(
height: height,
alignment: Alignment.center,
child: Text(
text,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: fontSize,
height: .9,
),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment