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.
To follow along with the tutorial, you should have the following:
Now this is the code for the dots section. Copy and paste it after the MyHomePageState
class.
1//..lib/main.dart 2 3 class Dots extends StatelessWidget { 4 final IndexController controller; 5 final int slideIndex; 6 final int numberOfDots; 7 8 Dots({this.controller, this.slideIndex, this.numberOfDots}); 9 10 Widget _activeSlide(int index) { 11 return GestureDetector( 12 onTap: () { 13 print('Tapped'); 14 // controller.move(index); 15 }, 16 child: new Container( 17 child: Padding( 18 padding: EdgeInsets.only(left: 8.0, right: 8.0), 19 child: Container( 20 width: 20.0, 21 height: 20.0, 22 decoration: BoxDecoration( 23 color: Colors.orangeAccent.withOpacity(.3), 24 borderRadius: BorderRadius.circular(50.0), 25 ), 26 ), 27 ), 28 ), 29 ); 30 } 31 32 Widget _inactiveSlide(int index) { 33 return GestureDetector( 34 onTap: () { 35 controller.move(index); 36 }, 37 child: new Container( 38 child: Padding( 39 padding: EdgeInsets.only(left: 5.0, right: 5.0), 40 child: Container( 41 width: 14.0, 42 height: 14.0, 43 decoration: BoxDecoration( 44 color: Colors.grey.withOpacity(0.7), 45 borderRadius: BorderRadius.circular(50.0)), 46 ), 47 ), 48 ), 49 ); 50 } 51 52 List<Widget> _generateDots() { 53 List<Widget> dots = []; 54 for (int i = 0; i < numberOfDots; i++) { 55 dots.add(i == slideIndex ? _activeSlide(i) : _inactiveSlide(i)); 56 } 57 return dots; 58 } 59 60 @override 61 Widget build(BuildContext context) { 62 return Center( 63 child: Row( 64 mainAxisAlignment: MainAxisAlignment.center, 65 children: _generateDots(), 66 )); 67 } 68 }
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:
1onTap: () { 2 controller.move(index); 3 },
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:
1return Center( 2 child: Row( 3 mainAxisAlignment: MainAxisAlignment.center, 4 children: _generateDots(), 5 ));
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.
1new ParallaxContainer( 2 position: info.position, 3 translationFactor: 500.0, 4 child: Dots( 5 controller: controller, 6 slideIndex: _slideIndex, 7 numberOfDots: images.length, 8 ), 9 )
With all the parts completed, you should have the following :
1import 'package:flutter/material.dart'; 2 import 'package:transformer_page_view/transformer_page_view.dart'; 3 4 void main() => runApp(MyApp()); 5 class MyApp extends StatelessWidget { 6 // This widget is the root of your application. 7 @override 8 Widget build(BuildContext context) { 9 return MaterialApp( 10 debugShowCheckedModeBanner: false, 11 title: 'Flutter Demo', 12 theme: ThemeData( 13 // This is the theme of your application. 14 // 15 // Try running your application with "flutter run". You'll see the 16 // application has a blue toolbar. Then, without quitting the app, try 17 // changing the primarySwatch below to Colors.green and then invoke 18 // "hot reload" (press "r" in the console where you ran "flutter run", 19 // or simply save your changes to "hot reload" in a Flutter IDE). 20 // Notice that the counter didn't reset back to zero; the application 21 // is not restarted. 22 primarySwatch: Colors.blue, 23 ), 24 home: MyHomePage(title: 'Flutter Demo Home Page'), 25 ); 26 } 27 } 28 class MyHomePage extends StatefulWidget { 29 final String title; 30 MyHomePage({this.title}); 31 @override 32 MyHomePageState createState() { 33 return new MyHomePageState(); 34 } 35 } 36 37 class MyHomePageState extends State<MyHomePage> { 38 int _slideIndex = 0; 39 final GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>(); 40 final List<String> images = [ 41 "assets/slide_1.png", 42 "assets/slide_2.png", 43 "assets/slide_3.png", 44 "assets/slide_4.png" 45 ]; 46 47 List<Color> colors = [Colors.orange]; 48 final List<String> text0 = [ 49 "Welcome in your app", 50 "Enjoy teaching...", 51 "Showcase your skills", 52 "Friendship is great" 53 ]; 54 55 final List<String> text1 = [ 56 "App for food lovers, satisfy your taste", 57 "Find best meals in your area, simply", 58 "Have fun while eating your relatives and more", 59 "Meet new friends from all over the world" 60 ]; 61 62 final IndexController controller = IndexController(); 63 @override 64 Widget build(BuildContext context) { 65 TransformerPageView transformerPageView = TransformerPageView( 66 pageSnapping: true, 67 onPageChanged: (index) { 68 setState(() { 69 this._slideIndex = index; 70 }); 71 }, 72 loop: false, 73 controller: controller, 74 transformer: new PageTransformerBuilder( 75 builder: (Widget child, TransformInfo info) { 76 return new Material( 77 color: Colors.white, 78 elevation: 8.0, 79 textStyle: new TextStyle(color: Colors.white), 80 borderRadius: new BorderRadius.circular(12.0), 81 child: new Container( 82 alignment: Alignment.center, 83 color: Colors.white, 84 child: Padding( 85 padding: const EdgeInsets.all(18.0), 86 child: Column( 87 mainAxisAlignment: MainAxisAlignment.center, 88 crossAxisAlignment: CrossAxisAlignment.center, 89 children: <Widget>[ 90 new ParallaxContainer( 91 child: new Text( 92 text0[info.index], 93 style: new TextStyle( 94 color: Colors.blueGrey, 95 fontSize: 34.0, 96 fontFamily: 'Quicksand', 97 fontWeight: FontWeight.bold), 98 ), 99 position: info.position, 100 opacityFactor: .8, 101 translationFactor: 400.0, 102 ), 103 SizedBox( 104 height: 45.0, 105 ), 106 new ParallaxContainer( 107 child: new Image.asset( 108 images[info.index], 109 fit: BoxFit.contain, 110 height: 350, 111 ), 112 position: info.position, 113 translationFactor: 400.0, 114 ), 115 SizedBox( 116 height: 45.0, 117 ), 118 new ParallaxContainer( 119 child: new Text( 120 text1[info.index], 121 textAlign: TextAlign.center, 122 style: new TextStyle( 123 color: Colors.blueGrey, 124 fontSize: 28.0, 125 fontFamily: 'Quicksand', 126 fontWeight: FontWeight.bold), 127 ), 128 position: info.position, 129 translationFactor: 300.0, 130 ), 131 SizedBox( 132 height: 55.0, 133 ), 134 new ParallaxContainer( 135 position: info.position, 136 translationFactor: 500.0, 137 child: Dots( 138 controller: controller, 139 slideIndex: _slideIndex, 140 numberOfDots: images.length, 141 ), 142 ) 143 ], 144 ), 145 ), 146 ), 147 ); 148 }), 149 itemCount: 4); 150 151 return Scaffold( 152 backgroundColor: Colors.white, 153 body: transformerPageView, 154 ); 155 } 156 } 157 class Dots extends StatelessWidget { 158 159 final IndexController controller; 160 final int slideIndex; 161 final int numberOfDots; 162 Dots({this.controller, this.slideIndex, this.numberOfDots}); 163 164 List<Widget> _generateDots() { 165 List<Widget> dots = []; 166 for (int i = 0; i < numberOfDots; i++) { 167 dots.add(i == slideIndex ? _activeSlide(i) : _inactiveSlide(i)); 168 } 169 return dots; 170 } 171 172 Widget _activeSlide(int index) { 173 return GestureDetector( 174 onTap: () { 175 print('Tapped'); 176 }, 177 child: new Container( 178 child: Padding( 179 padding: EdgeInsets.only(left: 8.0, right: 8.0), 180 child: Container( 181 width: 20.0, 182 height: 20.0, 183 decoration: BoxDecoration( 184 color: Colors.orangeAccent.withOpacity(.3), 185 borderRadius: BorderRadius.circular(50.0), 186 ), 187 ), 188 ), 189 ), 190 ); 191 } 192 193 Widget _inactiveSlide(int index) { 194 return GestureDetector( 195 onTap: () { 196 controller.move(index); 197 }, 198 child: new Container( 199 child: Padding( 200 padding: EdgeInsets.only(left: 5.0, right: 5.0), 201 child: Container( 202 width: 14.0, 203 height: 14.0, 204 decoration: BoxDecoration( 205 color: Colors.grey.withOpacity(0.7), 206 borderRadius: BorderRadius.circular(50.0)), 207 ), 208 ), 209 ), 210 ); 211 } 212 213 @override 214 Widget build(BuildContext context) { 215 return Center( 216 child: Row( 217 mainAxisAlignment: MainAxisAlignment.center, 218 children: _generateDots(), 219 )); 220 } 221 }
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 🙃
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.