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)