Do somthings

DoSomthings logo

Hi guys! We hope you are all well. So in this article, we create a new attractive Onboarding Screen without using any dart packages. First, we understand what this Screen is and why we use it.

Onboarding Screen

After the splash screen loads, one of the most common screens you’ll see in most programs is the onboarding screen. An app’s onboarding screen provides a brief overview. The majority of the onboarding screen’s three to four layouts move as we click the next button. This article will show you how to add an onboarding screen to a Flutter app.

We also created an article on this topic, How to create Onboarding Screen Sliders on Flutter If you want to create an Onboarding Screen with the help of a package, then go there.

Onboarding Screen in Flutter gif(Dosomthings.com)

Step 1: Set up a Flutter Project

If you haven’t already, install Flutter and create a new Flutter project by running flutter create my_onboarding_app.

Step 2: Edit main.dart Pages:

This page is most important because it is the first file that is executed when your Flutter app starts running.

import 'package:flutter/material.dart';
import 'package:new_testing/OnBordingPage.dart';

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  App({super.key});

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Onboarding Screen",
      home: OnBoardingPage(),
    );
  }
}

Step 3: Crete OnBoardingPage()

This is the main UI page to show all the Onboarding Screen functionality in your app.

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:new_testing/HomeSceen.dart';
import 'package:new_testing/core/styles.dart';
import 'package:new_testing/core/widgets/wallet.dart';
import 'core/data.dart';


class OnBoardingPage extends StatefulWidget {
  const OnBoardingPage({super.key});

  @override
  State<OnBoardingPage> createState() => _OnBoardingPageState();
}

class _OnBoardingPageState extends State<OnBoardingPage>
    with SingleTickerProviderStateMixin {
  late final AnimationController animationController;
  late final Animation<double> rotationAnimation;
  late final PageController pageController;
  static const viewportFraction = 0.7;
  int activeIndex = 0;

  @override
  void initState() {
    pageController = PageController(viewportFraction: viewportFraction);
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    final curvedAnimation = CurvedAnimation(
      parent: animationController,
      curve: Curves.easeOut,
    );
    rotationAnimation =
        Tween<double>(begin: 0, end: 30 * pi / 180).animate(curvedAnimation);
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    final itemWidth = screenSize.width * viewportFraction;

    return Scaffold(
      body: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const SizedBox(height: 40),
            const Center(
              child: Text(
                'My Wallet',
                style: TextStyle(fontSize: 35),
              ),
            ),
            const SizedBox(height: 40),
            Expanded(
              child: Stack(
                clipBehavior: Clip.none,
                children: [
                   Positioned(
                    left: -250 + 40,
                    width: 250,
                    top: -32,
                    bottom: -32,
                    child: WalletSide(),
                  ),
                  Positioned.fill(
                    child: GestureDetector(
                      onTapDown: (_) => animationController.forward(),
                      onTapUp: (_) => animationController.reverse(),
                      child: PageView.builder(
                        controller: pageController,
                        itemCount: onBoardingItems.length,
                        onPageChanged: (int index) {
                          setState(() {
                            activeIndex = index;
                          });
                          animationController.forward().then(
                                (value) => animationController.reverse(),
                          );
                        },
                        itemBuilder: (context, index) {
                          return AnimatedScale(
                            duration: const Duration(milliseconds: 300),
                            scale: index == activeIndex ? 1 : 0.8,
                            curve: Curves.easeOut,
                            child: Container(
                              decoration: BoxDecoration(
                                color: AppColors.onBlack,
                                borderRadius: BorderRadius.circular(25),
                                image: DecorationImage(
                                  image: AssetImage(
                                    onBoardingItems[index].image,
                                  ),
                                  fit: BoxFit.fitWidth,
                                ),
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                  Positioned(
                    left: -250 + 35,
                    width: 250,
                    top: -30,
                    bottom: -30,
                    child: AnimatedBuilder(
                      animation: animationController,
                      builder: (context, child) {
                        return Transform(
                          transform: Matrix4.identity()
                            ..setEntry(3, 2, 0.001)
                            ..rotateY(rotationAnimation.value),
                          alignment: Alignment.center,
                          child: const WalletSide(),
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
            Padding(
              padding: EdgeInsets.only(
                left: (screenSize.width - itemWidth) / 2,
                right: (screenSize.width - itemWidth) / 2,
                top: 40,
                bottom: 50,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  ..._buildItemInfo(activeIndex: activeIndex),
                  PageIndicator(
                    length: onBoardingItems.length,
                    activeIndex: activeIndex,
                  ),
                  ElevatedButton(
                      onPressed: () {
                        Navigator.of(context).push(
                          MaterialPageRoute<void>(
                            builder: (context) => const HomeScreen(),
                          ),
                        );
                      },
                      child: const Text(
                        'Get Started!',
                        style: TextStyle(color: AppColors.white),
                      ),
                  )

                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  List<Widget> _buildItemInfo({int activeIndex = 0}) {
    return [
      Center(
        child: Text(
          onBoardingItems[activeIndex].title,
          style: const TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.w500,
          ),
        ),
      ),
      const SizedBox(height: 10),
      Center(
        child: Text(
          onBoardingItems[activeIndex].subtitle,
          style: const TextStyle(fontSize: 16),
        ),
      ),
    ];
  }
}

class PageIndicator extends StatelessWidget {
  const PageIndicator({
    super.key,
    this.length = 1,
    this.activeIndex = 0,
    this.activeColor = AppColors.primary,
  });

  final int length;
  final int activeIndex;
  final Color activeColor;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 20),
      child: SizedBox.fromSize(
        size: const Size.fromHeight(8),
        child: LayoutBuilder(
          builder: (context, constraints) {
            final size = constraints.smallest;
            final activeWidth = size.width * 0.5;
            final inActiveWidth =
                (size.width - activeWidth - (2 * length * 2)) / (length - 1);

            return Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(
                length,
                    (index) => Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 2),
                  child: AnimatedContainer(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeOut,
                    height: index == activeIndex ? 8 : 5,
                    width: index == activeIndex ? activeWidth : inActiveWidth,
                    decoration: BoxDecoration(
                      color: index == activeIndex
                          ? activeColor
                          : AppColors.onBlack,
                      borderRadius: BorderRadius.circular(10),
                    ),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

Step 4: Creating core Directory

Right-click in the lib folder -> new -> Directory and create core folder Inside the core we create three files like:

constants.dart

class Constants {
  static const double appHPadding = 16;
  static const double walletStrapWidth = 85;
  static const double walletStrapHeight = 100;
  static const double perspectiveSm = 0.0005;
  static const double perspective = 0.001;
  static const double perspectiveLg = 0.002;
}

data.dart

import 'package:flutter/material.dart';
import 'package:new_testing/core/styles.dart';

class CreditCardData {
  const CreditCardData({
    required this.id,
    required this.name,
    required this.type,
    this.number = '1234567812345678',
    this.style = CreditCardStyle.primary,
  });

  final int id;
  final String name;
  final String number;
  final CreditCardStyle style;
  final CreditCardType type;
}

enum CreditCardType {
  visa,
  masterCard;

  String get label {
    switch (this) {
      case visa:
        return 'Visa';
      case masterCard:
        return 'MasterCard';
    }
  }
}

enum CreditCardStyle {
  primary,
  secondary,
  accent,
  onBlack,
  onWhite;

  Color get color {
    switch (this) {
      case primary:
        return AppColors.primary;
      case secondary:
        return AppColors.secondary;
      case accent:
        return AppColors.accent;
      case onBlack:
        return AppColors.onBlack;
      case onWhite:
        return AppColors.onWhite;
    }
  }

  Color get textColor {
    return color.computeLuminance() > 0.3 ? AppColors.black : AppColors.white;
  }

  String get frontBg => '$name-pattern-front.png';

  String get backBg => '$name-pattern-back.png';
}

class TabItem {
  const TabItem({
    required this.view,
    this.title = '',
  });

  final String title;
  final Widget view;
}

class OnBoardingItem {
  const OnBoardingItem({
    required this.title,
    required this.subtitle,
    required this.image,
  });

  final String title;
  final String subtitle;
  final String image;
}

const List<OnBoardingItem> onBoardingItems = [
  OnBoardingItem(
    title: 'Cards',
    subtitle: 'All your cards in one place!',
    image: 'Assets/images/on-boarding-1.png',
  ),
  OnBoardingItem(
    title: 'Transactions',
    subtitle: 'Send payments quickly and easily!',
    image: 'Assets/images/on-boarding-2.png',
  ),
  OnBoardingItem(
    title: 'Insights',
    subtitle: 'View your transaction history!',
    image: 'Assets/images/on-boarding-3.png',
  ),
];

const cards = [
  CreditCardData(
    id: 0,
    name: 'Central Bank',
    type: CreditCardType.visa,
  ),
  CreditCardData(
    id: 1,
    name: 'Bank of Commerce',
    style: CreditCardStyle.secondary,
    type: CreditCardType.masterCard,
  ),
  CreditCardData(
    id: 2,
    name: 'Central Bank',
    style: CreditCardStyle.accent,
    type: CreditCardType.visa,
  ),
  CreditCardData(
    id: 3,
    name: 'Central Bank',
    style: CreditCardStyle.onBlack,
    type: CreditCardType.masterCard,
  ),
  CreditCardData(
    id: 4,
    name: 'Bank of Commerce',
    style: CreditCardStyle.onWhite,
    type: CreditCardType.visa,
  ),
];

class Transaction {
  const Transaction({
    required this.title,
    required this.date,
    required this.amount,
    required this.icon,
  });

  final String title;
  final String date;
  final double amount;
  final String icon;
}

const transactions = <Transaction>[
  Transaction(
    title: 'Renew Subscription',
    date: 'July 23rd, 2023',
    amount: -400.5,
    icon: 'assets/icons/youtube.png',
  ),
  Transaction(
    title: 'Payment received',
    date: 'June 23rd, 2023',
    amount: 2000.5,
    icon: 'assets/icons/tiktok.png',
  ),
  Transaction(
    title: 'Renew Subscription',
    date: 'July 23rd, 2023',
    amount: -200.5,
    icon: 'assets/icons/twitter.png',
  ),
  Transaction(
    title: 'Renew Subscription',
    date: 'July 23rd, 2023',
    amount: -15.5,
    icon: 'assets/icons/youtube.png',
  ),
  Transaction(
    title: 'Renew Subscription',
    date: 'July 23rd, 2023',
    amount: -35.5,
    icon: 'assets/icons/twitter.png',
  ),
];

styles.dart

import 'package:flutter/material.dart';

class AppColors {
  static const Color primary = Color(0xff1A77FF);
  static const Color secondary = Color(0xffFFBB05);
  static const Color accent = Color(0xffF5C3D2);
  static const Color border = Color(0xff505254);
  static const Color danger = Color(0xffF70000);
  static const Color success = Color(0xff04B616);
  static const Color black = Color(0xff1A1A1A);
  static const Color onBlack = Color(0xff353535);
  static const Color onWhite = Color(0xffEBEBEB);
  static const Color white = Color(0xffF2F2F2);
}

class AppBorderRadius {
  static const double sm = 5;
  static const double md = 10;
  static const double lg = 15;
  static const double xl = 20;
  static const double xxl = 25;
}

class AppThemes {
  static ThemeData getTheme({bool isDark = true}) {
    return ThemeData(
      fontFamily: 'Raleway',
      scaffoldBackgroundColor: AppColors.black,
      dividerColor: AppColors.border,
      colorScheme: const ColorScheme.dark(
        primary: AppColors.primary,
        secondary: AppColors.secondary,
        background: AppColors.black,
        onBackground: AppColors.onBlack,
        shadow: AppColors.black,
      ),
      splashFactory: NoSplash.splashFactory,
      useMaterial3: true,
      appBarTheme: const AppBarTheme(
        backgroundColor: Colors.transparent,
        scrolledUnderElevation: 0,
        elevation: 0,
      ),
      navigationBarTheme: const NavigationBarThemeData(
        backgroundColor: Colors.transparent,
        elevation: 0,
        indicatorColor: Colors.transparent,
      ),
      // filledButtonTheme: FilledButtonThemeData(
      //   style: FilledButton.styleFrom(
      //     shape: RoundedRectangleBorder(
      //       borderRadius: BorderRadius.circular(15),
      //     ),
      //     fixedSize: const Size.fromHeight(50),
      //   ),
      // ),
      // dropdownMenuTheme: DropdownMenuThemeData(
      //   inputDecorationTheme: InputDecorationTheme(
      //     filled: true,
      //     border: OutlineInputBorder(
      //       borderRadius: BorderRadius.circular(12),
      //       borderSide: const BorderSide(width: 0, color: AppColors.border),
      //     ),
      //     enabledBorder: OutlineInputBorder(
      //       borderRadius: BorderRadius.circular(12),
      //       borderSide: const BorderSide(width: 0, color: AppColors.border),
      //     ),
      //     fillColor: AppColors.black,
      //   ),
      //   menuStyle: MenuStyle(
      //     backgroundColor:
      //     const MaterialStatePropertyAll<Color>(AppColors.black),
      //     elevation: const MaterialStatePropertyAll<double>(0),
      //     shape: MaterialStatePropertyAll<OutlinedBorder>(
      //       RoundedRectangleBorder(
      //         side: const BorderSide(color: AppColors.border),
      //         borderRadius: BorderRadius.circular(15),
      //       ),
      //     ),
      //   ),
      // ),
      inputDecorationTheme: InputDecorationTheme(
        filled: true,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12),
          borderSide: const BorderSide(width: 0, color: Colors.transparent),
        ),
        fillColor: AppColors.black,
        enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12),
          borderSide: const BorderSide(width: 0, color: AppColors.border),
        ),
        disabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12),
          borderSide: const BorderSide(width: 0, color: AppColors.border),
        ),
      ),
    );
  }

  static ThemeData get darkTheme => getTheme();
}

Step 5: Creating more pages

Inside the core we create a widget directory and a widget folder we create two dart files like:

dashed_container.dart

import 'dart:math';

import 'package:flutter/material.dart';

class DashedBorderContainer extends StatelessWidget {
  const DashedBorderContainer({
    super.key,
    this.width,
    this.height,
    this.child,
    this.borderColor,
    this.borderRadius,
    this.borderWidth = 1,
    this.dash = 10,
    this.gap = 10,
  });

  final double? width;
  final double? height;
  final Widget? child;
  final Color? borderColor;
  final BorderRadius? borderRadius;
  final double dash;
  final double gap;
  final double borderWidth;

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: CustomPaint(
        painter: DashedBorderPainter(
          dashColor: borderColor ?? Theme.of(context).dividerColor,
          borderRadius: borderRadius ?? BorderRadius.zero,
          dash: dash,
          gap: gap,
          borderWidth: borderWidth,
        ),
        child: child,
      ),
    );
  }
}

class DashedBorderPainter extends CustomPainter {
  DashedBorderPainter({
    this.dash = 5.0,
    this.gap = 3.0,
    this.dashColor = Colors.black,
    this.borderRadius = BorderRadius.zero,
    this.borderWidth = 1,
  });

  final double dash;
  final double gap;
  final Color dashColor;
  final BorderRadius borderRadius;
  final double borderWidth;

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = dashColor
      ..strokeWidth = borderWidth
      ..style = PaintingStyle.stroke;

    _drawDashedPath(
      canvas,
      paint,
      RRect.fromLTRBAndCorners(
        0,
        0,
        size.width,
        size.height,
        bottomLeft: borderRadius.bottomLeft,
        topLeft: borderRadius.topLeft,
        topRight: borderRadius.topRight,
        bottomRight: borderRadius.bottomRight,
      ),
    );
  }

  void _drawDashedPath(Canvas canvas, Paint paint, RRect rrect) {
    final path = Path()..addRRect(rrect);
    final dashLength = dash + gap;

    for (final metric in path.computeMetrics()) {
      final totalLength = metric.length;

      for (var distance = 0.0; distance < totalLength; distance += dashLength) {
        final start = distance;
        final double end = min(distance + dash, totalLength);
        final extractPath = metric.extractPath(start, end);
        canvas.drawPath(extractPath, paint);
      }
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

wallet.dart

import 'package:flutter/material.dart';
import 'package:new_testing/core/constant.dart';
import 'package:new_testing/core/styles.dart';
import 'package:new_testing/core/widgets/dashed_container.dart';


class Wallet extends StatelessWidget {
  const Wallet({
    required this.width,
    required this.height,
    super.key,
    this.onAddPressed,
    this.strapRotation = 0,
    this.bodyRotation = 0,
  });

  final double width;
  final double height;
  final VoidCallback? onAddPressed;
  final double strapRotation;
  final double bodyRotation;

  @override
  Widget build(BuildContext context) {
    return  SizedBox.expand(
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          Positioned(
            top: -12,
            left: 0,
            right: 0,
            child: Center(
              child: _WalletStrapSide(),
            ),
          ),
          WalletSide(),
          Positioned(
            top: -12,
            left: 0,
            right: 0,
            child: Center(
              child: _WalletStrapSide(),
            ),
          ),
        ],
      ),
    );
  }
}

class _WalletStrapSide extends StatelessWidget {
  const _WalletStrapSide();

  @override
  Widget build(BuildContext context) {
    return Container(
      width: Constants.walletStrapWidth,
      height: Constants.walletStrapHeight,
      decoration: BoxDecoration(
        color: Theme.of(context).colorScheme.onBackground,
        border: Border.all(color: Theme.of(context).dividerColor),
        borderRadius: const BorderRadius.only(
          topLeft: Radius.circular(AppBorderRadius.sm),
          topRight: Radius.circular(AppBorderRadius.sm),
          bottomRight: Radius.circular(Constants.walletStrapWidth / 2),
          bottomLeft: Radius.circular(Constants.walletStrapWidth / 2),
        ),
        boxShadow: [
          BoxShadow(
            color: Theme.of(context).colorScheme.shadow.withOpacity(0.5),
            blurRadius: 10,
          ),
        ],
      ),
      padding: const EdgeInsets.all(3),
      child: const DashedBorderContainer(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(AppBorderRadius.sm),
          topRight: Radius.circular(AppBorderRadius.sm),
          bottomRight: Radius.circular(Constants.walletStrapWidth / 2),
          bottomLeft: Radius.circular(Constants.walletStrapWidth / 2),
        ),
        dash: 3,
        gap: 3,
        borderWidth: 0.5,
      ),
    );
  }
}

class WalletSide extends StatelessWidget {
  const WalletSide({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Theme.of(context).colorScheme.onBackground,
        border: Border.all(color: Theme.of(context).dividerColor),
        borderRadius: BorderRadius.circular(AppBorderRadius.xl),
        boxShadow: [
          BoxShadow(
            color: Theme.of(context).colorScheme.shadow.withOpacity(0.5),
            blurRadius: 10,
          ),
        ],
      ),
      padding: const EdgeInsets.all(4),
      child: DashedBorderContainer(
        borderRadius: BorderRadius.circular(AppBorderRadius.xl),
        dash: 3,
        gap: 3,
        borderWidth: 0.5,
      ),
    );
  }
}

Step 5: Testing and Customization:

Test your screen and customize it as needed. You can adjust the colors, images, text, and animations to match your app’s branding.

Output

Onboarding Screen in Flutter1(Dosomthings.com)

Conclusion

To summarize, creating an this screen in Flutter involves designing individual screens, managing the state of the process, and implementing navigation between screens. You can use state management solutions like Provider or GetX to efficiently handle the state. The PageView widget is useful for displaying multiple screens, and you can use buttons or gestures to navigate through them. Once the onboarding is complete, users can be redirected to the main app content.

❤️❤️ Thanks for reading this article, ❤️❤️

If I got something wrong? Let me know in the comments. I would love to improve 🥰🥰🥰.

Clap 👏👏👏 If this article helps you.

If you like our work, please follow us on this Dosomthings.