programmingcodingsoftware engineering

Create a Bouncing Button Animation in Flutter

Add animations to your applications enriches the general user experience.

Published on March 24 2021

In Flutter creating animations is very simple and intuitive. In this case you will learn how to create a bouncing button animation working with controllers and values.

The final result of the tutorial

Bouncing Button Preview

AnimationController class lets you perform tasks such as:

AnimationController produces values that range from 0.0 to 1.0. during a given duration.

Ticker providers

An AnimationController needs a TickerProvider, which is configured using the vsync argument on the constructor.

The TickerProvider interface describes a factory for Ticker objects. A Ticker is an object that knows how to register itself with the SchedulerBinding and fires a callback every frame.

The AnimationController class uses a Ticker to step through the animation that it controls.

If an AnimationController is being created from a State, then the State can use the TickerProviderStateMixin and SingleTickerProviderStateMixin classes to implement the TickerProvider interface.

The TickerProviderStateMixin class always works for this purpose; the SingleTickerProviderStateMixin is slightly more efficient in the case of the class only ever needing one Ticker.

AnimationWidget

class AnimatedButton extends AnimatedWidget {
  final AnimationController _controller;
  const AnimatedButton({
     AnimationController controller,
  })  : _controller = controller,
        super(listenable: controller); // (a).

  
  Widget build(BuildContext context) {
    return Transform.scale( // (b).
      scale: 1 - _controller.value, // (c).
      child: Container(
        height: 70,
        width: 200,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20.0),
            boxShadow: const [
              BoxShadow(
                color: Color(0x80000000),
                blurRadius: 10.0,
                offset: Offset(0.0, 2.0),
              ),
            ],
            gradient: LinearGradient(
              colors: const [
                Color(0xff00e6dc),
                Color(0xff00ffb9),
              ],
            )),
        child: const Center(
          child: Text('Press button',
              style: TextStyle(
                fontSize: 20.0,
                fontWeight: FontWeight.bold,
                color: Color(0xff000028),
              )),
        ),
      ),
    );
  }
}

The layout button is based on a Container with a text widget. The button has border radius and a linear gradient style.

Bouncing Button Widget

class BouncingButton extends StatefulWidget {
  
  _BouncingButtonState createState() => _BouncingButtonState();
}

class _BouncingButtonState extends State<BouncingButton>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  
  void initState() {
    _controller = AnimationController( // (a).
      vsync: this, // (b).
      duration: Duration(
        milliseconds: 500,
      ),
      lowerBound: 0.0,
      upperBound: 0.1,
    )..addListener(() { // (c).
        setState(() {});
      });
    super.initState();
  }

  
  void dispose() {
    super.dispose();
    // (d).
    _controller.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            'Bouncing Button Animation',
            style: TextStyle(color: Colors.grey[700], fontSize: 20.0),
          ),
          const SizedBox(
            height: 20.0,
          ),
          Center(
            child: GestureDetector(
              onTapDown: _tapDown,
              onTapUp: _tapUp,
              child: AnimatedButton(controller: _controller),
            ),
          ),
        ],
      ),
    );
  }

  // (e).
  void _tapDown(TapDownDetails details) {
    _controller.forward();
  }

  // (f).
  void _tapUp(TapUpDetails details) {
    _controller.reverse();
  }
}

The full code

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: BouncingButton(),
    );
  }
}

class BouncingButton extends StatefulWidget {
  
  _BouncingButtonState createState() => _BouncingButtonState();
}

class _BouncingButtonState extends State<BouncingButton>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(
        milliseconds: 500,
      ),
      lowerBound: 0.0,
      upperBound: 0.1,
    )..addListener(() {
        setState(() {});
      });
    super.initState();
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            'Bouncing Button Animation',
            style: TextStyle(color: Colors.grey[700], fontSize: 20.0),
          ),
          const SizedBox(
            height: 20.0,
          ),
          Center(
            child: GestureDetector(
              onTapDown: _tapDown,
              onTapUp: _tapUp,
              child: AnimatedButton(controller: _controller),
            ),
          ),
        ],
      ),
    );
  }

  void _tapDown(TapDownDetails details) {
    _controller.forward();
  }

  void _tapUp(TapUpDetails details) {
    _controller.reverse();
  }
}

class AnimatedButton extends AnimatedWidget {
  final AnimationController _controller;
  const AnimatedButton({
     AnimationController controller,
  })  : _controller = controller,
        super(listenable: controller);

  
  Widget build(BuildContext context) {
    return Transform.scale(
      scale: 1 - _controller.value,
      child: Container(
        height: 70,
        width: 200,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20.0),
            boxShadow: const [
              BoxShadow(
                color: Color(0x80000000),
                blurRadius: 10.0,
                offset: Offset(0.0, 2.0),
              ),
            ],
            gradient: LinearGradient(
              colors: [
                Color(0xff00e6dc),
                Color(0xff00ffb9),
              ],
            )),
        child: const Center(
          child: Text('Press button',
              style: TextStyle(
                fontSize: 20.0,
                fontWeight: FontWeight.bold,
                color: Color(0xff000028),
              )),
        ),
      ),
    );
  }
}

I hope that this tip will help you for your next mobile development.

See you in the next tutorial. 😉