We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Build an onboarding experience for your mobile apps with Flutter - Part 2: Build the carousel

  • Ethiel Adiassa
March 6th, 2019
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:

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;
      }

      @override
      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.
      @override
      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});
      @override
      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();
      @override
      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)),
              ),
            ),
          ),
        );
      }

      @override
      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.

Clone the project repository
  • Android
  • Flutter
  • iOS
  • no pusher tech

Products

  • Channels
  • Beams
  • Chatkit

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.