Flutter: keep old value until implicit animation is done

51 views Asked by At

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?

1

There are 1 answers

10
diegoveloper On BEST ANSWER

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,
                    ),
                  ),
                ),
              ),
            ),
          ),
      ],
    );
  }
}