A simple widget for animating a set of images with full custom controls as an alternative to using a GIF file.

Overview

image_sequence_animator

Cosmos Software

Pub License

A simple widget for animating a set of images with full custom controls as an alternative to using a GIF file.

If you have a GIF file you would like to use with this package, I recommend EZGIF to convert your GIF file to an image sequence.

It is highly recommended to read the documentation and run the example project on a real device to fully understand and inspect the full range of capabilities.

Media | Description | How-to-Use

Recent

  • [isOnline] is added. If your [folderName] is an online path, this value should be set to true.

  • [waitUntilCacheIsComplete] is added. If you want the [ImageSequenceAnimator] to wait until the entire image sequence is cached, this value should be set to true i. Otherwise, the [ImageSequenceAnimator] will invoke [onReadyToPlay] and start playing if [isAutoPlay] is set to true when it approximates that the remaining caching can be completed without causing stutters. This value is only used if [isOnline] is set to true.

  • [cacheProgressIndicatorBuilder] is added. If you want to display a widget until the [ImageSequenceAnimator] is ready to be played, use this function.


Media

Watch on Youtube:

Image Sequence Animator

Description

This simple widget for animating a set of images (a.k.a an image sequence) with full custom controls as an alternative to using a GIF file.

GIF files are, as far as I know, not possible to control. With this package, you will have full control over your image sequence like controlling a video. You can loop, boomerang, change the color, play, pause, stop, skip, rewind, restart and more.

How-to-Use

First, add your image sequence to your assets and update the "pubspec.yaml" accordingly.

Then create an ImageSequenceAnimator widget as shown in the example:

ImageSequenceAnimator(
  "assets/ImageSequences/MyImageSequence",  //folderName 
  "Frame_",                                 //fileName
  0,                                        //suffixStart
  5,                                        //suffixCount 
  "png",                                    //fileFormat 
  60,                                       //frameCount
 {Key key,
  fps               : 60,
  isLooping         : false,
  isBoomerang       : false,
  isAutoPlay        : true,
  color             : Colors.white,
  onReadyToPlay     : _onReadyToPlay,
  onStartPlaying    : _onStartPlaying,
  onPlaying         : _onPlaying,
  onFinishPlaying   : _onFinishPlaying})

ImageSequenceAnimator(
  "https://www.domain.com/ImageSequences/MyImageSequence",  //folderName 
  "Frame_",                                                 //fileName
  0,                                                        //suffixStart
  5,                                                        //suffixCount 
  "png",                                                    //fileFormat 
  60,                                                       //frameCount
 {Key key,
  fps               :               60,
  isLooping         :               false,
  isBoomerang       :               false,
  isAutoPlay:                       true,
  isOnline:                         true,
  waitUntilCacheIsComplete:         true,
  cacheProgressIndicatorBuilder:    _cacheProgressIndicatorBuilder,
  color             :               Colors.white,
  onReadyToPlay     :               _onReadyToPlay,
  onStartPlaying    :               _onStartPlaying,
  onPlaying         :               _onPlaying,
  onFinishPlaying   :               _onFinishPlaying})
  
Widget _cacheProgressIndicatorBuilder(BuildContext _context , double _progress);  
void   _onReadyToPlay(ImageSequenceAnimatorState _imageSequenceAnimator);
void   _onStartPlaying(ImageSequenceAnimatorState _imageSequenceAnimator);
void   _onPlaying(ImageSequenceAnimatorState _imageSequenceAnimator);
void   _onFinishPlaying(ImageSequenceAnimatorState _imageSequenceAnimator); 

Further Explanations:

For a complete set of descriptions for all parameters and methods, see the documentation.

  • [isLooping] will override [isBoomerang] if both are set to true.
  • All [ImageSequenceProcessCallback] callbacks will return a reference to the created [ImageSequenceAnimator] state. You can save this instance for further actions.
  • Use [ImageSequenceAnimatorState]'s [void setIsLooping(bool isLooping)], [void setIsBoomerang(bool isBoomerang)], [void setColor(Color color)], [void play({double from: -1.0})], [void rewind({double from: -1.0})], [void pause()], [void skip(double value, {double percentage: -1.0})], [void restart()], [void stop()] methods for the corresponding actions.
  • Use [ImageSequenceAnimatorState]'s [bool get isLooping], [bool get isBoomerang], [double get currentProgress], [double get totalProgress], [double get currentTime] and [double get totalTime] methods to get the respective values.

Notes

I started using and learning Flutter only some weeks ago so this package might have some parts that don't make sense, that should be completely different, that could be much better, etc. Please let me know! Nicely!

Any help, suggestion or criticism is appreciated!

Cheers.





Comments
  • Using image sequence animator with Image.network

    Using image sequence animator with Image.network

    I really love this plugin, Im using it to create a "stop motion" from the lasted saved webcam screens. However these screens are all stored online and ofcourse updated every X minute.

    I would really love to use this plugin, where I can give a set of network images, instead of giving a location for my image assets.

    opened by roderikpeeters 10
  • Animation not working on splash screen

    Animation not working on splash screen

    I have been using this library for some time now, and it worked great. But recently I tried to add animation to splash screen - initial screen opens as splash screen and when animation ends I open another screen.

    Animation works well when I run my app from computer. However, when I close and open the app from device, only single image is being shown and there is no animation. What can be the reason for this? Should I wait for some initialization?

    opened by Zeynal7 4
  • Support for random named images

    Support for random named images

    I love this package, its exactly what I need. But I have images in firebase storage where the names are generated and don't follow the naming format you have implemented. Is there a possibility for an implementation where the frames can be looped from a list of urls instead?

    opened by jslattery26 3
  • How to control the speed of my animation?

    How to control the speed of my animation?

    My animation is running to fast, how can I control the speed of my animation?

    ImageSequenceAnimator(
                                    "assets/animation",
                                    "frame_",
                                    0,
                                    0,
                                    "png",
                                    22,
                                  ),
    
    opened by d-apps 1
  • Why is Color white chosen as default?

    Why is Color white chosen as default?

    I have been struggling to find the reason why my sequence is not playing and nothing is shown. Later I realized that my background is white, and default color is also white, so I was not seeing anything. The default should just be null. Is there any reason that white is chosen?

    opened by Zeynal7 1
  • Many pictures will make it impossible to display

    Many pictures will make it impossible to display

    Many pictures will make it impossible to display

    issue code branch【image-sequence-animator】

    Flutter issue

    issue code

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    import 'package:spring_button/spring_button.dart';
    import 'package:image_sequence_animator/image_sequence_animator.dart';
    
    class HomePage extends StatefulWidget {
      HomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      ImageSequenceAnimatorState imageSequenceAnimator;
      bool wasPlaying = false;
      Color color1 = Colors.greenAccent;
      Color color2 = Colors.indigo;
      String loopText = "Start Loop";
      String boomerangText = "Start Boomerang";
    
      void onReadyToPlay(ImageSequenceAnimatorState _imageSequenceAnimator) {
        imageSequenceAnimator = _imageSequenceAnimator;
      }
    
      void onPlaying(ImageSequenceAnimatorState _imageSequenceAnimator) {
        setState(() {});
      }
    
      Widget row(String text, Color color) {
        return Padding(
          padding: EdgeInsets.all(3.125),
          child: Container(
            decoration: BoxDecoration(
              color: color,
              borderRadius: const BorderRadius.all(const Radius.circular(10.0)),
            ),
            child: Center(
              child: Text(
                text,
                style: const TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                  fontSize: 12.5,
                ),
              ),
            ),
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Column(
            children: [
              Expanded(
                flex: 4,
                child: Padding(
                  padding: EdgeInsets.all(25),
                  child: ImageSequenceAnimator(
                    "assets/images/side",
                    "side_",
                    0,
                    0,
                    "jpg",
                    60,
                    isAutoPlay: false,
                    color: null,
                    onReadyToPlay: onReadyToPlay,
                    onPlaying: onPlaying,
                  ),
                ),
              ),
              Expanded(
                child: Row(
                  children: [
                    Expanded(
                      flex: 4,
                      child: CupertinoSlider(
                        value: imageSequenceAnimator == null ? 0.0 : imageSequenceAnimator.animationController.value,
                        min: imageSequenceAnimator == null ? 0.0 : imageSequenceAnimator.animationController.lowerBound,
                        max: imageSequenceAnimator == null ? 100.0 : imageSequenceAnimator.animationController.upperBound,
                        onChangeStart: (double value) {
                          wasPlaying = imageSequenceAnimator.animationController.isAnimating;
                          imageSequenceAnimator.pause();
                        },
                        onChanged: (double value) {
                          imageSequenceAnimator.skip(value);
                        },
                        onChangeEnd: (double value) {
                          if (wasPlaying) imageSequenceAnimator.play();
                        },
                      ),
                    ),
                    Expanded(
                      child: Center(
                        child: Text(
                          imageSequenceAnimator == null ? "0.0" : ((imageSequenceAnimator.currentTime.floor()).toString() + "/" + (imageSequenceAnimator.totalTime.floor()).toString()),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              Expanded(
                child: Row(
                  children: [
                    Expanded(
                      child: SpringButton(
                        SpringButtonType.OnlyScale,
                        row(
                          loopText,
                          Colors.cyan,
                        ),
                        useCache: false,
                        onTap: () {
                          setState(() {
                            loopText = imageSequenceAnimator.isLooping ? "Start Loop" : "Stop Loop";
                            boomerangText = "Start Boomerang";
                            imageSequenceAnimator.setIsLooping(!imageSequenceAnimator.isLooping);
                          });
                        },
                      ),
                    ),
                    Expanded(
                      child: SpringButton(
                        SpringButtonType.OnlyScale,
                        row(
                          boomerangText,
                          Colors.deepPurpleAccent,
                        ),
                        useCache: false,
                        onTap: () {
                          setState(() {
                            loopText = "Start Loop";
                            boomerangText = imageSequenceAnimator.isBoomerang ? "Start Boomerang" : "Stop Boomerang";
                            imageSequenceAnimator.setIsBoomerang(!imageSequenceAnimator.isBoomerang);
                          });
                        },
                      ),
                    ),
                  ],
                ),
              ),
              Expanded(
                child: SpringButton(
                  SpringButtonType.OnlyScale,
                  row(
                    "Change Colour",
                    Colors.redAccent,
                  ),
                  useCache: false,
                  onTap: () {
                    imageSequenceAnimator.changeColor(imageSequenceAnimator.color == color1 ? color2 : color1);
                  },
                ),
              ),
              Expanded(
                child: Row(
                  children: [
                    Expanded(
                      child: SpringButton(
                        SpringButtonType.OnlyScale,
                        row(
                          "Play/Pause",
                          Colors.deepOrangeAccent,
                        ),
                        useCache: false,
                        onTap: () {
                          setState(() {
                            imageSequenceAnimator.animationController.isAnimating ? imageSequenceAnimator.pause() : imageSequenceAnimator.play();
                          });
                        },
                      ),
                    ),
                    Expanded(
                      child: SpringButton(
                        SpringButtonType.OnlyScale,
                        row(
                          "Stop",
                          Colors.green,
                        ),
                        useCache: false,
                        onTap: () {
                          imageSequenceAnimator.stop();
                        },
                      ),
                    ),
                  ],
                ),
              ),
              Expanded(
                child: Row(
                  children: [
                    Expanded(
                      child: SpringButton(
                        SpringButtonType.OnlyScale,
                        row(
                          "Restart",
                          Colors.teal,
                        ),
                        useCache: false,
                        onTap: () {
                          imageSequenceAnimator.restart();
                        },
                      ),
                    ),
                    Expanded(
                      child: SpringButton(
                        SpringButtonType.OnlyScale,
                        row(
                          "Rewind",
                          Colors.indigoAccent,
                        ),
                        useCache: false,
                        onTap: () {
                          imageSequenceAnimator.rewind();
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    
    opened by jing-pei 1
  • How to handle the digits of suffix in image name?

    How to handle the digits of suffix in image name?

    ImageSequenceAnimator(
                        "assets/img/fraisier/AnimatedImage1", //folderName
                        "R19I1-", //fileName
                        1, //suffixStart
                        13, //suffixCount
                        "jpg", //fileFormat
                        60, //frameCount
                        isAutoPlay: true,
                      )
    

    My Images names look like this,

    images/R19I1-01.jpg images/R19I1-02.jpg

    Unable to load asset: assets/img/fraisier/AnimatedImage1/R19I1-0000000000001.jpg I am facing this issue! Can you please tell me how to handle the digits of the name? Or is there any fix?

    opened by balaji101010 1
  • Pause and Unpause

    Pause and Unpause

    Hi, I feel a bit stupid, but I can't get the animation to pause and unpause. Could you provide a simple implementation for this? imgSequence.createState().pause(); dose not seem to work.` Thank you.

    opened by llorenzo 0
  • Is it still working on Flutter 3.0.2?

    Is it still working on Flutter 3.0.2?

    I'm new to Flutter but I get HttpException: Invalid statusCode: 403.

    ImageSequenceAnimator(
            'https://cdn2.doki1001.com/wp-content/uploads/WP-manga/data/manga_6149d88d66b2b/db5198634b8b7c462f3668bc3e72e3fb',
            '',
            0,
            2,
            'jpg',
            10,
            key: const Key('online'),
            fps: 3,
            isAutoPlay: true,
            isOnline: true,
            onReadyToPlay: onOnlineReadyToPlay,
            onPlaying: onOnlinePlaying,
          ),
    
    opened by farmerswalker 0
  • Null error on reopening the activity

    Null error on reopening the activity

    The animation works fine when I start the app. After I press back button and open the activity again I get Null error.

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

    import 'package:flutter/cupertino.dart';

    import 'package:image_sequence_animator/image_sequence_animator.dart';

    class Image_Sequence_2 extends StatefulWidget { Image_Sequence_2() : super();

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

    class CarouselHistory extends State<Image_Sequence_2> { ImageSequenceAnimatorState? get imageSequenceAnimator => offlineImageSequenceAnimator; ImageSequenceAnimatorState? offlineImageSequenceAnimator;

    bool wasPlaying = false;

    bool _useFullPaths = true; List? _fullPathsOffline;

    void onOfflineReadyToPlay(ImageSequenceAnimatorState _imageSequenceAnimator) { offlineImageSequenceAnimator = _imageSequenceAnimator; }

    void onOfflinePlaying(ImageSequenceAnimatorState _imageSequenceAnimator) { setState(() {}); }

    @override void dispose() { super.dispose(); }

    @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey, appBar: AppBar( backgroundColor: Colors.black54, title: const Text("Demo", style: TextStyle(fontSize: 25.0, color: Colors.teal)), centerTitle: true, ), body: Container( color: Colors.black54, height: double.infinity, alignment: Alignment.topCenter, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Divider( height: 20, thickness: 5, endIndent: 0, color: Colors.black, ), Container( alignment: Alignment.center, child: ImageSequenceAnimator( "https://www.noorderlichtapp.nl", "aurora_", 1, 1, "jpg", 8, key: Key("online"), fps: 15, isAutoPlay: false, isOnline: true, waitUntilCacheIsComplete: true, cacheProgressIndicatorBuilder: (context, progress) { return CircularProgressIndicator( value: progress, ); }, fullPaths: _useFullPaths ? _fullPathsOffline : null, onReadyToPlay: onOfflineReadyToPlay, onPlaying: onOfflinePlaying, ), ), Container( alignment: Alignment.center, child: CupertinoSlider( value: imageSequenceAnimator == null ? 0.0 : imageSequenceAnimator!.currentProgress, min: 0.0, max: imageSequenceAnimator == null ? 100.0 : imageSequenceAnimator!.totalProgress, onChangeStart: (double value) { wasPlaying = imageSequenceAnimator!.isPlaying; imageSequenceAnimator!.pause(); }, onChanged: (double value) { imageSequenceAnimator!.skip(value); }, onChangeEnd: (double value) { if (wasPlaying) imageSequenceAnimator!.play(); }, ), ), const Divider( height: 20, thickness: 5, endIndent: 0, color: Colors.black, ), ], ), ), )); } } `

    opened by bharathreddyh 0
  • onFinishPlaying is being called twice

    onFinishPlaying is being called twice

    I am having a problem on my app where the init method is being called twice, after some tests I realized that onFinishPlyaing is being called twice, could you help me with that? Thank you.

    ImageSequenceAnimator(
                                    "assets/animation",
                                    "frame_",
                                    0,
                                    0,
                                    "png",
                                    22,
                                    fps: 20,
                                    onFinishPlaying: (imageSequenceAnimatorState){
    
                                      print("onFinishPlaying");
                                      splashController.init();
    
                                    },
                                  ),
    
    onFinishPlaying // it prints twice
    

    flutter doctor -v

    [✓] Flutter (Channel stable, 2.5.3, on macOS 11.6 20G165 darwin-arm, locale en-BR) • Flutter version 2.5.3 at /Users/djalma/development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 18116933e7 (7 weeks ago), 2021-10-15 10:46:35 -0700 • Engine revision d3ea636dc5 • Dart version 2.14.4

    [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at /Users/djalma/Library/Android/sdk • Platform android-31, build-tools 31.0.0 • ANDROID_HOME = /Users/djalma/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189) • All Android licenses accepted.

    [✓] Xcode - develop for iOS and macOS • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 13.1, Build version 13A1030d • CocoaPods version 1.10.2

    [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

    [✓] Android Studio (version 2020.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)

    [✓] Connected device (3 available) • iPad Pro (9.7-inch) (mobile) • 0C8AB907-97CE-4FD7-B4EC-A38A33242FDC • ios • com.apple.CoreSimulator.SimRuntime.iOS-15-0 (simulator) • macOS (desktop) • macos • darwin-arm64 • macOS 11.6 20G165 darwin-arm • Chrome (web) • chrome • web-javascript • Google Chrome 96.0.4664.55

    • No issues found!

    opened by d-apps 2
  • [Feature Request] Parameter for number of times to play the animation

    [Feature Request] Parameter for number of times to play the animation

    Need configuration like sequence plays as boomerang but not in the loop and only once.

    Planned to use callbacks onPlaying and onFinishPlaying to manually stop it after 1 iteration but does not seem to be a nice solution.

    Also, the issue is these callbacks are fired for every frame/image. So, onPlaying is called 64 times when I have 60 images.

    ImageSequenceAnimator(
                    "assets/images/newScrollSprites", //folderName
                    "", //fileName
                    1, //suffixStart
                    1, //suffixCount
                    "png", //fileFormat
                    60, //frameCount
                    // fps: 60,
                    isBoomerang: true,
                    isLooping: false,
                    isAutoPlay: false,
                    onReadyToPlay: onOfflineReadyToPlay,
                    onPlaying: onOfflinePlaying,
                    onFinishPlaying: onFinishPlaying,
     )
    
    

    Parameter like "animationIterations" which respects boomerang property would help.

    opened by uzumakinaruto123 0
Owner
AliYigitBireroglu
"It's true that we love one another, I love programming like a little brother."
AliYigitBireroglu
🛍 A full E-commerce app with nice UI consists of on-boarding, login, sign-up, home, product details, cart and user profile.

?? A full E-commerce app with nice UI consists of on-boarding, login, sign-up, home, product details, cart and user profile.

null 56 Nov 27, 2022
Custom widget for Flutter

Flushbar Use this package if you need more customization when notifying your user. For Android developers, it is made to substitute toasts and snackba

Andre Haueisen 899 Dec 30, 2022
A set of interesting and fun UI components, animations and routes for Flutter.

すごい ・ Sugoi A set of interesting and fun UI components, animations and routes for Flutter. By Jay (Jeroen) Meijer. This project was bootstrapped by Ve

Jeroen Meijer (Jay) 5 Jan 22, 2022
A set of high-level Flutter UI components and stand-alone widgets for easier implementation of most used UI features

A set of high-level Flutter UI components and stand-alone widgets for easier implementation of most used UI features

Evgeny Cherkasov 37 Jul 25, 2022
A flutter package which makes it easier to display the difference between two images.

?? Before After A flutter package which makes it easier to display the differences between two images.. The source code is 100% Dart, and everything r

Sahil Kumar 741 Dec 30, 2022
A vector tile renderer for use in creating map tile images or writing to a canvas. Written in Dart to enable use of vector tiles with Flutter.

vector_tile_renderer A vector tile renderer for use in creating map tile images or writing to a canvas. Written in Dart to enable use of vector tiles

David Green 30 Oct 7, 2022
🔁 A custom refresh indicator for flutter.

Liquid Pull To Refresh A beautiful and custom refresh indicator for flutter highly inspired from Ramotion Pull Down to Refresh. Table of contents Inst

Ayush Agarwal 1.1k Jan 8, 2023
A Splash screen with curved custom bottom sheet and dots indicator within it.

Pub.dev Curved Splash Screen A Splash screen with curved custom bottom sheet and dots indicator within it. You can add your custom splash screens acco

Hosain Mohamed 16 Dec 1, 2022
this repo is testing the custom painter by changing according to slider value. It was intended to be used for volume control in audio player.

custom_paint_demo Trying a widget called custom paint Getting Started This project is a starting point for a Flutter application. A few resources to g

Edy 7 Jan 16, 2022
A customizable timer builder, with controller, animation, intervals, callbacks and custom actions for Flutter.

Custom Timer ⌛ A highly customizable timer builder, with controller, animation, intervals, callbacks, custom actions, and more! ?? Simple Usage @overr

Federico De Sía 27 Nov 23, 2022
A custom is strong dropdown menu for Flutter

A custom is strong dropdown menu for Flutter. Easy to use and powerful for customization, it's up to you what you want to display in the dropdown menu!

干志雄 662 Dec 26, 2022
Movies App UI in Flutter using Simple Widgets without Using API's in Flutter.

Movies App UI in Flutter using Simple Widgets without Using API's in Flutter.

Habib ullah 3 May 15, 2022
A simple animated radial menu widget for Flutter.

flutter_radial_menu A radial menu widget for Flutter. . Installation Install the latest version from pub. Quick Start Import the package, create a Rad

Victor Choueiri 434 Jan 7, 2023
A Flutter Widget Approach for using HTML tags & CSS styles in your upcoming Apps.

html_widgets A Flutter Widget Approach for using HTML tags & CSS styles in your upcoming Apps. Text Widgets *text property is required for all the tex

XenonLabz 7 Jul 14, 2022
A simple Flutter component capable of using JSON Schema to declaratively build and customize web forms.

A simple Flutter component capable of using JSON Schema to declaratively build and customize web forms.

null 3 Oct 2, 2022
ID-Card - A Simple ID Card Project Using Flutter

ID-CARD A new Flutter project.A simple demonstration of my time in the Dart lang

edwromero 0 Jan 26, 2022
flutter stepper_touch widget

stepper_touch the concept of the widget inspired from Nikolay Kuchkarov. i extended the functionality to be more useful in real world applications Tha

Raouf Rahiche 271 Dec 30, 2022
A TypeAhead widget for Flutter, where you can show suggestions to users as they type

Flutter TypeAhead A TypeAhead (autocomplete) widget for Flutter, where you can show suggestions to users as they type Features Shows suggestions in an

null 661 Jan 5, 2023
A highly customisable Flutter widget for entering pin code. Suitable for use cases such as login and OTP.

pin_code_text_field It's a beautiful and highly customizable Flutter widget for entering pin code. Suitable for use cases such as login and OTP. Usage

Liew Jun Tung 309 Dec 28, 2022