
Creating a Beautiful Onboarding Screen in Flutter
Onboarding screens are an essential part of mobile applications. They provide users with a quick overview of the app’s features and guide them through its functionality before they dive into using it. In this tutorial, we’ll create a visually appealing onboarding screen using Flutter, complete with transitions between pages, image placeholders, and a dynamic “Continue” button that changes to “Get Started” on the final page.
Here’s what we’ll cover step-by-step:
- Setting up the Flutter project
- Using
PageView
for sliding between multiple onboarding screens - Adding visual elements like images, titles, descriptions, and indicators
- Handling navigation and user interaction
Let’s get started!
Step 1: Setting Up Your Flutter Project
First, ensure you have Flutter installed on your machine. If not, follow the official installation guide.
Once Flutter is ready, create a new project:
flutter create onboarding_app
cd onboarding_app
Now open the project in your favorite IDE (like VS Code or Android Studio).
Step 2: Add Dependencies
We’ll use the cached_network_image
package to efficiently load images from URLs. Add it to your pubspec.yaml
file under dependencies:
dependencies:
flutter:
sdk: flutter
cached_network_image: ^3.2.0
Run the following command to install the package:
flutter pub get
This will fetch the required libraries for handling network images.
Step 3: Prepare Assets
For simplicity, we’ll be pulling images from the web using cached_network_image
. However, if you prefer to use local assets, you can include images in the assets/images/
directory and reference them in your pubspec.yaml
.
Add the following section to your pubspec.yaml
if you plan to use local assets:
flutter:
assets:
- assets/images/
Then place your images inside the assets/images/
folder.
Step 4: Designing the Onboarding Screen UI
Create a new Dart file named onboarding_screen.dart
inside the lib
directory. This will contain our main onboarding logic.
1. Main Entry Point (main.dart
)
In the main.dart
file, set up the application structure. We’ll use MaterialApp
as the base widget.
import 'package:flutter/material.dart';
import 'onboarding_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Onboarding App',
theme: ThemeData(
primarySwatch: Colors.pink,
),
home: OnboardingScreen(),
);
}
}
2. Building the Onboarding Screen (onboarding_screen.dart
)
Now, let’s implement the onboarding screen itself. Here’s the complete implementation:
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
class OnboardingScreen extends StatefulWidget {
@override
_OnboardingScreenState createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen> {
final int _numPages = 3;
final PageController _pageController = PageController(initialPage: 0);
int _currentPage = 0;
// Build page indicator dots
List<Widget> _buildPageIndicator() {
List<Widget> list = [];
for (int i = 0; i < _numPages; i++) {
list.add(i == _currentPage ? _indicator(true) : _indicator(false));
}
return list;
}
// Custom indicator dot
Widget _indicator(bool isActive) {
return AnimatedContainer(
duration: Duration(milliseconds: 150),
margin: EdgeInsets.symmetric(horizontal: 8.0),
height: 8.0,
width: isActive ? 24.0 : 16.0,
decoration: BoxDecoration(
color: isActive ? Colors.pink : Colors.grey[300],
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 3,
child: PageView.builder(
physics: ClampingScrollPhysics(),
controller: _pageController,
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
itemCount: _numPages,
itemBuilder: (context, position) {
return _buildOnboardingPage(position);
},
),
),
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPageIndicator(),
),
),
SizedBox(height: 30.0),
_currentPage != _numPages - 1
? FlatButton(
onPressed: () {
_pageController.nextPage(
duration: Duration(milliseconds: 300),
curve: Curves.ease,
);
},
child: Text(
'Continue',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
color: Colors.pink,
padding: EdgeInsets.symmetric(
horizontal: 30.0, vertical: 15.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
)
: FlatButton(
onPressed: () {
// Navigate to the next screen when "Get Started" is pressed
},
child: Text(
'Get Started',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
color: Colors.pink,
padding: EdgeInsets.symmetric(
horizontal: 30.0, vertical: 15.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
SizedBox(height: 20.0),
],
),
),
),
);
}
// Function to build each onboarding page dynamically
Widget _buildOnboardingPage(int index) {
String imagePath;
String title;
String description;
switch (index) {
case 0:
imagePath = 'https://source.unsplash.com/random/300x300/?pizza';
title = 'Choose A Tasty Dish';
description = 'Order anything you want from your favorite restaurant.';
break;
case 1:
imagePath =
'https://source.unsplash.com/random/300x300/?breakfast';
title = 'Fast Delivery';
description = 'Your order will be delivered to your doorstep in minutes.';
break;
case 2:
imagePath = 'https://source.unsplash.com/random/300x300/?sandwich';
title = 'Enjoy Your Meal';
description = 'Sit back, relax, and enjoy your delicious meal!';
break;
default:
imagePath = '';
title = '';
description = '';
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CachedNetworkImage(
imageUrl: imagePath,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
width: 200,
height: 200,
fit: BoxFit.cover,
fadeInCurve: Curves.easeIn,
),
SizedBox(height: 30.0),
Text(
title,
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Text(
description,
style: TextStyle(
fontSize: 16.0,
color: Colors.grey[700],
),
textAlign: TextAlign.center,
),
),
],
);
}
}
Step 5: Understanding the Code
1. PageView
The PageView.builder
allows us to swipe between different pages smoothly. Each page is built dynamically by calling _buildOnboardingPage()
.
2. Page Indicators
We’ve implemented custom page indicators using AnimatedContainer
to highlight the active page with a longer pink dot while inactive pages have smaller grey dots.
3. Dynamic Button
The action button at the bottom switches between “Continue” and “Get Started” depending on whether the user is on the last page. The “Continue” button triggers a transition to the next page, whereas the “Get Started” button can be used to navigate to the next screen after onboarding.
4. Image Loading
We’re using CachedNetworkImage
to load images from external URLs. This ensures that the images are cached locally after being fetched, improving performance.
Step 6: Running the App
To run the app, use the following command:
flutter run
You should see three beautifully designed onboarding screens with images, titles, descriptions, and a smooth transition between them. The page indicator shows which screen you’re currently viewing, and the button changes to “Get Started” on the final screen.
Conclusion
In this tutorial, we learned how to create an engaging onboarding experience in Flutter. We covered how to use PageView
for screen transitions, add dynamic page indicators, and handle user interactions like swiping and button clicks. You can further customize the design by adding animations, more complex layouts, or additional functionalities such as skipping the onboarding process.
Feel free to experiment with different images, colors, and text to match your app’s branding!
Happy coding! 🚀