6

I have a IconButton, Normally, It's icon is Icons.expand_more but When I press that its icon should be Icons.expand_less. I want to animated this so that if I press that button, it will rotate and point the downwords from upwords. and same when I press the expand_less then it should become expand_more with rotating animation. How can I acheive this ? below is my code :

    IconButton(
      icon:  _expanded ? Icon(Icons.expand_less) : Icon(Icons.expand_more),
      onPressed: () {
        setState(() {
           _expanded = !_expanded;
        });
      },
   )

I tried to use animatedContainer but it didn't work as I am using two different icons and that rotation effect I cannot acheive with this. I also tried to rotate the icon with below approach but it cannot show the animation when it is rotating from 0 to 180 degree.

IconButton(
              icon: AnimatedContainer(
                  duration: Duration(seconds: 3),
                  child: Transform.rotate(
                      angle: _expanded ? 0 : 180 * math.pi / 180,
                      child: Icon(Icons.expand_less))),
              // icon:
              //     _expanded ? Icon(Icons.expand_less) : Icon(Icons.expand_more),
              onPressed: () {
                setState(() {
                  _expanded = !_expanded;
                });
              },
            )

This is before expansion :

before expansion

This is after expansion :

after expansion

I want the rotation animation on button click.

Aayush Shah
  • 584
  • 1
  • 8
  • 17

3 Answers3

12

achieved Animation

Thanks to @krumpli.

  1. Define AnimationController _controller.

  2. Define init method as :

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 300),
      upperBound: 0.5,
    );
  }
  1. Define dispose method as :
@override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
  1. With the use of the widget RotationTransition :
RotationTransition(
              turns: Tween(begin: 0.0, end: 1.0).animate(_controller),
              child: IconButton(
                icon: Icon(Icons.expand_less),
                onPressed: () {
                  setState(() {
                    if (_expanded) {
                      _controller..reverse(from: 0.5);
                    } else {
                      _controller..forward(from: 0.0);
                    }
                    _expanded = !_expanded;
                  });
                },
              ),
            )

Don't forget to add with SingleTickerProviderStateMixin in the class defintion.

Aayush Shah
  • 584
  • 1
  • 8
  • 17
  • Do you have and idea how to do it if you have a draggable body? Your solution works if I press the button (the button opens the bottom panel) , but If I drag the panel, the icon won't rotate when the panel is fully open. Same when closing, it works when pressed, it doesn't when it's dragged –  Sep 09 '22 at 09:21
  • 1
    Good work. There are a few places for improvement though. You don't need to use the `..` cascade operator in your call to `setState` since nothing is being returned. In your `dispose` method, `super.dispose` should be call after the controller is disposed, not before. Lastly, your icons are backwards. The down chevron (equivalent to the `expand_more` icon) should be visible in the collapsed state, and the up chevron (equivalent to the `expand_less` icon) should be visible in the expanded state. This way the icons indicate the action that will take place when pressed. – ubiquibacon Feb 22 '23 at 20:25
  • 1
    One additional observation/recommendation. If you change `reverse(from: 0.5)` to `1.0` you can remove `upperBound` entirely and set the `end: 0.5` when you create your `Tween` object. This will allow the same `AnimationController` to be reused for full animations (not just half) on different objects in the same widget. – ubiquibacon Feb 23 '23 at 01:33
3

Check out this test sample I have made for what you need.

This also applies a curve which is a good Flutter suggestion.

import 'package:flutter/material.dart';

class RotateIcon extends StatefulWidget {
  const RotateIcon({Key? key}) : super(key: key);

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

class _RotateIconState extends State<RotateIcon>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 500), vsync: this);
    _animation =
        Tween(begin: 0.0, end: .5).animate(CurvedAnimation(
        parent: _controller, curve: Curves.easeOut));
    }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
          child: Column(
            children: [
              RotationTransition(
                turns: _animation,
                child: const Icon(Icons.expand_more),
              ),
              ElevatedButton(
                  onPressed: () {
                    if (_controller.isDismissed) {
                      _controller.forward();
                    } else {
                      _controller.reverse();
                    }
                  },
                  child: const Text("Animate"))
            ],
          )),
    );
  }
}

Cavitedev
  • 613
  • 4
  • 17
  • 1
    OK, I'm glad my answer was good enough. You can now customize it with your code and change the times for the animation – Cavitedev Jun 29 '21 at 09:35
  • I figured out the solution from @krumpli's answer but after watching your solution( ) I confirmed that ! – Aayush Shah Jun 29 '21 at 09:37
  • OK I have updated the answer to add the important code which is the entire class. Is that better? – Cavitedev Jun 29 '21 at 11:06
1

Try this: Add an animation controller: AnimationController _animationController:

initState() {
    _animationController = new AnimationController(
        vsync: this, duration: Duration(milliseconds: 300));
    }

wrap your icon in this:

RotationTransition(
         turns: Tween(begin: 0.0, end: 1.0)
                .animate(_animationController),
         child: IconButton(icon: //YOUR ICON),
         onPressed: () {
             setState(() {
        _animationController1.forward(from: 0.0);
      });
     },
   ),
 )

Finally:

  @override
  void dispose() {
    super.dispose();
    _animationController?.dispose();
  }
krumpli
  • 723
  • 3
  • 15
  • thanks mate! Your solution does the job of rotating animation but I needed specific rotation animation on expanding and collapsing, which I figured out from your solution though. – Aayush Shah Jun 29 '21 at 09:25
  • 1
    Yepp, simple fix with a flag. Happy to help! – krumpli Jun 29 '21 at 09:32