2

I have this code which adds each character from a String to a Stream inside a timer creating this effect:

enter image description here

import 'dart:async';
import 'package:flutter/material.dart';

void main() async {
  runApp(
    MaterialApp(
      home: TextSpanStream(),
    ),
  );
}

class TextSpanStream extends StatefulWidget {
  TextSpanStream({Key? key}) : super(key: key);

  @override
  _TextSpanStreamState createState() => _TextSpanStreamState();
}

class _TextSpanStreamState extends State<TextSpanStream> {
  Timer? _timer;
  StreamController<String> _textShowController =
      StreamController<String>.broadcast();

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(seconds: 1), () => startBroadcast());
  }

  @override
  void dispose() {
    _textShowController.close();
    _timer?.cancel();
    super.dispose();
  }

  void startBroadcast() {
    String title = 'This is a title';
    int countLetter = 1;

    _timer = Timer.periodic(Duration(milliseconds: 100), (timer) {
      _textShowController.add(title.substring(0, countLetter));
      countLetter++;
      if (countLetter == title.length + 1) {
        timer.cancel();
        countLetter = 1;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.cyan[900],
      body: Center(
        child: Container(
          color: Colors.black26,
          width: 400,
          child: StreamBuilder<String>(
            stream: _textShowController.stream,
            builder: (context, snapshot) {
              return Text(
                snapshot.hasData ? (snapshot.data ?? '') : '',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 18,
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

The thing is, instead of a single String, I want to perform this with RichText, where each TextSpan comes from a List of TextSpan with unknown length. This way, each TextSpan would have its own different style and would be animated in sequence (one stream after another).

In example:

List<TextSpan> allSpans = [TextSpan(text: 'This is a ', style: TextStyle(color: Colors.white)), TextSpan(text: 'title', style: TextStyle(color: Colors.red))];

Is it possible to create a separate Stream for each TextSpan? Or is there a better approach?

1 Answer 1

2

You can create TextSpan and send it via stream to be built.

import 'dart:async';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rich Text Animation',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Rich Text Animation'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  StreamController<List<TextSpan>> _streamController =
      StreamController.broadcast();
  static const DELAY = Duration(milliseconds: 100); 

  @override
  void initState() {
    super.initState();
    _animate();
  }

  void _animate() {
    final _allSpans = [
      TextSpan(text: 'This is a ', style: TextStyle(color: Colors.white)),
      TextSpan(text: 'title', style: TextStyle(color: Colors.red)),
    ];
    Duration _delay = Duration(seconds: 1);
    _allSpans.forEach((span) {
      for (int i = 0; i < (span.text?.length ?? 0); i++) {
        Future.delayed(_delay, () {
          _streamController.add([
            ..._allSpans.sublist(0, _allSpans.indexOf(span)),
            TextSpan(text: span.text!.substring(0, i + 1), style: span.style)
          ]);
        });
        _delay += DELAY;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        color: Colors.green.shade700,
        child: Center(
          child: StreamBuilder<List<TextSpan>>(
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return RichText(
                  text: TextSpan(children: snapshot.data),
                );
              }
              return CircularProgressIndicator();
            },
            stream: _streamController.stream,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.animation),
        onPressed: () {
          _animate();
        },
      ),
    );
  }

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.