How to implement onboarding screen PageView in Flutter

In this article, we will discuss how to implement a PageView in a flutter. What shall we do today is to create an Onboarding Screen with the help of PageView.Builder. At first, we should know what is PageView in flutter is.

PageView is a widget that generates scrollable pages on the screen that can either be a fixed list of pages or a builder function that builds repeating pages. PageView acts similar to an ListView in the sense of constructing elements. However, PageView.builder constructor plays also a crucial role and acts differently than PageView. PageView.builder creates a scrollable list that works page by page using widgets. When we need to show a large number of children, this PageView.builder constructor is incomparable. Moreover, the builder is called only for those children that are actually visible.

The PageView widget allows the user to transition between different screens in their flutter application. All you need to set it up are a PageViewController and a PageView.

The types of PageView are:

  1. PageView
  2. PageView.builder
  3. PageView.custom

We are using PageView.builder for our application.

Also read: How To Implement Animated Icon In Flutter

Some of the properties of PageView.builder are given below

scrollDirection It sets the axis of scrolling (Vertical or horizontal)
controllerIt is used to control the pages
onPageChangedThis is called when page change occurs
pageSnappingIt takes a boolean value to determine whether the page snapping will be on or off for the PageView widget.
reverse It defines the scrolling direction. By default, it is set to false
dragStartBehaviour holds DragStartBehavior enum (final) as the object. It controls the way in which the drag behaviour will start to be registered.
itemCountno of the item to be displayed on the screen
clipBehaviourcontrols whether the content inside the PageView widget will be clipped or not.
itemBuildercreate a scrollable list that works page by page on the screen

Let’s take an example of how we can implement the PageView.builder in our flutter application.

we will create a new model name Fruit that includes the String of name description and the URL of that class name Fruit and store it into a new file inside the lib folder name fruit_model.dart.

class Fruit {
  String name, desc, url;
  Friut({
    required this.name,
    required this.desc,
    required this.url,
  });
}

Now we will again create a new file name as fruit_data.dart that includes the dummy data.

import 'package:vlogpost/model/student.dart';

List<Friut> friuts= [
  Friut(
      name: 'Apple',
      desc:
          'An apple is an edible fruit produced by an apple tree (Malus domestica). Apple trees are cultivated worldwide and are the most widely grown species in the',
      url:
          'assets/apple.jpg'),
  Friut(
      name: 'Banana',
      desc:
          'banana is an elongated, edible fruit - botanically a berry - produced by several kinds of large herbaceous flowering plants in the genus Musa.',
      url:
          'assets/banana.jpg'),
  Friut(
      name: 'Grape',
      desc:
          'The grapefruit (Citrus × paradisi) is a subtropical citrus tree known for its relatively large, sour to semi-sweet, somewhat bitter fruit.',
      url:
          'assets/grape.jpg'),
  Friut(
      name: 'Mango',
      desc:
          'A mango is an edible stone fruit produced by the tropical tree Mangifera indica which is believed to have originated from the region between northwestern ...',
      url:
          'assets/mango.jpg'),
  Friut(
      name: 'Orange',
      desc:
          'An orange is a fruit of various citrus species in the family Rutaceae ); it primarily refers to Citrus × sinensis, which is also called sweet orange',
      url:
          'assets/orange.jpg'),
  Friut(
      name: 'WaterMelon',
      desc:
          'Watermelon is grown in favorable climates from tropical to temperate regions worldwide for its large edible fruit',
      url:
          'assets/watermelon.jpg'),
  Friut(
      name: 'Papaya',
      desc:
          'Papayas grow in tropical climates and are also known as papaws or pawpaws. Their sweet taste, vibrant color, and the wide variety of health benefits they ',
      url:
          'assets/papaya.jpg'),
  Friut(
      name: 'Coconut',
      desc:
          'A coconut is the edible fruit of the coconut palm (Cocos nucifera), a tree of the palm family. ',
      url:
          'assets/coconut.jpg'),
  
];

In the above line of code, I have used the local image name as an assets folder to store the image of fruits. for these, I have taken different fruit images which are shown below.

The Next Step is to include the assets folder inside the pubspec. yaml which is shown below

# To add assets to your application, add an assets section, like this:
  assets:
    - assets/

Now let’s implement the code in the Flutter application.

import 'package:flutter/material.dart';
import 'package:vlogpost/data/food_data.dart';

import 'package:vlogpost/model/student.dart';
import 'package:vlogpost/screen/shimmer_effect.dart';

class PageViewScreen extends StatefulWidget {
  const PageViewScreen({Key? key}) : super(key: key);

  @override
  State<PageViewScreen> createState() => _PageViewScreenState();
}

class _PageViewScreenState extends State<PageViewScreen> {
  List<Fruit> fruit = [];

  @override
  void initState() {
    fruit = List.of(fruits);
    _pageController.addListener(() {
      setState(() {
        currentPage = _pageController.page!.toInt();
      });
    });
    super.initState();
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  int currentPage = 0;
  final _pageController = PageController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
            child: Column(
      children: [
        Padding(
          padding: const EdgeInsets.only(
            right: 15.0,
            top: 10,
          ),
          child: Align(
            alignment: Alignment.centerRight,
            child: Text(
              '$currentPage/' + '${fruit.length - 1}'.toString(),
              textScaleFactor: 1.5,
            ),
          ),
        ),
        Expanded(
          child: PageView.builder(
              itemCount: fruit.length,
              controller: _pageController,
              onPageChanged: (value) {
                setState(() {
                  currentPage = value;
                });
              },
              itemBuilder: ((context, index) {
                return OnBoardingScreen(
                  desc: fruit[index].desc,
                  image: fruit[index].url,
                  title: fruit[index].name,
                );
              })),
        ),
        Padding(
          padding: const EdgeInsets.only(
            right: 15.0,
            bottom: 10,
          ),
          child: Row(
            children: [
              ...List.generate(
                fruit.length,
                ((index) {
                  return Padding(
                    padding: const EdgeInsets.only(left: 10.0),
                    child: DotIndicator(
                      isActive: index == currentPage,
                    ),
                  );
                }),
              ),
              const Spacer(),
              InkWell(
                onTap: () {
                  _pageController.nextPage(
                      duration: const Duration(milliseconds: 800),
                      curve: Curves.easeInOutQuint);
                  Navigator.push(context,
                      MaterialPageRoute(builder: (_) => const ShimmerScreen()));
                },
                child: AnimatedContainer(
                  alignment: Alignment.center,
                  duration: const Duration(milliseconds: 300),
                  height: 70,
                  width: (currentPage == (fruit.length - 1)) ? 200 : 75,
                  decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(35)),
                  child: (currentPage == (fruit.length - 1))
                      ? const Text(
                          "Get Started",
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 20,
                          ),
                        )
                      : const Icon(
                          Icons.navigate_next,
                          size: 50,
                          color: Colors.white,
                        ),
                ),
              ),
            ],
          ),
        ),
      ],
    )));
  }
}

class DotIndicator extends StatelessWidget {
  const DotIndicator({Key? key, this.isActive = false}) : super(key: key);

  final bool isActive;
  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      height: 10,
      width: isActive ? 20 : 5,
      decoration: BoxDecoration(
          color: isActive ? Colors.blue : Colors.green,
          borderRadius: BorderRadius.circular(12)),
    );
  }
}

class OnBoardingScreen extends StatelessWidget {
  const OnBoardingScreen({
    Key? key,
    required this.desc,
    required this.image,
    required this.title,
  }) : super(key: key);
  final String title, desc, image;
  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(
        horizontal: 15,
        vertical: 10,
      ),
      elevation: 5,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(20),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Image.asset(
            image,
            fit: BoxFit.fill,
          ),
          Text(
            title,
            textAlign: TextAlign.center,
            style: Theme.of(context).textTheme.headline5!.copyWith(
                  fontWeight: FontWeight.w600,
                ),
          ),
          const SizedBox(
            height: 10,
          ),
          Text(
            desc,
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

In the above code, we have used 2 custom widgets one for displaying information about the fruit name as OnBoardingScreen. The second custom widget we have used for the indicator for sliding the widget name is DotIndicator Screen.On Navigating to its details screen the code is shown below

import 'package:flutter/material.dart';
import 'package:vlogpost/Widget/shimmer.dart';
import 'package:vlogpost/data/food_data.dart';
import 'package:vlogpost/model/student.dart';

class ShimmerScreen extends StatefulWidget {
  const ShimmerScreen({Key? key}) : super(key: key);

  @override
  State<ShimmerScreen> createState() => _ShimmerScreenState();
}

class _ShimmerScreenState extends State<ShimmerScreen> {
  List<Fruit> food = [];

  @override
  void initState() {
    getFood();
    super.initState();
  }

  Future getFood() async {
    setState(() {
      showEffect = true;
    });
    await Future.delayed(const Duration(seconds: 2), () {});
    food = List.of(fruits);
    setState(() {
      showEffect = false;
    });
  }

  bool showEffect = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shimmer Effect'),
        centerTitle: true,
      ),
      body: ListView.builder(
          itemCount: showEffect ? 8 : food.length,
          itemBuilder: (context, index) {
            if (showEffect) {
              return foodShimmerEffect();
            } else {
              final foodData = food[index];
              return showFoodsList(foodData);
            }
          }),
    );
  }

  Widget showFoodsList(Fruit food) => Card(
        child: ListTile(
          title: Text(
            food.name,
            style: const TextStyle(fontSize: 18),
          ),
          subtitle: Text(
            food.desc,
            maxLines: 1,
            style: const TextStyle(fontSize: 14),
          ),
          leading: CircleAvatar(
            radius: 40,
            backgroundImage: AssetImage(
              food.url,
            ),
          ),
        ),
      );

  Widget foodShimmerEffect() => ListTile(
        title: Align(
          alignment: Alignment.bottomLeft,
          child: ShimmerWidget.rectangular(
              width: MediaQuery.of(context).size.width * 0.3, height: 18),
        ),
        subtitle: const ShimmerWidget.rectangular(height: 14),
        leading: const ShimmerWidget.circular(
          height: 80,
          width: 70,
        ),
      );
}

Meanwhile, when we run this application in the emulator or device, we should get the UI similar to the screenshot below:

Thank you for reading the Guide about how to implement the PageView in flutter.

Also read: How to implement Hero Animation in Flutter.