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("장바구니 담기"), | |
), | |
), | |
); | |
} | |
} |
[유튜브 강좌 영상]
'모바일어플개발 > Flutter 실전어플 개발' 카테고리의 다른 글
[006] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성6 - 제품 결제시작 페이지 만들기 3) (1) | 2023.11.04 |
---|---|
[005] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성5 - 제품 결제시작 페이지 만들기 2) (2) | 2023.10.14 |
[004] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성4 - 제품 결제시작 페이지 만들기 1) (1) | 2023.10.09 |
[003] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성3 - 제품 장바구니 페이지 만들기) (0) | 2023.09.28 |
[001] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성1 - 제품 리스트 페이지 만들기) (0) | 2023.09.24 |