Recreating Slack app’s on-boarding animation with Flutter

Darshan Kawar
Flutter Community
Published in
7 min readSep 1, 2019
Photo by RhondaK Native Florida Folk Artist on Unsplash

Slack is used in many tech companies for daily collaboration across teams and departments. If you have it’s app on your phone and open it, you will see a cool animation of on-boarding feature that shows user how the app works, how to use it and what you can do with it.

In today’s article, I will recreate this animation using animation APIs Flutter provides. So let’s get started.

Scope

For sake of the length of the article, we will recreate the animation of the first and second screen, as most of the implementation and it’s logic will be consumed by these screens. The last 2 screens will more or less have the same code as first 2.

Approach

Looking at the gif above, we notice that on the first screen, there are 3 different types of animations taking place, so we will use divide-and-implement approach by splitting the first screen into 3 sections (middle, top, and bottom), as shown below:

  1. Middle section

When app launches, we see three images scaling-in one by one after the delay of the split of a second between them at the center of the screen.

2. Top section

The text appears out of screen at the top after all three images are rendered fully.

3. Bottom section

The page indicator, get started and sign in widgets appear simultaneously at one go.

Apart from the animation which is going to be our focus here, we will also make use of PageView and Page Indicator to help complete the screen.

Implementing the middle section

I took screenshots of the images that we are going to use from the original app and added into assets folder.

As we saw earlier, the images scale-in one after another. To achieve this, we will make use of Staggered Animation which helps us to animate sequentially using Interval that in turn is used to delay an animation. We will start by creating an AnimationController object.

AnimationController controller;

Then, we will create an animation object of type double for each image followed by creating Tween for each along with the interval.

Animation<double> firImgAnimation;
Animation<double> secImgAnimation;
Animation<double> thrImgAnimation;
firImgAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.050,
0.200,
curve: Curves.decelerate,

)
)
);

The begin and end values indicate that we want the image to render 100% and we will be using these values for all our animation objects. Since the first image’s animation doesn’t start immediately when the app launches, we’ll specify the start value of the interval from 0.05 and will last till 0.2. We will use curve type as decelerating (that starts quickly and end decelerating).

Now we have created an animation object for the first image. We will need to feed this to a widget that will help to display it on screen. Since the image appears to be zooming in, the equivalent of it is scaling and Flutter provides a widget named ScaleTransition which has a property called scale in which we will provide the animation object we created above. Let’s see how:

Container(
padding: EdgeInsets.only(top: 40),
child: ScaleTransition(
scale: firImgAnimation,
child: Image.asset('assets/first.png'),
),
),

Similarly, we will create Tween for the second and third image by specifying intervals in such a way that they scale-in on-screen after the split of a second.

secImgAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.250,
0.400,
curve: Curves.decelerate
)
)
);

thrImgAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.450,
0.600,
curve: Curves.decelerate
)
)
);

Then, we will pass these in separate Container widget that has ScaleTransition as it’s a child.

Container(
height: 100,
padding: EdgeInsets.only(left: 50, top: 20),
child: ScaleTransition(
scale: secImgAnimation,
child: Image.asset('assets/second.png'),
)
),
Container(
width: 250,
padding: EdgeInsets.only(top: 20),
child: ScaleTransition(
scale: thrImgAnimation,
child: Image.asset('assets/third.png'),
)
),

Let’s see the result:

Implementing top section

For the text that follows the animation of the image, it appears on the screen from top and gradually settles on top of the first image. For this animation to achieve, we will create an animation object of type Offset that will allow us to use vertical values (dy) followed by creating Tween of the object.

Animation<Offset> textAnimation;textAnimation =
Tween<Offset>(begin: Offset(0, -1.5), end: Offset(0.0, 0.4)).animate(
CurvedAnimation(parent: controller, curve: Interval(
0.620, 0.920, curve: Curves.fastOutSlowIn
)
));

Since the text appears from out of the screen at the top, we used negative dy offset and specify to stop after reaching 0.4 range. The interval values continue from where the third image values were left adding some delay and used Curves.fastOutSlowIn to make the text animation effect to appear slow.

After this setup, we will use SlideTransition widget that has position property to which we will pass the textAnimation object we created above.

Container(
padding: EdgeInsets.only(top: 30),
alignment: Alignment.topCenter,
child: SlideTransition(position: textAnimation,
child:
RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: ' All your team communication\n',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 22)
),
TextSpan(
text: 'in one place, instantly searchable.',
style: TextStyle(
color: Colors.black,
fontSize: 22)
)
]
),
)
),
),

Let’s see the result combined with middle section animation:

Implementing bottom section

For this section, all the widgets animate at once, so will only require to create one animation object of type double and create a tween and specify rest of the interval duration. Then, we will use FadeTransition widget and pass the animation object to opacity property. For all widgets to fade in simultaneously, we will specify same opacity property to each.

Animation<double> fadeInAnimation;fadeInAnimation = Tween<double>(begin: 0, end: 1.0).animate(
CurvedAnimation(parent: controller, curve: Interval(0.935, 1.0
))
);
Container(
padding: EdgeInsets.only(top: 30),
child: FadeTransition(opacity: fadeInAnimation,
child: DotsIndicator(
dotsCount: 4,
position: 0,
decorator: DotsDecorator(
color: Colors.grey,
activeColor: const Color(0xD9263238)
),
),),
),
Container(
width: 250,
padding: EdgeInsets.only(top: 20),
child: FadeTransition(
opacity: fadeInAnimation,
child: RaisedButton(
color: const Color(0xFF2E7D32),
onPressed: () {},
child: Text('Get Started',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),)
),
)
),
Container(
padding: EdgeInsets.only(top: 20),
child: FadeTransition(
opacity: fadeInAnimation,
child: Text('Sign in', style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black),
),
)
)

Let’s now see the result of all animations at one go:

Onto next screen

When we swipe to the right, we see animation at center of the screen in which an image scales-in. Rest of the sections (top and bottom) are not animated. Let’s see how to implement and animate this screen.

In a new class, we will create AnimationController object and similar Animation object of type double and use ScaleTransition to achieve the desired effect.

AnimationController controller;
Animation<double> imgAnimation;
imgAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.500,
0.950,
curve: Curves.decelerate,

)
)
);
Container(
padding: EdgeInsets.only(top: 40),
child: ScaleTransition(
scale: imgAnimation,
child: Image.asset('assets/second_screen.png'),
),
),

Since the image appears after a delay when screen renders, we specified interval to be 0.5 to 0.95 that will help us to apply delay effect.

And the result including first screen is :

Similarly, animation for remaining screens can be implemented using above approach.

There are few code snippets that I haven’t discussed here, but are important to run animation properly. They are :

  • Set the duration of animation of first screen in initState() as:
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 3000), vsync: this
);
}
  • Set animation to forward as:

controller.forward();

  • Rendering widgets on screen are wrapped inside AnimatedBuilder so as to animate the widgets.

In this article, we looked at following Animation terms and concepts and got to know how they can be used to achieve and recreate subtle animations.

And that wasn’t difficult at all, correct ?

  • AnimationController
  • Animation<double>
  • Animation<Offset>
  • Tween
  • Interval
  • ScaleTransition
  • SlideTransition
  • FadeTransition

All above code is available here and I will be adding rest of the code for other screens soon.

That’s all for now. I hope you liked what you read here. Thanks for reading and feel free to comment below your thoughts or any suggestions/feedback on this article.

I am available on Twitter, LinkedIn, and Github.

My other articles on Flutter are:

--

--

Darshan Kawar
Flutter Community

Open Source Support Engineer For Flutter @nevercodeHQ. Android Nanodegree Certified. Previously, Android Automation Test Engineer.