[004] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(UI구성4 - 제품 결제시작 페이지 만들기 1)

2023. 10. 9. 16:44모바일어플개발/Flutter 실전어플 개발

반응형

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

 

이번 포스팅에서는 결제시작 페이지 UI 구성에 대해 살펴보도록 하겠습니다.

 

Step 1: item_basket_page.dart를 복사하셔서 이름을 item_checkout_page.dart로 변경해줍니다. 클래스, State 명도 모두 변경해줍니다.

 

 

Step 2: basketList 변수를 checkoutList로 이름을 변경해주고 quantityList는 그대로 내버려둡니다.

 

 

Step 3: 아래에 있는 basketContainer 위젯의 이름을 checkoutContainer로 변경해줍니다.

 

 

Step 4: 다음 부분처럼 title과 basketList를 checkoutList로 변경해주시면 됩니다. 또한 53번째 줄에 basketContainer를 checkoutContainer로 변경해줍니다.

 

 

Step 5: item_basket_page.dart를 열어주셔서 다음처럼 onPressed 안에 결제시작 페이지로 이동할 수 있도록 Navigator push를 넣어줍니다. 

 

 

Step 6: 완료되었다면 저장을 하시고 장바구니 페이지에서 총 1,350,000원 결제하기 파란색 버튼을 누르시면 아래 화면처럼 '결제시작' 페이지로 랜딩이 됩니다.

 

 

 

Step 7: 장바구니 페이지와 달리 결제시작에서는 수량을 수정할 수 없고 제품 또한 제거하지 못하도록 막아야 하기 때문에 여기에서는 수량만 표시해주시고 수량을 조절하는 버튼과 제거 버튼은 모두 없애줍니다. basketContainer에서 Row로 구성되어 있던 부분을 Text 위젯 하나로 모두 표현해주시면 됩니다.

 

 

Step 8: 이제 입력할 수 있는 폼을 넣어야 하는데, 여기에는 주문자, 이메일, 휴대전화, 배송지(받는 사람, 주소, 받는 사람 휴대전화, 비회원 주문조회 비밀번호), 결제수단(카드, 무통장입금 등) 정보 등 많은 입력필드가 필요합니다. 먼저 아래 TextEditingController를 쭉 추가해주시면 됩니다.

//! controller 변수 추가
TextEditingController buyerNameController = TextEditingController();
TextEditingController buyerEmailController = TextEditingController();
TextEditingController buyerPhoneController = TextEditingController();
TextEditingController receiverNameController = TextEditingController();
TextEditingController receiverPhoneController = TextEditingController();
TextEditingController receiverZipController = TextEditingController();
TextEditingController receiverAddress1Controller = TextEditingController();
TextEditingController receiverAddress2Controller = TextEditingController();
TextEditingController userPwdController = TextEditingController();
TextEditingController userConfirmPwdController = TextEditingController();
TextEditingController cardNoController = TextEditingController();
TextEditingController cardAuthController = TextEditingController();
TextEditingController cardExpiredDateController = TextEditingController();
TextEditingController cardPwdTwoDigitsController = TextEditingController();

 

 

Step 9: 별도의 위젯으로 buyerNameTextField를 먼저 하나 추가해줍니다. 

 

 

Step 10: body 부분을 수정합니다. ListView.builder 밑에 입력필드들을 두어야 하기 때문에 Column으로 먼저 감싼 뒤, SingleChildScrollView로 다시 감싸줍니다. 그 후에 ListView.builder 내에 shrinkWrap: true를 넣으셔야 렌더링 오류가 발생하지 않습니다. 마지막에 buyerNameTextField()를 넣어서 잘 나오는지 확인합니다. 

 

 

Step 11: 나머지 부분도 반영하여 넣어줍니다 (맨 아래에 소스 코드 있습니다) 

 

 

 

반영한 모습입니다. 이제 여기에 카드결제, 무통장입금 등 결제수단 선택과 우편번호 선택할 수 있는 패키지를 추가하는 방법을 다음 포스팅에서 진행하도록 하겠습니다. 

 

[전체 소스 코드]

 

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:project/constants.dart';
import 'package:project/models/product.dart';
class ItemCheckoutPage extends StatefulWidget {
const ItemCheckoutPage({super.key});
@override
State<ItemCheckoutPage> createState() => _ItemCheckoutPageState();
}
class _ItemCheckoutPageState extends State<ItemCheckoutPage> {
List<Product> checkoutList = [
Product(
productNo: 1,
productName: "노트북(Laptop)",
productImageUrl: "https://picsum.photos/id/1/300/300",
price: 600000),
Product(
productNo: 4,
productName: "키보드(Keyboard)",
productImageUrl: "https://picsum.photos/id/60/300/300",
price: 50000),
];
List<Map<int, int>> quantityList = [
{1: 2},
{4: 3},
];
double totalPrice = 0;
final formKey = GlobalKey<FormState>();
//! controller 변수 추가
TextEditingController buyerNameController = TextEditingController();
TextEditingController buyerEmailController = TextEditingController();
TextEditingController buyerPhoneController = TextEditingController();
TextEditingController receiverNameController = TextEditingController();
TextEditingController receiverPhoneController = TextEditingController();
TextEditingController receiverZipController = TextEditingController();
TextEditingController receiverAddress1Controller = TextEditingController();
TextEditingController receiverAddress2Controller = TextEditingController();
TextEditingController userPwdController = TextEditingController();
TextEditingController userConfirmPwdController = TextEditingController();
TextEditingController cardNoController = TextEditingController();
TextEditingController cardAuthController = TextEditingController();
TextEditingController cardExpiredDateController = TextEditingController();
TextEditingController cardPwdTwoDigitsController = TextEditingController();
@override
void initState() {
super.initState();
for (int i = 0; i < checkoutList.length; i++) {
totalPrice +=
checkoutList[i].price! * quantityList[i][checkoutList[i].productNo]!;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("결제시작"),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: checkoutList.length,
itemBuilder: (context, index) {
return checkoutContainer(
productNo: checkoutList[index].productNo ?? 0,
productName: checkoutList[index].productName ?? "",
productImageUrl: checkoutList[index].productImageUrl ?? "",
price: checkoutList[index].price ?? 0,
quantity: quantityList[index]
[checkoutList[index].productNo] ??
0);
},
),
//! 입력폼 필드
buyerNameTextField(),
buyerEmailTextField(),
buyerPhoneTextField(),
receiverNameTextField(),
receiverPhoneTextField(),
receiverZipTextField(),
receiverAddress1TextField(),
receiverAddress2TextField(),
userPwdTextField(),
userConfirmPwdTextField(),
cardNoTextField(),
cardAuthTextField(),
cardExpiredDateTextField(),
cardPwdTwoDigitsTextField(),
],
),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(20),
child: FilledButton(
onPressed: () {},
child: Text("총 ${numberFormat.format(totalPrice)}원 결제하기"),
)),
);
}
Widget checkoutContainer({
required int productNo,
required String productName,
required String productImageUrl,
required double price,
required int quantity,
}) {
return Container(
padding: const EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CachedNetworkImage(
width: MediaQuery.of(context).size.width * 0.3,
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.symmetric(horizontal: 15, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
productName,
textScaleFactor: 1.2,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
Text("${numberFormat.format(price)}원"),
Text("수량: $quantity"),
Text("합계: ${numberFormat.format(price * quantity)}원"),
],
),
),
],
),
);
}
Widget buyerNameTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: buyerNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "주문자명",
),
),
);
}
Widget buyerEmailTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: buyerEmailController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "주문자 이메일",
),
),
);
}
Widget buyerPhoneTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: buyerPhoneController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "주문자 휴대전화",
),
),
);
}
Widget receiverNameTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: receiverNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "받는 사람 이름",
),
),
);
}
Widget receiverPhoneTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: receiverPhoneController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "받는 사람 휴대 전화",
),
),
);
}
Widget receiverZipTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: receiverZipController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "우편번호",
),
),
);
}
Widget receiverAddress1TextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: receiverAddress1Controller,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "기본 주소",
),
),
);
}
Widget receiverAddress2TextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: receiverAddress2Controller,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "상세 주소",
),
),
);
}
Widget userPwdTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: userPwdController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "비회원 주문조회 비밀번호",
),
),
);
}
Widget userConfirmPwdTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: userConfirmPwdController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "비회원 주문조회 비밀번호 확인",
),
),
);
}
Widget cardNoTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: cardNoController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "카드번호",
),
),
);
}
Widget cardAuthTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: cardAuthController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "카드명의자 주민번호 앞자리",
),
),
);
}
Widget cardExpiredDateTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: cardExpiredDateController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "카드 만료일",
),
),
);
}
Widget cardPwdTwoDigitsTextField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: cardPwdTwoDigitsController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "카드 비밀번호 앞2자리",
),
),
);
}
}

 

[유튜브 강좌 영상]

 

https://youtu.be/BOmZGcN6pcw

 

반응형