Google I/O Filter Animation in Flutter
Hello Flutter Learners,
Some days back, when I started Flutter development, I was very pleased with how UI designing changed from the era of XML/XAML/HTML to Dart. There is no way any developer in the world who is working on Flutter can get away without knowing animations. There was a time when animations were hard for me in Android, before I discovered value animators, object animators, which made jobs easier. But the way, Flutter has implemented animations makes it a hell of a lot easier and I can bet that it is the easiest way than any other languages out there.
What you will learn
Designing UI
First and foremost, is designing the filter button by considering the fact that the elements in the button will animate when clicked.
Taking a Container
of height 40.0 with a rounded border. Container’s child will be a stack
having 3 children.
- Rounded dot — Creating a rounded dot using a rectangle box decoration because it becomes easier when we change the width and height of the box only. Height = Width = 20.0 initially.
- Text — Text has left padding of 40.0 and right padding of 20.0 initially.
- Icon — Rounded container positioned 5.0 from right having a close icon.
Widget getWidget(BuildContext context, Widget widget) {
return Center(
child: InkWell(
onTap: () {},
child: Container(
height: 40.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
border: Border.all(color: Colors.black26, width: 1.0),
),
child: Stack(
children: <Widget>[
Positioned(
left: 10.0,
top: 8.5,
child: Container(
height: 20.0,
width: 20.0,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
),
),
),
Align(
widthFactor: 1.0,
alignment: Alignment.center,
child: Container(
padding: EdgeInsets.only(left: 40.0, right: 20.0),
child: Text(
"Flutter",
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
),
),
Positioned(
width: 0.0,
height: 0.0,
right: 5.0,
top: 7.0,
child: Container(
padding: EdgeInsets.all(2.0),
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.grey.shade400),
child: Icon(
Icons.close,
size: 20.0,
color: Colors.white,
),
),
)
],
),
),
),
);
}
The output of the above code is —
What actually happens in the animation?
When the button is pressed —
- The text moves to left by certain points
- Dot covers the container with its color, basically, the dot which I created using rectangle changes its height to 40.0 and width to the height of the container.
- Close icon grows from zero to a certain size
I have achieved these value changes using tweens in Flutter. Let’s have a look.
Implementing Tweens
What are tweens? Tween is just a set of 2 values — begin and end. When tween moves in forwarding direction by the controller, the begin value moves to become end value in some given time and in some given pattern(defined by curves). Vice-versa happens when the controller moves in reverse direction.
In this animation’s case, there are few values that change simultaneously to bring out the desired animation. Have a look at the below table below —
Text animation
3 values change for our text, left padding, right padding, and text color
AnimationController _controller;
Animation<double> _textLeftPaddingAnimation;
Animation<double> _textRightPaddingAnimation;
Animation<Color> _textColorAnimation;@override
void initState() {
super.initState();
_controller =
AnimationController(
vsync: this,
duration: Duration(
milliseconds: 400),);
_textLeftPaddingAnimation =
Tween<double>(
begin: 40.0,
end: 20.0).
animate(_controller);
_textRightPaddingAnimation =
Tween<double>(
begin: 20.0,
end: 40.0).
animate(_controller);
_textColorAnimation =
ColorTween(
begin: Colors.black,
end: Colors.white).
animate(_controller);
}//Code for the text in the stackAlign(
widthFactor: 1.0,
alignment: Alignment.center,
child: Container(
padding: EdgeInsets.only(
left: _textLeftPaddingAnimation.value,
right: _textRightPaddingAnimation.value),
child: Text(
"Flutter",
style: TextStyle(
fontSize: 20.0, color: _textColorAnimation.value),
),
),
),
Output
Close Icon Animation
Updated code for Icon in the stack and applied animation tween. Using Scale transition(makes stuff easier when dealing with the size of a widget)
Animation<double> _closeIconAnimation;_closeIconAnimation = Tween<double>(
begin: 0.0,
end: 1.0).
animate(_controller);Positioned(
right: 5.0,
top: 7.0,
child: ScaleTransition(
scale: _closeIconAnimation,
child: Container(
padding: EdgeInsets.all(2.0),
decoration:
BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400),
child: Icon(
Icons.close,
size: 20.0,
color: Colors.white,
),
),
),
)
Output
Dot Animation
Well, so far so good. But now, the dot has to change its width according to the container’s width and height as 40.0 which is default in this case. We will change 4 values here, dot width, dot height, dot left position and dot top position.
Using Global keys we can get width of container once its built in the widget tree.
Create a key for the container which contains the stack
final GlobalKey _parentContainer = GlobalKey(debugLabel: 'parent container');child: Container(
key: _parentContainer,
child: Stack()
)
Get the width of the container, considering default value as 20.0 for the dot.
var width = 20.0;
if (_parentContainer.currentContext != null) {
final RenderBox renderBox = _parentContainer.currentContext.findRenderObject();
width = renderBox.size.width;
}
Tweens for the dot positioning and size
_dotHeightAnimation = Tween<double>(
begin: 20.0,
end: 40.0).animate(_controller);
_dotLeftPositionAnimation = Tween<double>(
begin: 10.0,
end: 0.0).animate(_controller);
_dotTopPositionAnimation = Tween<double>(
begin: 8.5,
end: -1.0).animate(_controller);Animation<double> getDotWidth() {
var width = 20.0;
if (_parentContainer.currentContext != null) {
final RenderBox renderBox = _parentContainer.currentContext.findRenderObject();
width = renderBox.size.width;
} return Tween<double>(
begin: 20.0,
end: width - 2).animate(_controller);
}
Change the dot UI with animations
Positioned(
left: _dotLeftPositionAnimation.value,
top: _dotTopPositionAnimation.value,
child: Container(
height: _dotHeightAnimation.value,
width: getDotWidth().value,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(20.0))),
),
),
Output
Yay!! Animation completed.
Key gems out of this animation — that I personally learned while making.
Keys- utilizing them to get the width, the height of a widget
AnimatedBuilder- Very important to accommodate animations while building the widget as in our case.
Circular dot- I could create a rounded dot by using both BoxShape.circle and BoxShape.rectangle.
The full code can be found on — https://github.com/PrateekSharma1712/flutter-articles/blob/master/googleiofilteroptionanimation.dart
Some code snippets that you should also know, I used in this animation
AnimationBuilder
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: getWidget,
animation: _controller,
);
}
AnimationStatus
bool get animationStatus {
AnimationStatus status = _controller.status;
return status == AnimationStatus.completed;
}
InkWell — wrapping Container with _parentContainer key
Making the controller moving forward or reverse based on its current status. Basically toggling.
InkWell(
onTap: () {
animationStatus ? _controller.reverse() : _controller.forward();
})
That is it for this article. Feel free to ask questions or discuss better ways to implement this animation. Do clap 👏 if you enjoyed the article. Check out the Github. Thank you very much.