├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example └── example.dart ├── lib └── countup.dart ├── pubspec.lock ├── pubspec.yaml └── test └── countup_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0af027f80543302c65f99e1c1a2f3b3cbb8d04f3 8 | channel: dev 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * First version of Countup. 4 | 5 | ## 0.0.2 6 | 7 | * README.md Updated. 8 | 9 | ## 0.0.3 10 | 11 | * Prefix prop added and README.md updated. 12 | 13 | ## 0.0.4 14 | 15 | * README.md hotfix. 16 | 17 | ## 0.0.5 18 | 19 | * Cleaning the code. 20 | 21 | ## 0.0.6 22 | 23 | * MIT License requirements. 24 | 25 | ## 0.1.0 26 | 27 | * Example added. 28 | 29 | ## 0.1.1 30 | 31 | * Bug fixed with the help of @sommestad. 32 | 33 | ## 0.1.2 34 | 35 | * fix(animation): ensure animation reset if values change 36 | 37 | ## 0.1.3 38 | 39 | * add suffix props 40 | 41 | ## 0.1.3 42 | 43 | * null safety 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alp Ceylan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/countup.svg)](https://pub.dev/packages/countup) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | 4 | 5 | # Flutter Countup 6 | 7 | A Flutter package that helps you create animated Text widgets for Android and IOS. Based by https://gitlab.com/kmcgill88/mccounting_text 8 | 9 | # Simple Usage 10 | 11 | You should give a begin, end and a duration value to Countup widget. If you want you can also give separator and style value. 12 | 13 | ```dart 14 | Countup( 15 | begin: 0, 16 | end: 7500, 17 | duration: Duration(seconds: 3), 18 | separator: ',', 19 | style: TextStyle( 20 | fontSize: 36, 21 | ), 22 | ) 23 | ``` 24 | 25 | 26 | !['flutter-countup-basic'](https://media0.giphy.com/media/dvfNHGdpm984wCYfKh/giphy.gif) 27 | 28 | ### Props 29 | 30 | #### `begin: double` 31 | 32 | #### `end: double` 33 | 34 | #### `separator: String` 35 | 36 | #### `duration: Duration` 37 | 38 | #### `style: TextStyle` 39 | 40 | #### `prefix: String` 41 | 42 | #### `suffix: String` 43 | 44 | #### `precision: int` 45 | 46 | #### `curve: Curve` 47 | 48 | #### `textAlign: TextAlign` 49 | 50 | #### `textDirection: TextDirection` 51 | 52 | #### `locale: Locale` 53 | 54 | #### `softWrap: bool` 55 | 56 | #### `overflow: TextOverflow` 57 | 58 | #### `textScaleFactor: double` 59 | 60 | #### `maxLines: int` 61 | 62 | #### `semanticsLabel: String` -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // OUR PACKAGE 4 | import 'package:countup/countup.dart'; 5 | 6 | void main() => runApp(MyApp()); 7 | 8 | class MyApp extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | title: 'Example', 13 | theme: ThemeData( 14 | primarySwatch: Colors.pink, 15 | ), 16 | home: Container( 17 | alignment: Alignment.center, 18 | child: Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | crossAxisAlignment: CrossAxisAlignment.center, 21 | children: [ 22 | Countup( 23 | begin: 0, 24 | end: 7500, 25 | duration: Duration(seconds: 3), 26 | separator: ',', 27 | style: TextStyle( 28 | fontSize: 36, 29 | ), 30 | ), 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/countup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class Countup extends StatefulWidget { 4 | final double begin; 5 | final double end; 6 | final int precision; 7 | final Curve curve; 8 | final Duration duration; 9 | final TextStyle? style; 10 | final TextAlign? textAlign; 11 | final TextDirection? textDirection; 12 | final Locale? locale; 13 | final bool? softWrap; 14 | final TextOverflow? overflow; 15 | final double? textScaleFactor; 16 | final int? maxLines; 17 | final String? semanticsLabel; 18 | final String? separator; 19 | final String prefix; 20 | final String suffix; 21 | 22 | Countup({ 23 | Key? key, 24 | required this.begin, 25 | required this.end, 26 | this.precision = 0, 27 | this.curve = Curves.linear, 28 | this.duration = const Duration(milliseconds: 250), 29 | this.style, 30 | this.textAlign, 31 | this.textDirection, 32 | this.locale, 33 | this.softWrap, 34 | this.overflow, 35 | this.textScaleFactor, 36 | this.maxLines, 37 | this.semanticsLabel, 38 | this.separator, 39 | this.prefix = '', 40 | this.suffix = '', 41 | }) : super(key: key); 42 | 43 | @override 44 | _CountupState createState() => _CountupState(); 45 | } 46 | 47 | class _CountupState extends State with TickerProviderStateMixin { 48 | late AnimationController _controller; 49 | late Animation _animation; 50 | double? _latestBegin; 51 | double? _latestEnd; 52 | 53 | @override 54 | void dispose() { 55 | _controller.dispose(); 56 | super.dispose(); 57 | } 58 | 59 | @override 60 | void initState() { 61 | super.initState(); 62 | _controller = AnimationController(duration: widget.duration, vsync: this); 63 | _latestBegin = widget.begin; 64 | _latestEnd = widget.end; 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | CurvedAnimation curvedAnimation = 70 | CurvedAnimation(parent: _controller, curve: widget.curve); 71 | _animation = Tween(begin: widget.begin, end: widget.end) 72 | .animate(curvedAnimation); 73 | 74 | if (widget.begin != _latestBegin || widget.end != _latestEnd) { 75 | _controller.reset(); 76 | } 77 | 78 | _latestBegin = widget.begin; 79 | _latestEnd = widget.end; 80 | _controller.forward(); 81 | 82 | return _CountupAnimatedText( 83 | key: UniqueKey(), 84 | animation: _animation, 85 | precision: widget.precision, 86 | style: widget.style, 87 | textAlign: widget.textAlign, 88 | textDirection: widget.textDirection, 89 | locale: widget.locale, 90 | softWrap: widget.softWrap, 91 | overflow: widget.overflow, 92 | textScaleFactor: widget.textScaleFactor, 93 | maxLines: widget.maxLines, 94 | semanticsLabel: widget.semanticsLabel, 95 | separator: widget.separator, 96 | prefix: widget.prefix, 97 | suffix: widget.suffix, 98 | ); 99 | } 100 | } 101 | 102 | class _CountupAnimatedText extends AnimatedWidget { 103 | final RegExp reg = new RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'); 104 | 105 | final Animation animation; 106 | final int precision; 107 | final TextStyle? style; 108 | final TextAlign? textAlign; 109 | final TextDirection? textDirection; 110 | final Locale? locale; 111 | final bool? softWrap; 112 | final TextOverflow? overflow; 113 | final double? textScaleFactor; 114 | final int? maxLines; 115 | final String? semanticsLabel; 116 | final String? separator; 117 | final String? prefix; 118 | final String? suffix; 119 | 120 | _CountupAnimatedText({ 121 | Key? key, 122 | required this.animation, 123 | required this.precision, 124 | this.style, 125 | this.textAlign, 126 | this.textDirection, 127 | this.locale, 128 | this.softWrap, 129 | this.overflow, 130 | this.textScaleFactor, 131 | this.maxLines, 132 | this.semanticsLabel, 133 | this.separator, 134 | this.prefix, 135 | this.suffix, 136 | }) : super(key: key, listenable: animation); 137 | 138 | @override 139 | Widget build(BuildContext context) => Text( 140 | separator != null 141 | ? '$prefix' + 142 | this 143 | .animation 144 | .value 145 | .toStringAsFixed(precision) 146 | .replaceAllMapped( 147 | reg, (Match match) => '${match[1]}$separator') + '$suffix' 148 | : '$prefix' + this.animation.value.toStringAsFixed(precision) + '$suffix', 149 | style: this.style, 150 | textAlign: this.textAlign, 151 | textDirection: this.textDirection, 152 | locale: this.locale, 153 | softWrap: this.softWrap, 154 | overflow: this.overflow, 155 | textScaleFactor: this.textScaleFactor, 156 | maxLines: this.maxLines, 157 | semanticsLabel: this.semanticsLabel, 158 | ); 159 | } 160 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.11" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.1.3" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.7.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.0" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.8.1" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.2.0" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.4.8" 138 | typed_data: 139 | dependency: transitive 140 | description: 141 | name: typed_data 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.3.0" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.1" 152 | sdks: 153 | dart: ">=2.14.0 <3.0.0" 154 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: countup 2 | description: An flutter plugin that will help you to build animated counter texts. 3 | homepage: https://github.com/alpceylan/flutter-countup 4 | version: 0.1.4 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | flutter: -------------------------------------------------------------------------------- /test/countup_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:countup/countup.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MaterialApp(home: Countup(begin: 0, end: 0,),)); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------