Criar um aplicativo de Shop UI no Flutter

Tempo de leitura: 5 minutes

Neste tutorial, mostrarei como criar um aplicativo simples de Shop usando flutter; Neste tutorial, você aprenderá como criar uma onboard simples, telas de login e registro, uma home page com varias funcionalidades além drawer especial e como criar um layout limpo para o seu aplicativo

Abaixo as fotos de como o aplicativo é….

 

Código fonte (apenas parte do código)

main.dart

import 'package:flutter/material.dart';

import 'src/screens/onboarding/onboarding_page.dart';
import 'src/components/themes/theme.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: AppTheme(context).lightTheme,
      home: const OnboardingPage(),
    );
  }
}

->src->screen->onboarding

onboarding_page.dart

import 'package:flutter/material.dart';

import '../auth/login_page.dart';
import 'components/onboarding_content_view.dart';
import 'data/onboarding_data.dart';

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

  @override
  State<OnboardingPage> createState() => _OnboardingPageState();
}

class _OnboardingPageState extends State<OnboardingPage> {
  late PageController _pageController;
  int currentPage = 0;

  /// When the next button is pressed if we are on first page we will go to second
  /// page, otherwise we will go to login page
  void _onNextButtonPressed() {
    if (currentPage + 1 == OnboardingData.boards.length) {
      _goToLoginPage();
    } else {
      int newPage = currentPage + 1;
      _pageController.animateToPage(
        newPage,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeIn,
      );
    }
    setState(() {});
  }

  void _goToLoginPage() {
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (context) => const LoginPage()));
  }

  @override
  void initState() {
    super.initState();
    _pageController = PageController();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //backgroundColor: ,
      appBar: AppBar(
        leading: Padding(
          padding: const EdgeInsets.only(left: 16),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              // Active Page
              Text(
                '${currentPage + 1}',
                style: Theme.of(context).textTheme.bodyLarge,
              ),
              // Total Pages
              Text(
                '/${OnboardingData.boards.length}',
                style: Theme.of(context)
                    .textTheme
                    .bodyLarge
                    ?.copyWith(color: Colors.grey),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: _goToLoginPage,
            child: const Text('Skip'),
          ),
        ],
      ),

      /// Next button is inside [OnboardingContentView] widget
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          /// Image
          Expanded(
            child: PageView.builder(
              itemBuilder: (context, index) {
                return OnboardingContentView(
                  board: OnboardingData.boards[index],
                  currentIndex: index,
                  onNext: _onNextButtonPressed,
                );
              },
              onPageChanged: (v) {
                currentPage = v;
                setState(() {});
              },
              controller: _pageController,
              itemCount: OnboardingData.boards.length,
            ),
          ),
        ],
      ),
    );
  }
}

->src->onboarding->components

onboarding_content_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_iconly/flutter_iconly.dart';

import '../../../components/network_image.dart';
import '../../../constants/constants.dart';
import '../data/onboarding_data.dart';

class OnboardingContentView extends StatelessWidget {
  const OnboardingContentView({
    super.key,
    required this.board,
    required this.currentIndex,
    required this.onNext,
  });

  final OnboardingModel board;
  final int currentIndex;
  final void Function() onNext;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Padding(
          padding: const EdgeInsets.all(AppDefaults.padding),
          child: AspectRatio(
            aspectRatio: 1 / 1,
            child: NetworkImageWithLoader(
              board.imageLink,
              fit: BoxFit.cover,
            ),
          ),
        ),

        /// Title, Subtitle, Button
        Padding(
          padding: const EdgeInsets.all(AppDefaults.padding * 2),
          child: Column(
            children: [
              Text(
                board.title,
                style: Theme.of(context).textTheme.titleLarge,
              ),
              const SizedBox(height: AppDefaults.margin),
              Text(
                board.subtitle,
                style: Theme.of(context).textTheme.bodyMedium,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: AppDefaults.margin),
              // Button
            ],
          ),
        ),
        SizedBox(
          width: MediaQuery.of(context).size.width * 0.5,
          child: ElevatedButton(
              onPressed: onNext,
              style: ElevatedButton.styleFrom(
                shape: const StadiumBorder(),
                padding: const EdgeInsets.all(16),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text('Next'),
                  Row(
                    children: List.generate(
                      currentIndex + 1,
                      (index) => Icon(
                        IconlyLight.arrowRight2,
                        color: currentIndex == index
                            ? Colors.white
                            : Colors.white54,
                        size: 16,
                      ),
                    ),
                  )
                ],
              )),
        )
      ],
    );
  }
}

->src->screen->onboarding->data

onboarding_data.dart

import '../../../constants/app_images.dart';

class OnboardingModel {
  String title, subtitle, imageLink;
  OnboardingModel({
    required this.title,
    required this.subtitle,
    required this.imageLink,
  });
}

class OnboardingData {
  static List<OnboardingModel> boards = [
    OnboardingModel(
      title: 'Choose Product',
      subtitle:
          'A product is the item offered for sale. A product can be a service or an item. It can be physical or in virtual or cyber form',
      imageLink: AppImages.illustration1,
    ),
    OnboardingModel(
      title: 'Make Payment',
      subtitle:
          'Payment is the transfer of money services in exchange product or Payments typically made terms agreed',
      imageLink: AppImages.illustration2,
    ),
    OnboardingModel(
      title: 'Get Your Order',
      subtitle:
          'Business or commerce an order is a stated intention either spoken to engage in a commercial transaction specific products ',
      imageLink: AppImages.illustration3,
    ),
  ];
}

->src->screen->home

home_page.dart

import 'package:clothing_app/src/screens/home/components/search_bar_componnent.dart';
import 'package:flutter/material.dart';

import '../../constants/app_defaults.dart';
import 'components/categories_list.dart';
import 'components/home_header.dart';
import 'components/home_new_arrival_section.dart';
import 'components/title_and_subtitle.dart';

class HomePage extends StatelessWidget {
  const HomePage({
    super.key,
    this.backButton,
  });

  final Widget? backButton;

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            HomeHeader(backButton: backButton),
            const SizedBox(height: AppDefaults.margin / 2),
            const TitleAndSubtitle(),
            const SizedBox(height: AppDefaults.margin / 2),
            const SearchBarComponent(),
            const SizedBox(height: AppDefaults.margin / 2),
            const CategoriesList(),
            const SizedBox(height: AppDefaults.margin / 2),
            const NewArrivalSection(),
          ],
        ),
      ),
    );
  }
}

search_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_iconly/flutter_iconly.dart';

import '../../components/product_tile_square.dart';
import '../../constants/constants.dart';
import 'components/home_header.dart';
import 'components/search_bar_componnent.dart';

class SearchPage extends StatelessWidget {
  const SearchPage({super.key, required this.searchedValue});

  final String searchedValue;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const HomeHeader(),
          const SizedBox(height: AppDefaults.margin / 2),
          const SearchBarComponent(),
          const RecentSearchesButton(),
          const Divider(),
          const SizedBox(height: AppDefaults.margin / 2),
          Padding(
            padding: const EdgeInsets.symmetric(
                horizontal: AppDefaults.padding * 1.5),
            child: Text(
              'Search results showing for "$searchedValue"',
              style: Theme.of(context).textTheme.bodyLarge,
            ),
          ),
          const SearchedProductsList(),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    /// We are putting this here just for the sake of simplicity, and this is
    /// just for mockup..
    /// You shouldn't use or write any logic or keep your data here,
    /// this is bad for your app if you fetch or put any data here
    List<Widget> foundProducts = const [
      ProductTileSquare(
        title: 'Long Sleeve Shirts',
        price: 165,
        imageLink: 'https://i.imgur.com/QVroKWd.png',
        hasFavourite: true,
        isFavourite: true,
      ),
      ProductTileSquare(
        title: 'Casual Henley Shirts',
        price: 175,
        imageLink: 'https://i.imgur.com/PFBRThN.png',
        hasFavourite: true,
      ),
      ProductTileSquare(
        title: 'Curved Hem Shirts',
        price: 100,
        imageLink: 'https://i.imgur.com/8dNDjHk.png',
        hasFavourite: true,
      ),
      ProductTileSquare(
        title: 'Casual Nolin',
        price: 100,
        imageLink: 'https://i.imgur.com/KexwuK9.png',
        hasFavourite: true,
      ),
      ProductTileSquare(
        title: 'Casual Nolin',
        price: 100,
        imageLink: 'https://i.imgur.com/fDwKPuo.png',
        hasFavourite: true,
      ),
      ProductTileSquare(
        title: 'Casual Nolin',
        price: 100,
        imageLink: 'https://i.imgur.com/sjDVeNV.png',
        hasFavourite: true,
      ),
    ];

    return Expanded(
      child: GridView.builder(
        padding: EdgeInsets.zero,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisExtent: 290,
        ),
        itemBuilder: (context, index) {
          return foundProducts[index];
        },
        itemCount: foundProducts.length,
      ),
    );
  }
}

/// Text Button With Recent Search
class RecentSearchesButton extends StatelessWidget {
  const RecentSearchesButton({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: AppDefaults.margin),
      child: TextButton(
          onPressed: () {},
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                'Recent Searches',
                style: Theme.of(context).textTheme.bodyLarge,
              ),
              const Icon(
                IconlyLight.arrowRight2,
                color: Colors.grey,
              ),
            ],
          )),
    );
  }
}

 

Código Fonte completo no meu GitHub (Link)