Build an onboarding experience for your mobile apps with Flutter - Part 2: Build the carousel
You will need Dart and Flutter set up on your machine.
Introduction
Hi folks! I guess you have been looking for this second part right ? 😏 Well, here we are. This is the second part of the tutorial series. If you are here it means you have completed the first part and already have a beautiful working app. We’re going to give more control to our app users by providing slides’ carousel indicators, and allowing our users to switch to a particular screen by clicking on the corresponding carousel indicator.
Prerequisites
To follow along with the tutorial, you should have the following:
- Visual Studio Code editor installed on your machine if you haven’t yet. We’ll use it in our tutorial.
- Visual Studio Code Flutter plugin
- An emulator or physical device (for app testing/debugging purpose)
- Having completed the first part of the tutorial series
Building the carousel indicators
Now this is the code for the dots section. Copy and paste it after the MyHomePageState
class.
//..lib/main.dart
class Dots extends StatelessWidget {
final IndexController controller;
final int slideIndex;
final int numberOfDots;
Dots({this.controller, this.slideIndex, this.numberOfDots});
Widget _activeSlide(int index) {
return GestureDetector(
onTap: () {
print('Tapped');
// controller.move(index);
},
child: new Container(
child: Padding(
padding: EdgeInsets.only(left: 8.0, right: 8.0),
child: Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
color: Colors.orangeAccent.withOpacity(.3),
borderRadius: BorderRadius.circular(50.0),
),
),
),
),
);
}
Widget _inactiveSlide(int index) {
return GestureDetector(
onTap: () {
controller.move(index);
},
child: new Container(
child: Padding(
padding: EdgeInsets.only(left: 5.0, right: 5.0),
child: Container(
width: 14.0,
height: 14.0,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.7),
borderRadius: BorderRadius.circular(50.0)),
),
),
),
);
}
List<Widget> _generateDots() {
List<Widget> dots = [];
for (int i = 0; i < numberOfDots; i++) {
dots.add(i == slideIndex ? _activeSlide(i) : _inactiveSlide(i));
}
return dots;
}
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _generateDots(),
));
}
}
The Dots
class constructor takes an instance of the index controller, the current slide index and the number of carousel indicators to build. We’ll use these variables to control the look and behavior of the carousel indicators.
Next, we have to build indicators for active and inactive slides. So based on the status of the slide the proper icon will be rendered. Therefore we have _activeSlide
and _inactiveSlide
widgets.
Basically there are composed of a container widget, which has a border radius as its decoration and a color. Both of these widgets have almost same properties. The sole difference is the opacity of the color. Also we wrapped them inside a GestureDetector
widget which should enable us to listen to various types of gestures triggered on it (tap, doubleTap, longPress,tapUp, etc). Each of these gestures have a corresponding listener to react accordingly:
onTap: () {
controller.move(index);
},
The code above tells the slide controller to move the slides to the page corresponding to the indicator tapped/clicked. Thus, we can control our slides by simply clicking on the dots we provided.
After this step, we need to generate the indicators. The _generateDots
function returns a list of indicators based on the numberOfDots
provided in the constructor inside the loop. If the current iteration number is equal to the current slide index, we add an _activeWidget
to the list, if not we add an _inactiveWidget
.
Finally, inside the build
method we render and return our indicators inside a centered row so they can be aligned horizontally:
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _generateDots(),
));
Now, we need to add the indicators to our slides. Just paste the following piece of code into the transformerPageView
after the last SizedBox
widget.
new ParallaxContainer(
position: info.position,
translationFactor: 500.0,
child: Dots(
controller: controller,
slideIndex: _slideIndex,
numberOfDots: images.length,
),
)
With all the parts completed, you should have the following :
import 'package:flutter/material.dart';
import 'package:transformer_page_view/transformer_page_view.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({this.title});
MyHomePageState createState() {
return new MyHomePageState();
}
}
class MyHomePageState extends State<MyHomePage> {
int _slideIndex = 0;
final GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
final List<String> images = [
"assets/slide_1.png",
"assets/slide_2.png",
"assets/slide_3.png",
"assets/slide_4.png"
];
List<Color> colors = [Colors.orange];
final List<String> text0 = [
"Welcome in your app",
"Enjoy teaching...",
"Showcase your skills",
"Friendship is great"
];
final List<String> text1 = [
"App for food lovers, satisfy your taste",
"Find best meals in your area, simply",
"Have fun while eating your relatives and more",
"Meet new friends from all over the world"
];
final IndexController controller = IndexController();
Widget build(BuildContext context) {
TransformerPageView transformerPageView = TransformerPageView(
pageSnapping: true,
onPageChanged: (index) {
setState(() {
this._slideIndex = index;
});
},
loop: false,
controller: controller,
transformer: new PageTransformerBuilder(
builder: (Widget child, TransformInfo info) {
return new Material(
color: Colors.white,
elevation: 8.0,
textStyle: new TextStyle(color: Colors.white),
borderRadius: new BorderRadius.circular(12.0),
child: new Container(
alignment: Alignment.center,
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new ParallaxContainer(
child: new Text(
text0[info.index],
style: new TextStyle(
color: Colors.blueGrey,
fontSize: 34.0,
fontFamily: 'Quicksand',
fontWeight: FontWeight.bold),
),
position: info.position,
opacityFactor: .8,
translationFactor: 400.0,
),
SizedBox(
height: 45.0,
),
new ParallaxContainer(
child: new Image.asset(
images[info.index],
fit: BoxFit.contain,
height: 350,
),
position: info.position,
translationFactor: 400.0,
),
SizedBox(
height: 45.0,
),
new ParallaxContainer(
child: new Text(
text1[info.index],
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.blueGrey,
fontSize: 28.0,
fontFamily: 'Quicksand',
fontWeight: FontWeight.bold),
),
position: info.position,
translationFactor: 300.0,
),
SizedBox(
height: 55.0,
),
new ParallaxContainer(
position: info.position,
translationFactor: 500.0,
child: Dots(
controller: controller,
slideIndex: _slideIndex,
numberOfDots: images.length,
),
)
],
),
),
),
);
}),
itemCount: 4);
return Scaffold(
backgroundColor: Colors.white,
body: transformerPageView,
);
}
}
class Dots extends StatelessWidget {
final IndexController controller;
final int slideIndex;
final int numberOfDots;
Dots({this.controller, this.slideIndex, this.numberOfDots});
List<Widget> _generateDots() {
List<Widget> dots = [];
for (int i = 0; i < numberOfDots; i++) {
dots.add(i == slideIndex ? _activeSlide(i) : _inactiveSlide(i));
}
return dots;
}
Widget _activeSlide(int index) {
return GestureDetector(
onTap: () {
print('Tapped');
},
child: new Container(
child: Padding(
padding: EdgeInsets.only(left: 8.0, right: 8.0),
child: Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
color: Colors.orangeAccent.withOpacity(.3),
borderRadius: BorderRadius.circular(50.0),
),
),
),
),
);
}
Widget _inactiveSlide(int index) {
return GestureDetector(
onTap: () {
controller.move(index);
},
child: new Container(
child: Padding(
padding: EdgeInsets.only(left: 5.0, right: 5.0),
child: Container(
width: 14.0,
height: 14.0,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.7),
borderRadius: BorderRadius.circular(50.0)),
),
),
),
);
}
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _generateDots(),
));
}
}
The code is pretty concise for the nice work we’ve achieved. Flutter helps us build this awesome app with no hassle and with a minimum effort 😄 .
We are done with this tutorial. Run your app with this command in your terminal: flutter run
and see the magic happen 🙃
Conclusion
This is the end of the tutorial series. I do hope it has been useful to you, and you can apply the knowledge acquired to build beautiful apps 😌 . The source code for this part is available here; feel free to fork it and modify it as per your needs.
6 March 2019
by Ethiel Adiassa