Last active
August 4, 2021 12:52
-
-
Save roipeker/1b405cafcd904318ec3dc1306ea43fd8 to your computer and use it in GitHub Desktop.
SliderText in the widget tree, shaders masks.
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
// 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', | |
]; | |
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
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), | |
), | |
), | |
), | |
); | |
} | |
} |
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
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