Skip to content

Instantly share code, notes, and snippets.

@bluemix
Created October 26, 2021 14:08
Show Gist options
  • Save bluemix/4c932e8c4a1cd6f497a4353d9e536f57 to your computer and use it in GitHub Desktop.
Save bluemix/4c932e8c4a1cd6f497a4353d9e536f57 to your computer and use it in GitHub Desktop.
Animated Counter Text for Flutter
class AnimatedCount extends ImplicitlyAnimatedWidget {
AnimatedCount({
Key? key,
required this.count,
Duration duration = const Duration(milliseconds: 600),
Curve curve = Curves.fastOutSlowIn,
}) : super(duration: duration, curve: curve, key: key);
final num count;
@override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
return _AnimatedCountState();
}
}
class _AnimatedCountState extends AnimatedWidgetBaseState<AnimatedCount> {
IntTween _intCount = IntTween(begin: 0, end: 1);
Tween<double> _doubleCount = Tween<double>();
@override
Widget build(BuildContext context) {
return widget.count is int
? Text(_intCount.evaluate(animation).toString())
: Text(_doubleCount.evaluate(animation).toStringAsFixed(1));
}
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
if (widget.count is int) {
_intCount = visitor(
_intCount,
widget.count,
(dynamic value) => IntTween(begin: value),
) as IntTween;
} else {
_doubleCount = visitor(
_doubleCount,
widget.count,
(dynamic value) => Tween<double>(begin: value),
) as Tween<double>;
}
}
}
@WizardingStudios
Copy link

I have an exception here:

_doubleCount = visitor(

Exception:

Exception has occurred. _CastError (type 'Null' is not a subtype of type 'double' in type cast)

Only when I use doubles. Integers working as expected.

@tchex
Copy link

tchex commented Dec 10, 2022

Hello. Same here. Did you find a workaround?

I have an exception here:

_doubleCount = visitor(

Exception:

Exception has occurred. _CastError (type 'Null' is not a subtype of type 'double' in type cast)

Only when I use doubles. Integers working as expected.

@tchex
Copy link

tchex commented Dec 10, 2022

I changed this line:
Tween<double> _doubleCount = Tween<double>();

To:
Tween<double> _doubleCount = Tween<double>(begin: 0, end: 1);

and it seems to be ok

@AndyMo905
Copy link

Would there be a way to change the duration based on the number value delta? For example, having the duration at 2000ms when the number is changing from 0 to 100, vs having the duration at 300ms when the number is changing from 0-5 ?

@kazukisugita
Copy link

This is so cool

@Muhammed-Rahif
Copy link

Another issue I have faced that it not animating in first render. to fix that I have added the initState:

import 'package:flutter/material.dart';

class AnimatedCount extends ImplicitlyAnimatedWidget {
  const AnimatedCount({
    Key? key,
    required this.count,
    this.style,
    Duration duration = const Duration(milliseconds: 600),
    Curve curve = Curves.fastOutSlowIn,
    this.prefix = '',
    this.suffix = '',
  }) : super(duration: duration, curve: curve, key: key);

  final num count;
  final String prefix;
  final String suffix;
  final TextStyle? style;

  @override
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
    return _AnimatedCountState();
  }
}

class _AnimatedCountState extends AnimatedWidgetBaseState<AnimatedCount> {
  IntTween _intCount = IntTween(begin: 0, end: 1);
  Tween<double> _doubleCount = Tween<double>(begin: 0, end: 1);

  @override
  void initState() {
    super.initState();
    if (widget.count is int) {
      _intCount = IntTween(begin: 0, end: widget.count.toInt());
    } else {
      _doubleCount = Tween<double>(begin: 0, end: widget.count.toDouble());
    }
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    final String text;
    if (widget.count is int) {
      final countStr = _intCount.evaluate(animation).toString();
      text = '${widget.prefix}$countStr${widget.suffix}';
    } else {
      final countStr = _doubleCount.evaluate(animation).toStringAsFixed(1);
      text = '${widget.prefix}$countStr${widget.suffix}';
    }

    return Text(text, style: widget.style);
  }

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    if (widget.count is int) {
      _intCount = visitor(
        _intCount,
        widget.count,
        (dynamic value) => IntTween(begin: value),
      ) as IntTween;
    } else {
      _doubleCount = visitor(
        _doubleCount,
        widget.count,
        (dynamic value) => Tween<double>(begin: value),
      ) as Tween<double>;
    }
  }
}

Extra: added suffix and prefix arguments.

@blackorbs-dev
Copy link

Another issue I have faced that it not animating in first render. to fix that I have added the initState:

import 'package:flutter/material.dart';

class AnimatedCount extends ImplicitlyAnimatedWidget {
  const AnimatedCount({
    Key? key,
    required this.count,
    this.style,
    Duration duration = const Duration(milliseconds: 600),
    Curve curve = Curves.fastOutSlowIn,
    this.prefix = '',
    this.suffix = '',
  }) : super(duration: duration, curve: curve, key: key);

  final num count;
  final String prefix;
  final String suffix;
  final TextStyle? style;

  @override
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
    return _AnimatedCountState();
  }
}

class _AnimatedCountState extends AnimatedWidgetBaseState<AnimatedCount> {
  IntTween _intCount = IntTween(begin: 0, end: 1);
  Tween<double> _doubleCount = Tween<double>(begin: 0, end: 1);

  @override
  void initState() {
    super.initState();
    if (widget.count is int) {
      _intCount = IntTween(begin: 0, end: widget.count.toInt());
    } else {
      _doubleCount = Tween<double>(begin: 0, end: widget.count.toDouble());
    }
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    final String text;
    if (widget.count is int) {
      final countStr = _intCount.evaluate(animation).toString();
      text = '${widget.prefix}$countStr${widget.suffix}';
    } else {
      final countStr = _doubleCount.evaluate(animation).toStringAsFixed(1);
      text = '${widget.prefix}$countStr${widget.suffix}';
    }

    return Text(text, style: widget.style);
  }

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    if (widget.count is int) {
      _intCount = visitor(
        _intCount,
        widget.count,
        (dynamic value) => IntTween(begin: value),
      ) as IntTween;
    } else {
      _doubleCount = visitor(
        _doubleCount,
        widget.count,
        (dynamic value) => Tween<double>(begin: value),
      ) as Tween<double>;
    }
  }
}

Extra: added suffix and prefix arguments.

This works for me, thanks 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment