#10FlutterChallenges - Part 2: switch button β€οΈβπ₯
Real scenarios & real problems. Learn how to make a custom switch bar with the smooth toggle animation!
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:
- 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:

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