[Flutter] Flutter 예제 - Shopping Cart App 만들기

류재성's avatar
Apr 16, 2024
[Flutter] Flutter 예제 - Shopping Cart App 만들기
 
 

1. 기본 세팅

notion image
 
assets 폴더를 만든 후 사진을 넣는다.
 
notion image
 
pubspec.yml 을 세팅 후 pub get 을 누른다.
 

2. 테마 설정하기

 
constants.dart
import 'package:flutter/material.dart'; const kPrimaryColor = MaterialColor( 0xFFeeeeee, <int, Color>{ 50: Color(0xFFeeeeee), 100: Color(0xFFeeeeee), 200: Color(0xFFeeeeee), 300: Color(0xFFeeeeee), 400: Color(0xFFeeeeee), 500: Color(0xFFeeeeee), 600: Color(0xFFeeeeee), 700: Color(0xFFeeeeee), 800: Color(0xFFeeeeee), 900: Color(0xFFeeeeee), }, ); const kSecondaryColor = Color(0xffc6c6c6);// 기본 버튼색 const kAccentColor = Color(0xFFff7643);
 
theme.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/constants.dart'; ThemeData theme(){ return ThemeData( primarySwatch: kPrimaryColor, scaffoldBackgroundColor: kPrimaryColor , ); }
 
main.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/theme.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: theme(), ); } }
 

3. ShoppingCartHeader 만들기

 
import 'package:flutter/material.dart'; import 'package:shoping_app/theme.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: theme(), home: Scaffold( body: Column( children: [ SizedBox(), ], ), ), ); } }
notion image
 
SizedBox 컴포넌트를 분리 후 외부 클래스로 만든다.
 
components/shoping_cart_header.dart
import 'package:flutter/material.dart'; class ShoppingCartHeader extends StatelessWidget { const ShoppingCartHeader({ super.key, }); @override Widget build(BuildContext context) { return SizedBox(); } }
 
notion image
 
외부로 분리한 컴퍼넌트에 alt+enter를 눌러 Stateful 위젯으로 변경한다.
 
shopping_cart_header.dart
import 'package:flutter/material.dart'; class ShoppingCartHeader extends StatefulWidget { @override State<ShoppingCartHeader> createState() => _ShoppingCartHeaderState(); } class _ShoppingCartHeaderState extends State<ShoppingCartHeader> { int selectedId = 0; List<String> selectedPic = [ "assets/p1.jpeg", "assets/p2.jpeg", "assets/p3.jpeg", "assets/p4.jpeg", ]; @override Widget build(BuildContext context) { return SizedBox(); } }
 
동일한 방법으로 ShoppingCartDetail 도 만든다.
 
shopping_cart_detail.dart
import 'package:flutter/material.dart'; class ShoppingCartHeader extends StatefulWidget { @override State<ShoppingCartHeader> createState() => _ShoppingCartHeaderState(); } class _ShoppingCartHeaderState extends State<ShoppingCartHeader> { int selectedId = 0; List<String> selectedPic = [ "assets/p1.jpeg", "assets/p2.jpeg", "assets/p3.jpeg", "assets/p4.jpeg", ]; @override Widget build(BuildContext context) { return SizedBox(); } }
 
main.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/theme.dart'; import 'components/shopping_cart_detail.dart'; import 'components/shopping_cart_header.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: theme(), home: Scaffold( body: Column( children: [ ShoppingCartHeader(), ShoppingCartDetail(), ], ), ), ); } }
 

4. AppBar 만들기

 
main.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/theme.dart'; import 'components/shopping_cart_detail.dart'; import 'components/shopping_cart_header.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: theme(), home: Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () {}, ), actions: [ IconButton( icon: Icon(Icons.shopping_cart), onPressed: () {}, ), SizedBox(width: 16), ], elevation: 0.0, ), body: Column( children: [ ShoppingCartHeader(), ShoppingCartDetail(), ], ), ), ); } }
notion image
 
AppBar를 별도로 분리한다.
 
main.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/theme.dart'; import 'components/shopping_cart_detail.dart'; import 'components/shopping_cart_header.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: theme(), home: Scaffold( appBar: _buildShoppingCarAppBar(), body: Column( children: [ ShoppingCartHeader(), ShoppingCartDetail(), ], ), ), ); } AppBar _buildShoppingCarAppBar() { return AppBar( leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () {}, ), actions: [ IconButton( icon: Icon(Icons.shopping_cart), onPressed: () {}, ), SizedBox(width: 16), ], elevation: 0.0, ); } }
 

5. setState() 함수 사용하기

 
우선 화면 상단에 사진을 표시한다.
 
shopping_cart_header.dart
import 'package:flutter/material.dart'; class ShoppingCartHeader extends StatefulWidget { @override State<ShoppingCartHeader> createState() => _ShoppingCartHeaderState(); } class _ShoppingCartHeaderState extends State<ShoppingCartHeader> { int selectedId = 0; List<String> selectedPic = [ "assets/p1.jpeg", "assets/p2.jpeg", "assets/p3.jpeg", "assets/p4.jpeg", ]; @override Widget build(BuildContext context) { return Column( children: [ AspectRatio( aspectRatio: 5 / 3, child: Image.asset( selectedPic[selectedId], fit: BoxFit.cover, ), ) ], ); } }
 
notion image
 
AspectRatio를 별도 컴포넌트로 분리한다.
 
shopping_cart_header_pic.dart
import 'package:flutter/material.dart'; class HeaderPic extends StatelessWidget { const HeaderPic({ super.key, required this.selectedPic, required this.selectedId, }); final List<String> selectedPic; final int selectedId; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16.0), child: AspectRatio( aspectRatio: 5 / 3, child: Image.asset( selectedPic[selectedId], fit: BoxFit.cover, ), ), ); } }
 
다음으로 사진을 변경할 때 사용할 버튼을 만들어보자.
 
shopping_cart_header.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/components/shopping_cart_header_pic.dart'; import 'package:shoping_app/constants.dart'; class ShoppingCartHeader extends StatefulWidget { @override State<ShoppingCartHeader> createState() => _ShoppingCartHeaderState(); } class _ShoppingCartHeaderState extends State<ShoppingCartHeader> { int selectedId = 0; List<String> selectedPic = [ "assets/p1.jpeg", "assets/p2.jpeg", "assets/p3.jpeg", "assets/p4.jpeg", ]; @override Widget build(BuildContext context) { return Column( children: [ HeaderPic(selectedPic: selectedPic, selectedId: selectedId), Row( children: [ Container( width: 70, height: 70, decoration: BoxDecoration( color: 0 == selectedId ? kAccentColor : kSecondaryColor, borderRadius: BorderRadius.circular(20), ), child: IconButton( icon: Icon(Icons.directions_bike), onPressed: () { setState(() { selectedId = 0 ; }); }, ), ), ], ), ], ); } }
 
 
notion image
 
 
버튼을 컴포넌트로 분리 후 변수화 한다.
 
shopping_cart_header.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/components/header_selector.dart'; import 'package:shoping_app/components/shopping_cart_header_pic.dart'; class ShoppingCartHeader extends StatefulWidget { @override State<ShoppingCartHeader> createState() => _ShoppingCartHeaderState(); } class _ShoppingCartHeaderState extends State<ShoppingCartHeader> { int selectedId = 0; List<String> selectedPic = [ "assets/p1.jpeg", "assets/p2.jpeg", "assets/p3.jpeg", "assets/p4.jpeg", ]; //콜백함수로 부모위젯에서 자식 위젯으로 setState를 넘긴다 void updateSelectedId(int id){ setState(() { selectedId = id ; }); } @override Widget build(BuildContext context) { return Column( children: [ HeaderPic(selectedPic: selectedPic, selectedId: selectedId), HeaderSelector(updateSelectedId,selectedId), ], ); } }
 
💡
자식 위젯은 stateless로 setState 함수가 없어서 부모 위젯에서 콜백 함수를 만들어서 넘긴다.
 
header_selector.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:shoping_app/constants.dart'; import 'header_selector_button.dart'; class HeaderSelector extends StatelessWidget { var updateSelectedId ; int selectedId ; HeaderSelector(this.updateSelectedId,this.selectedId); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 30,right: 30,top: 10,bottom: 30), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ HeaderSelectorButton(0,selectedId,Icons.directions_bike,updateSelectedId), HeaderSelectorButton(1,selectedId,Icons.motorcycle,updateSelectedId), HeaderSelectorButton(2,selectedId,CupertinoIcons.car_detailed,updateSelectedId), HeaderSelectorButton(3,selectedId,CupertinoIcons.airplane,updateSelectedId), ], ), ); } }
 
header_selector_button.dart
import 'package:flutter/material.dart'; import '../constants.dart'; class HeaderSelectorButton extends StatelessWidget { int id ; int selectedId; IconData mIcon; var updateSelectedId; HeaderSelectorButton(this.id,this.selectedId,this.mIcon, this.updateSelectedId); @override Widget build(BuildContext context) { return Container( width: 70, height: 70, decoration: BoxDecoration( color: id == selectedId ? kAccentColor : kSecondaryColor, borderRadius: BorderRadius.circular(20), ), child: IconButton( icon: Icon(mIcon), onPressed: () { updateSelectedId(id); }, ), ); } }
 
notion image
notion image
 
 

5. Shpping_cart_detail.dart 만들기

 
shopping_cart_detail.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/components/detail_button.dart'; import 'package:shoping_app/components/detail_color_option.dart'; import 'package:shoping_app/components/detail_rating_and_review_count.dart'; import 'detail_name_and_price.dart'; class ShoppingCartDetail extends StatelessWidget { const ShoppingCartDetail({ super.key, }); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(40), ), child: Padding( padding: const EdgeInsets.all(30.0), child: Column( children: [ DetailNameAndPrice(), DetailRatingAndReviewCount(), DetailColorOptions(), DetailButton(), ], ), ), ); } }
 
우선 구조를 만들고 각 클래스를 만들어보자
 
detail_name_and_price.dart
import 'package:flutter/material.dart'; class DetailNameAndPrice extends StatelessWidget { const DetailNameAndPrice({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 10), child: Row( children: [ Text( "Urban Soft AL 10.0", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( "\$699", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ) ], ), ); } }
 
detail_rating_and_review_count.dart
import 'package:flutter/material.dart'; class DetailRatingAndReviewCount extends StatelessWidget { const DetailRatingAndReviewCount({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 20), child: Row( children: [ Icon(Icons.star,color: Colors.yellow), Icon(Icons.star,color: Colors.yellow), Icon(Icons.star,color: Colors.yellow), Icon(Icons.star,color: Colors.yellow), Icon(Icons.star,color: Colors.yellow), Spacer(), Text("review"), Text("(26)",style: TextStyle(color: Colors.blue)), ], ), ); } }
 
detail_color_option.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/components/detail_icon.dart'; class DetailColorOptions extends StatelessWidget { const DetailColorOptions({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 20), child: Column( children: [ Text("Color Options"), SizedBox(height: 10), Row( children: [ DetailIcon(Colors.black), DetailIcon(Colors.green), DetailIcon(Colors.orange), DetailIcon(Colors.grey), DetailIcon(Colors.white), ], ), ], ), ); } }
 
detail_icon.dart
import 'package:flutter/material.dart'; class DetailIcon extends StatelessWidget { Color mColor ; DetailIcon(this.mColor); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 10), child: Stack( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.white, border: Border.all(), shape: BoxShape.circle, ), ), Positioned( left: 5, top: 5, child: ClipOval( child: Container( color: mColor, width: 40, height: 40, ), ), ) ], ), ); } }
💡
Stack 위젯은 자식 위젯들을 겹쳐서 배치할 수 있는 위젯이다. Stack의 자식 위젯 중 Positioned 위젯으로 감싸진 것들은 top, right, bottom, left 속성을 사용하여 Stack 내에서의 위치를 지정할 수 있습니다.
 
 
detail_button.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/constants.dart'; class DetailButton extends StatelessWidget { const DetailButton({super.key}); @override Widget build(BuildContext context) { return Align( child: TextButton( onPressed: () {}, style: TextButton.styleFrom( backgroundColor: kAccentColor, minimumSize: Size(300, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: Text( "Add to Cart", style: TextStyle(color: Colors.white), ), ), ); } }
 
notion image
 

6. CupertinoAlertDialog 위젯 만들기

 
shopping_cart_detail.dart
import 'package:flutter/material.dart'; import 'package:shoping_app/components/detail_button.dart'; import 'package:shoping_app/components/detail_color_option.dart'; import 'package:shoping_app/components/detail_rating_and_review_count.dart'; import 'detail_name_and_price.dart'; class ShoppingCartDetail extends StatelessWidget { const ShoppingCartDetail({ super.key, }); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(40), ), child: Padding( padding: const EdgeInsets.all(30.0), child: Column( children: [ DetailNameAndPrice(), DetailRatingAndReviewCount(), DetailColorOptions(), DetailButton(context), ], ), ), ); } }
 
💡
context를 전달하면 UI 업데이트, 의존성 주입, 화면 전환 등의 작업을 수행할 수 있다. DetailButton 위젯에서 다른 화면으로 이동해야 할 경우, context를 통해 Navigator 객체에 접근할 수 있.
 
detail_button.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:shoping_app/constants.dart'; class DetailButton extends StatelessWidget { const DetailButton(BuildContext context, {super.key}); @override Widget build(BuildContext context) { return Align( child: TextButton( onPressed: () { showCupertinoDialog( context: context, builder: (context) => CupertinoAlertDialog( title: Text("장바구니에 담으시겠습니까?"), actions: [ CupertinoDialogAction( child: Text("확인"), onPressed: () { Navigator.pop(context); }, ), ], )); }, style: TextButton.styleFrom( backgroundColor: kAccentColor, minimumSize: Size(300, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: Text( "Add to Cart", style: TextStyle(color: Colors.white), ), ), ); } }
 
notion image
 
Share article

{CODE-RYU};