Search code examples
flutterflutter-animationflutter-containerflutter-opacity

Flutter: keep old value until implicit animation is done


I have this code:

AnimatedOpacity(
  duration: Duration(seconds: 1),
  opacity: _text == 'hide text'? 0 : 1,
  child: Text(_text),
)

What is the easiest way to show old value of text (eg not 'hide text', but whatever is there before the text changes to this) for the duration of fading the text out?


Solution

  • You can create your own Widget to do what you need. I created a sample for you:

    Result

    enter image description here

    Explanation

    First, I created this code, where the main value is "show text", when the user press the button, the value will be updated to "hide text" and the animation will start.

    There is a function fadeBuilder that you can use to validate when you want to hide the text, in this sample the condition to hide the text is value == 'hide text'

    class _SampleViewState extends State<SampleView> {
      String _myText = 'show text';
    
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyAnimatedTextOpacity(
              value: _myText,
              fadeBuilder: (value) => value == 'hide text',
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _myText = 'hide text';
                });
              },
              child: const Text('change text'),
            )
          ],
        );
      }
    }
    

    Now this is the custom widget:

    typedef FadeBuilder = bool Function(String value);
    
    class MyAnimatedTextOpacity extends StatefulWidget {
      const MyAnimatedTextOpacity({
        required this.value,
        required this.fadeBuilder,
        super.key,
      });
    
      final String value;
      final FadeBuilder fadeBuilder;
    
      @override
      State<MyAnimatedTextOpacity> createState() => _MyAnimatedTextOpacityState();
    }
    
    class _MyAnimatedTextOpacityState extends State<MyAnimatedTextOpacity> {
      late final String _value = widget.value;
    
      @override
      Widget build(BuildContext context) {
        return AnimatedOpacity(
          duration: const Duration(seconds: 1),
          opacity: widget.fadeBuilder(widget.value) ? 0 : 1,
          child: Text(_value),
        );
      }
    }
    
    

    A new sample based on your requirements:

    Result

    enter image description here

    Code:

    class BadgesDemo extends StatefulWidget {
      const BadgesDemo({super.key});
    
      @override
      State<BadgesDemo> createState() => _BadgesDemoState();
    }
    
    class _BadgesDemoState extends State<BadgesDemo> {
      int? badgeValue;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Badges Demo'),
          ),
          body: Center(
            child: BadgeWidget(
              value: badgeValue?.toString(),
              child: const Icon(Icons.phone),
            ),
          ),
          floatingActionButton: FloatingActionButton.extended(
            onPressed: () {
              // Assign the badge value
              setState(() {
                badgeValue = Random().nextInt(100);
              });
            },
            label: const Text('Generate random number'),
          ),
        );
      }
    }
    
    class BadgeWidget extends StatefulWidget {
      const BadgeWidget({
        required this.child,
        this.value,
        super.key,
      });
    
      final Widget child;
      final String? value;
    
      @override
      State<BadgeWidget> createState() => _BadgeWidgetState();
    }
    
    class _BadgeWidgetState extends State<BadgeWidget>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void didUpdateWidget(covariant BadgeWidget oldWidget) {
        if (widget.value != oldWidget.value) {
          _controller.forward(from: 0);
        }
        super.didUpdateWidget(oldWidget);
      }
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 1),
        );
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        const dotSize = 20.0;
        return Stack(
          clipBehavior: Clip.none,
          children: [
            ClipRRect(
              borderRadius: BorderRadius.circular(10),
              child: Material(
                color: Colors.green[400],
                child: InkWell(
                  onTap: () {
                    _controller.reverse();
                  },
                  child: Container(
                    padding: const EdgeInsets.all(10.0),
                    child: widget.child,
                  ),
                ),
              ),
            ),
            if (widget.value != null)
              Positioned(
                right: -dotSize / 3,
                top: -dotSize / 3,
                child: FadeTransition(
                  opacity: _controller,
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.red,
                      borderRadius: BorderRadius.circular(10),
                    ),
                    width: dotSize,
                    height: dotSize,
                    child: Center(
                      child: Text(
                        widget.value!,
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 12,
                        ),
                      ),
                    ),
                  ),
                ),
              ),
          ],
        );
      }
    }