Reputation: 363
Information
I needed to apply typewriting animation to the texts on a website I was building with Flutter. I have successfully implemented this animation. For the animation to be applied, the value of the 'length' and 'style' properties of the text to be written to the screen and taken as a parameter must be known. Here is the widget I wrote:
// @dart=2.9
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class TypeWriterAnimatedText extends StatefulWidget {
TextSpan textSpan;
TypeWriterAnimatedText({Key key, @required this.textSpan}) : super(key: key);
@override
_TypeWriterAnimatedTextState createState() => _TypeWriterAnimatedTextState();
}
class _TypeWriterAnimatedTextState extends State<TypeWriterAnimatedText> with TickerProviderStateMixin {
Animation<double> _textBlinkCursorAnimation;
Animation<double> _textBlinkCursorAnimationCurve;
AnimationController _textBlinkCursorAnimationController;
bool _blink = true;
@override
void initState() {
super.initState();
_textBlinkCursorAnimationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 200));
_textBlinkCursorAnimationCurve = CurvedAnimation(
parent: _textBlinkCursorAnimationController,
curve: Curves.easeInOutQuart,
reverseCurve: Curves.linear);
_textBlinkCursorAnimation = Tween<double>(begin: 0, end: 1)
.animate(_textBlinkCursorAnimationCurve)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed && _blink == true) {
_textBlinkCursorAnimationController.reverse();
} else if (status == AnimationStatus.dismissed && _blink == true) {
_textBlinkCursorAnimationController.forward();
}
});
_textBlinkCursorAnimationController.forward();
}
@override
Widget build(BuildContext context) {
return Container(
height: 100,
child: TweenAnimationBuilder<int>(
tween: IntTween(begin: 0, end: widget.textSpan.text.length),
builder: (BuildContext context, int value, Widget child){
WidgetsBinding.instance.addPostFrameCallback((_){
if(value == widget.textSpan.text.length){
Future.delayed(Duration(milliseconds: 1800)).then((value){
setState(() {
_blink = false;
_textBlinkCursorAnimationController.reset();
});
});
}
});
return SelectableText.rich(
TextSpan(
children: [
widget.textSpan,
TextSpan(
text: _blink == false ? '' : '_',
style: widget.textSpan.style.copyWith(color: widget.textSpan.style.color.withOpacity(_textBlinkCursorAnimation.value)),
),
]),
);
},
duration: Duration(seconds: 2),
),
);
}
}
The Problem
But the widget I wrote to apply the animation needs to take TextSpan as a parameter in the constructor. Here, I come across a problem. TextSpans are in a nested structure. On the other hand, I need to find the 'length' sum of all 'text' properties, and 'style' of last TextSpan's 'text' property in the TextSpan that I take as a parameter. But since TextSpans are nested, some of them return null for the 'length' and 'style' getters (widget.textSpan.text.length
and widget.textSpan.style
). This is causing me to get an error.
What I Want To Do?
Even if the data I give to the Widget as a TextSpan contains nested TextSpans, I need to access the 'length' properties of all 'text's and find the sum of it. For example, I need to find the sum of the lengths of the 'Text1-1', 'Text1-2' and 'Text2' Strings, even if there is a data like here:
TextSpan(
children: [
TextSpan(
children: [
TextSpan(
text: 'Text1-1',
style: TextStyle(
fontSize: 40,
fontFamily: 'RobotoMono-Bold',
color: Colors.white),
),
TextSpan(
text: 'Text1-2',
style: TextStyle(
fontSize: 16,
fontFamily: 'RobotoMono-Bold',
color: Colors.white),
),
],
style: TextStyle(
fontSize: 40,
fontFamily: 'RobotoMono-Bold',
color: Colors.white),
),
TextSpan(
text: 'Text2',
style: TextStyle(
fontSize: 16,
fontFamily: 'RobotoMono-Bold',
color: Colors.white),
),
],
),
Upvotes: 2
Views: 248
Reputation: 10136
If you have a nested TextSpan
, you can call toPlainText
to render the full text into a String
:
final span = TextSpan(
children: [
TextSpan(text: 'hello'),
TextSpan(text: 'world'),
],
);
span.toPlainText() // returns 'helloworld'
Then you can call length
on that String
:
int totalLength(TextSpan span) => span.toPlainText().length;
For the style, you can use the visitor pattern, which Flutter uses extensively. You can call textSpan.visitChildren(visitor)
to walk the full tree and "visit" each child recursively.
For example, if you want to get the last style in the tree, you could use:
TextStyle finalStyle(TextSpan span) {
TextStyle? style;
bool visit(InlineSpan span) {
if (span.style != null) style = span.style; // if you find a style, keep a reference to it
return true; // continue through the tree
}
span.visitChildren(visit);
return style ?? (throw AssertionError('no styles found'));
}
You could also return a TextStyle?
instead of throwing if none are found, depends on your use case.
Upvotes: 2