[002] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성2 - 제품 상세 페이지 만들기)

2023. 9. 28. 13:15모바일어플개발/Flutter 실전어플 개발

반응형

안녕하세요~ totally 개발자입니다.

 

이전 강좌에 이어 이번 포스팅에서는 제품 리스트에서 각 제품을 클릭했을 때 각 제품의 상세 페이지로 이동해줄 수 있도록 상세 페이지를 만들어보도록 하겠습니다.

 

Step 1: constants.dart 파일을 먼저 생성해줍니다.

 

 

Step 2: 저번 시간에 item_list_page.dart에서 만들었던 NumberFormat 부분이 계속 사용되고 중복되는 관계로 이 변수를 constants.dart 파일 여기로 분리해줍니다.

아래 부분을 없애주시고,

아래 constants.dart 파일에 다음처럼 복사해주신 뒤,

그 후에 오류로 표시되어 있는 numberFormat에 필요한 이 부분을 임포트하여 줍니다.

 

item_list_page.dart에서 constants를 임포트하여 완료한 모습입니다.

 

Step 3: item_list_page.dart에 있었던 내용들을 일단 다 가지고 오신 뒤, 다음처럼 ItemDetailsPage로 변경해주시고 불필요한 내용을 제거하신 뒤에 작업을 시작합니다.

 

 

Step 4: 아래처럼 매개변수들을 전달해서 받을 수 있도록 만들어줍니다.

 

 

Step 5: item_list_page.dart 파일로 가셔서 Container 부분에 마우스를 왼쪽 클릭하시고 VSCode 기준으로 윈도우는 Ctrl과 . 키를 누르시고 맥은 Command와 .키를 누르셔서 Wrap with widget...를 선택해줍니다.

 

 

아래 widget를 GestureDetector로 변경해줍니다. GestureDetector은 제스쳐를 인식할 수 있도록 해주는 위젯입니다.

 

75번째 줄처럼 onTap: 속성을 넣어주시고 다음 페이지로 이동해줄 수 있도록 추가해줍니다. 다만 아직 productNo은 위의 productContainer 매개변수로 받아 오지 않기 때문에 아직 에러로 표시가 됩니다.

61번째 줄에 매개변수로 전달해주시고 73번째에서 매개변수를 전달 받고, 82번째에 productNo를 그 상세 페이지로 전달하는 것까지 모두 완료해주시면 됩니다.

 

Step 5: item_details_page.dart로 넘어가서 아래처럼 이미지를 먼저 표시해줍니다. 37번째에 MediaQuery를 사용하여 길이를 반응형으로 조절할 수 있습니다. 0.8를 곱함으로서, 80%의 길이를 표시해주기 위함입니다. 

 

 

Step 6: 이미지 아래에 제품 제목과 가격을 표시해줍니다.

 

 

Step 7: 그 후 bottomNavigationBar를 활용하여 버튼을 맨 아래에 장바구니 담기 버튼으로 만들어줍니다.

 

 

아직은 장바구니 페이지를 만들지 않았기 때문에 onPressed: () {} 부분은 공백으로 두고 다음 포스팅에서는 장바구니 페이지 UI 구성에 대해 살펴보도록 하겠습니다. 감사합니다.

 

[전체 소스 코드]

 

[item_list_page.dart]

 

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:project/constants.dart';
import 'package:project/item_details_page.dart';
import 'package:project/models/product.dart';
class ItemListPage extends StatefulWidget {
const ItemListPage({super.key});
@override
State<ItemListPage> createState() => _ItemListPageState();
}
class _ItemListPageState extends State<ItemListPage> {
List<Product> productList = [
Product(
productNo: 1,
productName: "노트북(Laptop)",
productImageUrl: "https://picsum.photos/id/1/300/300",
price: 600000),
Product(
productNo: 2,
productName: "스마트폰(Phone)",
productImageUrl: "https://picsum.photos/id/20/300/300",
price: 500000),
Product(
productNo: 3,
productName: "머그컵(Cup)",
productImageUrl: "https://picsum.photos/id/30/300/300",
price: 15000),
Product(
productNo: 4,
productName: "키보드(Keyboard)",
productImageUrl: "https://picsum.photos/id/60/300/300",
price: 50000),
Product(
productNo: 5,
productName: "포도(Grape)",
productImageUrl: "https://picsum.photos/id/75/200/300",
price: 75000),
Product(
productNo: 6,
productName: "책(book)",
productImageUrl: "https://picsum.photos/id/24/200/300",
price: 24000),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("제품 리스트"),
centerTitle: true,
),
body: GridView.builder(
itemCount: productList.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.9, crossAxisCount: 2),
itemBuilder: (context, index) {
return productContainer(
productNo: productList[index].productNo ?? 0,
productName: productList[index].productName ?? "",
productImageUrl: productList[index].productImageUrl ?? "",
price: productList[index].price ?? 0,
);
},
),
);
}
Widget productContainer(
{required int productNo,
required String productName,
required String productImageUrl,
required double price}) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return ItemDetailsPage(
productNo: productNo,
productName: productName,
productImageUrl: productImageUrl,
price: price);
},
));
},
child: Container(
padding: const EdgeInsets.all(5),
child: Column(
children: [
CachedNetworkImage(
height: 150,
fit: BoxFit.cover,
imageUrl: productImageUrl,
placeholder: (context, url) {
return const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
);
},
errorWidget: (context, url, error) {
return const Center(
child: Text("오류 발생"),
);
},
),
Container(
padding: const EdgeInsets.all(8),
child: Text(
productName,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.all(8),
child: Text("${numberFormat.format(price)}원"),
),
],
),
),
);
}
}

 

[item_details_page.dart]

 

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:project/constants.dart';
class ItemDetailsPage extends StatefulWidget {
int productNo;
String productName;
String productImageUrl;
double price;
ItemDetailsPage(
{super.key,
required this.productNo,
required this.productName,
required this.productImageUrl,
required this.price});
@override
State<ItemDetailsPage> createState() => _ItemDetailsPageState();
}
class _ItemDetailsPageState extends State<ItemDetailsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("제품 상세 페이지"),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(15),
child: CachedNetworkImage(
width: MediaQuery.of(context).size.width * 0.8,
fit: BoxFit.cover,
imageUrl: widget.productImageUrl,
placeholder: (context, url) {
return const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
);
},
errorWidget: (context, url, error) {
return const Center(
child: Text("오류 발생"),
);
},
),
),
Container(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
widget.productName,
textScaleFactor: 1.5,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
"${numberFormat.format(widget.price)}원",
textScaleFactor: 1.3,
),
),
],
),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(20),
child: FilledButton(
onPressed: () {},
child: const Text("장바구니 담기"),
),
),
);
}
}

 

[유튜브 강좌 영상]

 

https://youtu.be/5Wso03FQwYE

 

 

반응형