1. 기본 세팅

assets 폴더를 만든 후 사진을 넣는다.

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(),
],
),
),
);
}
}

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();
}
}

외부로 분리한 컴퍼넌트에 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(),
],
),
),
);
}
}

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,
),
)
],
);
}
}

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 ;
});
},
),
),
],
),
],
);
}
}

버튼을 컴포넌트로 분리 후 변수화 한다.
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);
},
),
);
}
}


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),
),
),
);
}
}

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),
),
),
);
}
}

Share article