콘텐츠 위에 콘텐츠를 쌓으려면 stack 위젯 사용
Stack -> Positioned 구조로 이루어진다.
Positioned은 Stack의 직접적인 자식 요소로만 사용가능하며,
Positioned을 padding으로 감싸는등 직접적인 자식으로 두지 않을 경우 에러가 난다.
ListView는 Expanded위젯으로 감싸서 넘쳐도 에러가 나지않게 한다.
리스트 당 하나의 모달만 갖고있는 상태
bool isModalOne = false;
으로 초기 상태 선언
GestureDetector위젯으로 아이콘 onTap시 state값 변경 (IconButton 활용법도 있다고 함)
GestureDetector(
onTap: () {
setState(() {
isModalOne = !isModalOne;
});
},
child: const Icon(
Icons.error_outline_sharp,
color: Color(0xFF6a6d6c),
size: 30,
),
),
if (isModalOne == true) Positioned(모달UI)
로 isModalOne이 true일때만 모달UI가 보이게끔 작성List<bool>
를 사용모든 요소가 주어진 초기 값으로 채워진 리스트를 생성할 수 있다.
List<T> filled(int length, T fill, {bool growable = false})
length
: 생성할 리스트의 길이입니다.fill
: 리스트의 모든 요소에 할당할 값입니다.growable
: 기본값은 false로, 이 경우 생성된 리스트의 길이는 고정됩니다. true로 설정하면 리스트의 길이를 변경할 수 있습니다.var myList = List<bool>.filled(5, false);
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
final List<Map<String, String>> productHelpu = [
{
'img': 'assets/images/biki.png',
'title': '[헬퓨] D데이팩',
'delivery': '무료배송',
'price': '62,400',
},
];
final List<Map<String, String>> productHelpuTwo = [
{
'img': 'assets/images/biki.png',
'title': '[헬퓨] D데이팩2',
'delivery': '무료배송',
'price': '52,400',
},
{
'img': 'assets/images/biki.png',
'title': '[헬퓨] D데이팩3',
'delivery': '무료배송',
'price': '12,400',
},
];
class HelpuScreen extends StatefulWidget {
const HelpuScreen({super.key});
@override
State<HelpuScreen> createState() => _HelpuScreenState();
}
class _HelpuScreenState extends State<HelpuScreen> {
List<bool> isCheckedListOne = List.filled(productHelpu.length, false);
List<bool> isCheckedListTwo = List.filled(productHelpuTwo.length, false);
bool isModalOne = false;
bool isModalTwo = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 20,
horizontal: 20,
),
child: Column(
children: [
Expanded(
child: Stack(
children: [
Positioned(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Text(
'맞춤 멀티팩',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
GestureDetector(
onTap: () {
setState(() {
isModalOne = !isModalOne;
});
},
child: const Icon(
Icons.error_outline_sharp,
color: Color(0xFF6a6d6c),
size: 30,
),
),
],
),
const SizedBox(
height: 20,
),
Expanded(
child: ListView.builder(
// shrinkWrap: true,
itemCount: productHelpu.length,
itemBuilder: (context, index) {
final productHelpuItem = productHelpu[index];
return Stack(
children: [
Padding(
padding:
const EdgeInsets.only(bottom: 10),
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(8),
border: Border.all(
width: 1,
color: const Color(0xFFd9d9d9),
)),
child: Padding(
padding: const EdgeInsets.all(5.0),
child: CheckboxListTile(
value: isCheckedListOne[
index], // 체크박스의 상태를 관리하는 변수
onChanged: (bool? value) {
setState(() {
isCheckedListOne[index] =
value!;
});
},
title: SizedBox(
width: double.infinity,
child: Row(
children: [
Container(
decoration:
const BoxDecoration(
color: Color(0xFFd9d9d9),
),
child: Image.asset(
productHelpuItem['img']!,
fit: BoxFit.cover,
width: 50,
height: 50,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Text(productHelpuItem[
'title']!),
Text(productHelpuItem[
'delivery']!)
],
),
),
],
),
),
secondary: Text(
'${productHelpuItem['price']!} 원'),
controlAffinity:
ListTileControlAffinity.leading,
),
),
),
),
],
);
},
),
),
],
),
),
if (isModalOne == true)
Positioned(
top: 55,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 3, 0, 5),
child: Container(
width: 340,
height: 75,
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.black,
),
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'맞춤 멀티팩',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800),
),
SizedBox(
height: 5,
),
Text('구매할 멀티팩을 선택해보세요.'),
],
),
GestureDetector(
onTap: () {
setState(() {
isModalOne = !isModalOne;
});
},
child: const Icon(Icons.close),
)
],
),
),
),
),
),
],
),
),
Expanded(
child: Stack(
children: [
Positioned(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Text(
'단일 멀티팩',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
GestureDetector(
onTap: () {
setState(() {
isModalTwo = !isModalTwo;
});
},
child: const Icon(
Icons.error_outline_sharp,
color: Color(0xFF6a6d6c),
size: 30,
),
),
],
),
const SizedBox(
height: 20,
),
Expanded(
child: ListView.builder(
// shrinkWrap: true,
itemCount: productHelpuTwo.length,
itemBuilder: (context, index) {
final productHelpuItemTwo =
productHelpuTwo[index];
return Stack(
children: [
Padding(
padding:
const EdgeInsets.only(bottom: 10),
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(8),
border: Border.all(
width: 1,
color: const Color(0xFFd9d9d9),
)),
child: Padding(
padding: const EdgeInsets.all(5.0),
child: CheckboxListTile(
value: isCheckedListTwo[
index], // 체크박스의 상태를 관리하는 변수
onChanged: (bool? value) {
setState(() {
isCheckedListTwo[index] =
value!;
});
},
title: SizedBox(
width: double.infinity,
child: Row(
children: [
Container(
decoration:
const BoxDecoration(
color: Color(0xFFd9d9d9),
),
child: Image.asset(
productHelpuItemTwo[
'img']!,
fit: BoxFit.cover,
width: 50,
height: 50,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Text(
productHelpuItemTwo[
'title']!),
Text(
productHelpuItemTwo[
'delivery']!)
],
),
),
],
),
),
secondary: Text(
'${productHelpuItemTwo['price']!} 원'),
controlAffinity:
ListTileControlAffinity.leading,
),
),
),
),
],
);
},
),
),
],
),
),
if (isModalTwo == true)
Positioned(
top: 55,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 3, 0, 5),
child: Container(
width: 340,
height: 120,
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.black,
),
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'맞춤 영양제',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800),
),
SizedBox(
height: 10,
),
Text('멀티팩과 함께 섭취할 제품을 선택해보세요.'),
Text('혹은 단품만 선택하여 구매가 가능합니다.'),
],
),
GestureDetector(
onTap: () {
setState(() {
isModalTwo = !isModalTwo;
});
},
child: const Icon(Icons.close),
)
],
),
),
),
),
),
],
),
),
],
),
),
),
);
}
}