In this lesson, we want to explore a project teaching us how to create a stepper in flutter. A stepper is also called a a wizard and provides us a widget we can use to show content that require steps.
Let's start.
Start by adding the following dependencies in your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
shared_preferences: ^0.4.2
fluttertoast: ^2.0.7
(a). /Constant/Constant.dart
In your lib folder create a folder called Constant
. Inside this Constant folder create a Constant.dart
file and add the following code:
String
STEPPER_SCREEN = '/StepperScreen',
ANIMATED_SPLASH = '/SplashScreen';
(b). /Screens/StepperScreen.dart
This class will create a splash screen for the flutter app using a Timer.
Start by adding imports:
import 'dart:async';
import 'package:flutter_stepper_demo/Constant/Constant.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Create a widget to represent the SplashScreen:
class SplashScreen extends StatefulWidget {
@override
SplashScreenState createState() => new SplashScreenState();
}
Create a state class with several instance fields:
class SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
var _visible = true;
AnimationController animationController;
Animation<double> animation;
Create an async method called startTime()
:
startTime() async {
}
Inside the method start by instantiating the Duration
class:
var _duration = new Duration(seconds: 3);
Then return a Timer
object:
return new Timer(_duration, navigationPage);
Then create a method to navigate to another page:
void navigationPage() {
Navigator.of(context).pushReplacementNamed(STEPPER_SCREEN);
}
In the initState()
method, do several initializations:
@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this, duration: new Duration(seconds: 2));
animation =
new CurvedAnimation(parent: animationController, curve: Curves.easeOut);
animation.addListener(() => this.setState(() {}));
animationController.forward();
setState(() {
_visible = !_visible;
});
startTime();
}
Finally override the build()
method of the Widget class:
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 30.0),
child: new Image.asset(
'assets/images/powered_by.png',
height: 25.0,
fit: BoxFit.scaleDown,
))
],
),
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.asset(
'assets/images/logo.png',
width: animation.value * 250,
height: animation.value * 250,
),
],
),
],
),
);
}
Here is the whole code for this class:
import 'dart:async';
import 'package:flutter_stepper_demo/Constant/Constant.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
@override
SplashScreenState createState() => new SplashScreenState();
}
class SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
var _visible = true;
AnimationController animationController;
Animation<double> animation;
startTime() async {
var _duration = new Duration(seconds: 3);
return new Timer(_duration, navigationPage);
}
void navigationPage() {
Navigator.of(context).pushReplacementNamed(STEPPER_SCREEN);
}
@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this, duration: new Duration(seconds: 2));
animation =
new CurvedAnimation(parent: animationController, curve: Curves.easeOut);
animation.addListener(() => this.setState(() {}));
animationController.forward();
setState(() {
_visible = !_visible;
});
startTime();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 30.0),
child: new Image.asset(
'assets/images/powered_by.png',
height: 25.0,
fit: BoxFit.scaleDown,
))
],
),
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.asset(
'assets/images/logo.png',
width: animation.value * 250,
height: animation.value * 250,
),
],
),
],
),
);
}
}
(c). /Screens/StepperScreen.dart
This is the wizard screen. Start by defining the imports:
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter/services.dart';
Then a widget to represent the Stepper Screen:
class StepperScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Stepper',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
accentColor: Colors.red,
primaryColor: Colors.red,
primaryColorDark: Colors.red),
home: StepScreen(
title: 'Stepper App',
),
);
}
}
Then a widget to represent a single step:
class StepScreen extends StatefulWidget {
final String title;
StepScreen({this.title});
@override
_StepScreenState createState() => _StepScreenState();
}
Here is the full code for this class:
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter/services.dart';
class StepperScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Stepper',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
accentColor: Colors.red,
primaryColor: Colors.red,
primaryColorDark: Colors.red),
home: StepScreen(
title: 'Stepper App',
),
);
}
}
class StepScreen extends StatefulWidget {
final String title;
StepScreen({this.title});
@override
_StepScreenState createState() => _StepScreenState();
}
class _StepScreenState extends State<StepScreen> {
/*login*/
static TextEditingController email_controller = new TextEditingController();
static TextEditingController password_controller = new TextEditingController();
static bool loginSucess = false;
/*Signup*/
static final TextEditingController name_controller = new TextEditingController();
static final TextEditingController re_password_controller = new TextEditingController();
static int currStep = 0;
static var _focusNode = new FocusNode();
GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
@override
void initState() {
super.initState();
_focusNode.addListener(() {
setState(() {});
print('Has focus: $_focusNode.hasFocus');
});
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
List<Step> steps = [
new Step(
title: const Text('SignUp'),
isActive: true,
state: StepState.indexed,
content: _signupUi()),
new Step(
title: const Text('LogIn'),
isActive: true,
state: StepState.indexed,
content: _loginUi()),
new Step(
title: const Text('Home'),
isActive: true,
state: StepState.complete,
content: new HomeScreen("Aeologic Technologies")),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
style: TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
body: new Container(
child: new ListView(children: <Widget>[
new Stepper(
steps: steps,
type: StepperType.vertical,
currentStep: currStep,
onStepContinue: () {
setState(() {
if(currStep==0){
if (!(name_controller.value.text
.toString()
.trim()
.length >
0)) {
Fluttertoast.showToast(
msg: "Please enter user name.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1);
}
else if (!(password_controller.value.text
.toString()
.trim()
.length >
0)) {
Fluttertoast.showToast(
msg: "Please enter the password.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1);
} else if (!(re_password_controller.value.text
.toString()
.trim()
.length >
0)) {
Fluttertoast.showToast(
msg:
"Please enter the confirm password.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1);
} else {
if (re_password_controller.value.text
.toString()
.endsWith(password_controller.value.text
.toString())) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
} else {
Fluttertoast.showToast(
msg:
"Your Password and confirm password do not match.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1);
}
}
}
else if(currStep==1){
if (!(email_controller.value.text
.trim()
.toString()
.length >
1)) {
Fluttertoast.showToast(
msg: "Please enter user name.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1);
} else if (!(password_controller.value.text
.trim()
.toString()
.length >
1)) {
Fluttertoast.showToast(
msg: "Please enter the password.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1);
} else {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
}
});
},
onStepCancel: () {
setState(() {
if (currStep > 0) {
currStep = currStep - 1;
} else {
currStep = 0;
}
});
},
onStepTapped: (step) {
setState(() {
currStep = step;
});
},
),
],shrinkWrap: true,reverse: true,),
),
);
}
static Widget _signupUi(){
return new Center(
child: new ListView(
shrinkWrap: true,
reverse: false,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Center(
child: new Center(
child: new Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 30.0, right: 30.0),
child: new Form(
autovalidate: false,
child: new Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Padding(
padding: EdgeInsets.only(left: 10.0, right: 10.0),
child: new TextFormField(
controller: name_controller,
decoration: new InputDecoration(
labelText: "Full Name*",
filled: false,
prefixIcon: Padding(
padding: EdgeInsets.only(bottom: 10.0,top: 10.0,left: 10.0,right: 10.0),
child: new Image.asset(
"assets/images/user_icon.png",
height: 20.0,
width: 20.0,
fit: BoxFit.scaleDown,
))),
keyboardType: TextInputType.emailAddress,
),
),
new Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0),
child: new TextFormField(
obscureText: false,
controller: email_controller,
decoration: new InputDecoration(
labelText: "Email-Id",
enabled: true,
filled: false,
prefixIcon: Padding(
padding: EdgeInsets.only(bottom: 10.0,top: 10.0,left: 10.0,right: 10.0),
child: new Image.asset(
"assets/images/email_icon.png",
height: 20.0,
width: 20.0,
fit: BoxFit.scaleDown,
))),
keyboardType: TextInputType.text,
)),
new Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0),
child: new TextFormField(
obscureText: true,
controller: password_controller,
decoration: new InputDecoration(
labelText: "Password*",
enabled: true,
filled: false,
prefixIcon: Padding(
padding: EdgeInsets.only(bottom: 10.0,top: 10.0,left: 10.0,right: 10.0),
child: new Image.asset(
"assets/images/password_icon.png",
height: 20.0,
width: 20.0,
fit: BoxFit.scaleDown,
))),
keyboardType: TextInputType.text,
)),
new Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0),
child: new TextFormField(
obscureText: true,
controller: re_password_controller,
decoration: new InputDecoration(
labelText: "Confirm Password*",
enabled: true,
filled: false,
prefixIcon: Padding(
padding: EdgeInsets.only(bottom: 10.0,top: 10.0,left: 10.0,right: 10.0),
child: new Image.asset(
"assets/images/password_icon.png",
height: 20.0,
width: 20.0,
fit: BoxFit.scaleDown,
))),
keyboardType: TextInputType.text,
)),
],
),
)),
],
),
))
],
)
],
));
}
static Widget _loginUi() {
return new Center(
child: new ListView(
shrinkWrap: true,
reverse: false,
children: <Widget>[
new SizedBox(
height: 20.0,
),
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Center(
child: new Center(
child: new Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 30.0, right: 30.0),
child: new Form(
autovalidate: false,
child: new Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Padding(
padding: EdgeInsets.only(left: 10.0, right: 10.0),
child: new TextFormField(
controller: email_controller,
autofocus: false,
decoration: new InputDecoration(
labelText: "User Name*",
prefixIcon: Padding(
padding: EdgeInsets.only(bottom: 10.0,top: 10.0,left: 10.0,right: 10.0),
child: new Image.asset(
"assets/images/user_icon.png",
height: 20.0,
width: 20.0,
fit: BoxFit.scaleDown,
)),
),
keyboardType: TextInputType.emailAddress,
),
),
new Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0),
child: new TextFormField(
obscureText: true,
autofocus: false,
controller: password_controller,
decoration: new InputDecoration(
labelText: "Password*",
prefixIcon: Padding(
padding: EdgeInsets.only(bottom: 10.0,top: 10.0,left: 10.0,right: 10.0),
child: new Image.asset(
"assets/images/email_icon.png",
height: 20.0,
width: 20.0,
fit: BoxFit.scaleDown,
))),
keyboardType: TextInputType.text,
)),
],
),
)),
],
),
))
],
)
],
));
}
}
/*HomeScreen Code start*/
class HomeScreen extends StatelessWidget {
HomeScreen(this.userName);
String userName;
@override
Widget build(BuildContext context) {
return
new Stack(
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 40.0,
),
Padding(
padding: const EdgeInsets.only(left: 0.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Welcome, ",
style: TextStyle(
fontWeight: FontWeight.normal, fontSize: 20.0)),
new Row(
children: <Widget>[],
)
],
),
),
SizedBox(
height: 15.0,
),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(userName,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 18.0))
],
),
)
],
),
Padding(
padding: const EdgeInsets.all(40.0),
child: new Center(
child: new Opacity(
opacity: 0.5,
child: new Image.asset(
"assets/images/logo.png",
)),
),
),
],
);
// TODO: implement build
}
}
(d). main.dart
Lastly come and modify the main.dart
as follows:
import 'package:flutter/material.dart';
import 'package:flutter_stepper_demo/Constant/Constant.dart';
import 'package:flutter_stepper_demo/Screens/SplashScreen.dart';
import 'package:flutter_stepper_demo/Screens/StepperScreen.dart';
main() {
runApp(new MaterialApp(
debugShowCheckedModeBanner: false,
home: new SplashScreen(),
routes: <String, WidgetBuilder>{
ANIMATED_SPLASH: (BuildContext context) => new SplashScreen(),
STEPPER_SCREEN: (BuildContext context) => new StepperScreen(),
},
));
}
Special thanks to flutter-devs for this project.
Download Project here.