#10FlutterChallenges - Part 2: switch button ❀️‍πŸ”₯

Real scenarios & real problems. Learn how to make a custom switch bar with the smooth toggle animation!

#10FlutterChallenges - Part 2: switch button ❀️‍πŸ”₯
Photo by Christopher Gower / Unsplash

Welcome to the part 2 of the Flutter Challenges blog series.

Part 2 πŸ‘‹:

In this blog post, we will implement a custom switch button that can be used for many functions that you need e.g. on / off.

Issue #1 – switch UI

First things first.

In this feature, we will have to use the following built-in class:

The last widget is very important here. With this widget, the user interface will be able to change in response to the state:

A widget that has mutable state.

Below you can check the basic code of the switch button. Of course, it can be more customized, e.g. support different sizes or colors:

class SwitchButton extends StatelessWidget {
  const SwitchButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {},
      child: Container(
        width: 65.0,
        height: 40.0,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(20.0),
          color: Colors.greenAccent,
        ),
        child: Padding(
          padding: const EdgeInsets.all(4.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              const Padding(
                padding: EdgeInsets.only(left: 22.0, right: 4.0),
              ),
              Align(
                child: Container(
                  width: 28.8,
                  height: 28.8,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: Colors.white,
                    border: Border.all(
                        color: Colors.black.withOpacity(0.04), width: 0.6),
                    boxShadow: <BoxShadow>[
                      BoxShadow(
                        color: Colors.black.withOpacity(0.16),
                        offset: const Offset(0, 1),
                        blurRadius: 1,
                      ),
                    ],
                  ),
                ),
              ),
              Container(),
            ],
          ),
        ),
      ),
    );
  }
}

Issue #2 – state

As I mentioned at the beginning, our switch will require a state where we can store a value and change it when tapping the switch. In response to the change, the UI will refresh and update the widget.

So now convert StatelessWidget to StatefulWidget as follows:

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

  @override
  State<SwitchButton> createState() => _SwitchButtonState();
}

class _SwitchButtonState extends State<SwitchButton> {

In the last step, add a state variable at the top of the class and name it whatever you want, in my case it is the value:

class _SwitchButtonState extends State<SwitchButton> {
	bool value = false;

Issue #3 – Animation 😲

Now it's time to add some animation to make our switch button move. To use AnimationController, we need to extend our class with a SingleTickerProviderStateMixin:

class _SwitchButtonState extends State<SwitchButton>
    with SingleTickerProviderStateMixin {

Then we define two variables below our previous state variable that will hold the animation and the controller values:

class _SwitchButtonState extends State<SwitchButton>
    with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
late final Animation _circleAnimation;

We need to initialize the controller and animation as soon as possible. Fortunately, when we change our widget to stateful, we can use a method called initState. The following code is responsible for initializing the state in the initState method. It updates each time the widget configuration appears in didUpdateWidget. And in the dispose method, we remove the animation controller when the widget is removed from the widget tree:

class _SwitchButtonState extends State<SwitchButton>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  late final Animation _circleAnimation;
  
 @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 60),
      vsync: this,
    );
    _circleAnimation =
        AlignmentTween(begin: Alignment.centerLeft, end: Alignment.centerRight)
            .animate(
      CurvedAnimation(parent: _animationController, curve: Curves.linear),
    );

    if (value) {
      _animationController.forward();
    } else {
      _animationController.reverse();
    }
  }

  @override
  void didUpdateWidget(covariant SwitchButton oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (value) {
      _animationController.forward();
    } else {
      _animationController.reverse();
    }
  }

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

To change the value in state, we also need to add logic to the onTap method provided by GestureDetector:

onTap: () {
	if (value) {
		value = false;
        _animationController.reverse();
    } else {
       	value = true;
        _animationController.forward();
    }
},

So far we are doing well, the next step is to wrap all the code in the build method with the AnimatedBuilder class and provide the required parameters: animation and builder:

@override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (_, __) {
        return GestureDetector(

As you can see, the builder method has two parameters, but since we're not using either parameter, we can omit it with an underscore. At this point, our switch should animate, but reports an overflow error.

Let's fix it.

We use the _circleAnimation field for this to do conditional rendering in 3 places:

  1. Change the decoration of the first child Container:
decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20.0),
              color: _circleAnimation.value == Alignment.centerLeft
                  ? Colors.black
                  : Colors.greenAccent,
            ),

2. Change the children of the first Row element:

 child: Row(
 	mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: <Widget>[
        _circleAnimation.value == Alignment.centerRight
        ? const Padding(padding: EdgeInsets.only(left: 22.0, right: 4.0),)
        : Container(),

3. Replace the empty Container at the end of the code:

_circleAnimation.value == Alignment.centerLeft
    ? const Padding(padding: EdgeInsets.only(left: 4.0, right: 22.0),)
    : Container(),

Summary:

That's it 😎 We just finished the switch button which animates smoothly. There are many ways to adapt this code to our needs.

You can always check out the full source code below:

This is the source code of the Flutter Challenges series post: https://fun4code.com/10flutterchallanges-part2/
This is the source code of the Flutter Challenges series post: https://fun4code.com/10flutterchallanges-part2/ - main.dart

What’s next?

And that's all for this challenge ✌️

In this post, I went over the build switch button using the built-in Flutter widgets.

In the next chapter, we will build an audio and lifecycle services to manage sounds in our applications.


Thanks for reading β™₯️β™₯️

…and stay tuned! πŸ‘Š

Also check out the rest of the Flutter Challenges:

Part 1 β€” #10FlutterChallenges - Part 1: progress bar with gradient
Part 2 β€” #10FlutterChallenges - Part 2: switch button
Part 3 β€” #10FlutterChallenges - Part 3: Audio + LifeCycle service