Search code examples
flutterflutter-animation

Flutter custom onTap scale effect


I would like to create custom onTap (or any on other onTap callbacks provided by GestureDetector "indicator" where content(child) is scaled-down and text becomes darker, like to this:example

Is there already a prebuilt package for that type of animation? If not can you at least fix snippet below.

I tried creating one using AnimationController, Transform.scale and various combinations of onTap callbacks but it's pretty unresponsive (gets 'stuck' all the time)

Here is a basic snippet:

 double squareScaleA = 1;
  AnimationController _controllerA;
  @override
  void initState() {
    _controllerA = AnimationController(
        vsync: this,
        lowerBound: 0.9,
        upperBound: 1.0,
        duration: Duration(milliseconds: 100));
    _controllerA.addListener(() {
      setState(() {
        squareScaleA = _controllerA.value;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (dp) {
        _controllerA.forward();
      },
      onTapUp: (dp) {
        _controllerA.reverse();
      },
      child: Transform.scale(
        scale: squareScaleA,
        child: widget.child,
      ),
    );
  }

Solution

  • I found the issue with my code, the animation was "reversed", on onTapDown would cause child to expand (nothing happened since child was already at max scale), so I just reversed _controllerA.reverse and _controllerA.forward (actually replaced _controllerA.forward with _controllerA.fling). This is final code:

    class OnTapScaleAndFade extends StatefulWidget {
      final Widget child;
      final void Function() onTap;
      const OnTapScaleAndFade({Key key, this.child, this.onTap}) : super(key: key);
    
      @override
      _OnTapScaleAndFadeState createState() => _OnTapScaleAndFadeState();
    }
    
    class _OnTapScaleAndFadeState extends State<OnTapScaleAndFade>
        with TickerProviderStateMixin {
      double squareScaleA = 1;
      AnimationController _controllerA;
      @override
      void initState() {
        _controllerA = AnimationController(
          vsync: this,
          lowerBound: 0.98,
          upperBound: 1.0,
          value: 1,
          duration: Duration(milliseconds: 10),
        );
        _controllerA.addListener(() {
          setState(() {
            squareScaleA = _controllerA.value;
          });
        });
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          behavior: HitTestBehavior.translucent,
          onTap: () {
            _controllerA.reverse();
            widget.onTap();
          },
          onTapDown: (dp) {
            _controllerA.reverse();
          },
          onTapUp: (dp) {
            Timer(Duration(milliseconds: 150), () {
              _controllerA.fling();
            });
          },
          onTapCancel: () {
            _controllerA.fling();
          },
          child: Transform.scale(
            scale: squareScaleA,
            child: widget.child,
          ),
        );
      }
    
      @override
      void dispose() {
        _controllerA.dispose();
        super.dispose();
      }
    }
    

    Feel free to add your custom gesture callbacks, I needed only onTap.