1. setState()
로 상태 바꾸기 (선택 버튼 ↔ 이미지 교체)
핵심 아이디어
- 상태값
selectedId
가 바뀌면,build()
가 다시 호출되어selectedPic[selectedId]
이미지가 즉시 바뀐다.
- 버튼 UI도
selectedId
에 따라 배경색이 달라진다.
class ShoppingcartHeader extends StatefulWidget {
@override
State<ShoppingcartHeader> createState() => _ShoppingcartHeaderState();
}
class _ShoppingcartHeaderState extends State<ShoppingcartHeader> {
int selectedId = 0;
final selectedPic = ["assets/p1.jpeg","assets/p2.jpeg","assets/p3.jpeg","assets/p4.jpeg"];
@override
Widget build(BuildContext context) {
return Column(
children: [
// 선택된 id에 맞는 이미지
Padding(
padding: const EdgeInsets.all(16.0),
child: AspectRatio(
aspectRatio: 5 / 3,
child: Image.asset(selectedPic[selectedId], fit: BoxFit.cover),
),
),
// 선택 버튼들
Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 30, 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_selector(0, Icons.directions_bike),
_selector(1, Icons.motorcycle),
_selector(2, CupertinoIcons.car_detailed),
_selector(3, CupertinoIcons.airplane),
],
),
),
],
);
}
Widget _selector(int id, IconData icon) {
final isSelected = id == selectedId;
return Container(
width: 70, height: 70,
decoration: BoxDecoration(
color: isSelected ? kAccentColor : kSecondaryColor,
borderRadius: BorderRadius.circular(20),
),
child: IconButton(
icon: Icon(icon, color: Colors.black),
onPressed: () => setState(() => selectedId = id), // 상태 변경 → 리빌드
),
);
}
}
메모
setState()
는 동기적으로 상태를 바꾸고 프레임 스케줄러에 리빌드 예약을 건다.
- 가능한 최소 범위의 위젯에서만 호출(불필요한 리빌드 방지).
- 리스트/맵은 참조 변경(새 인스턴스) 없이 내부만 수정하면 변경 감지가 어려울 수 있음 → 가능하면 불변 패턴 지향.
2. Stack
+ Positioned
로 겹쳐 배치

핵심 아이디어
Stack
은 자식들을 Z축으로 겹쳐 그린다.
Positioned
로 자식의 절대 위치(left/top/right/bottom) 를 지정한다.
- 원형 컨테이너 위에 작게 색원(ClipOval)을 살짝 내려놓는 UI를 구현.
Widget _buildDetailIcon(Color color) {
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(width: 40, height: 40, color: color),
),
),
],
),
);
}
메모
- 부모
Stack
의 크기는 자식의 크기나 부모 제약(Padding/ConstrainedBox/AspectRatio 등) 로 정한다.
- 정교한 배치를 원하면
Align
/Positioned.fill
/FractionalTranslation
도 유용.
3. CupertinoAlertDialog
+ Navigator.pop()
(스택에서 팝업 제거)

핵심 아이디어
showCupertinoDialog()
는 현재 Navigator 스택 위에 다이얼로그 Route를 푸시(push) 한다.
- 닫을 때는
Navigator.pop(context)
로 팝(pop) 하여 스택에서 제거한다.
Widget _buildDetailButton(BuildContext context) {
return Align(
child: TextButton(
onPressed: () {
showCupertinoDialog(
context: context,
builder: (_) => CupertinoAlertDialog(
title: const Text("장바구니에 담으시겠습니까?"),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.pop(context), // 팝업 닫기 = pop
child: const Text("확인", style: TextStyle(color: Colors.blue)),
),
],
),
);
},
style: TextButton.styleFrom(
backgroundColor: kAccentColor,
minimumSize: const Size(300, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
child: const Text("Add to Cart", style: TextStyle(color: Colors.white)),
),
);
}
메모
- 머티리얼 스타일이 필요하면
showDialog
+AlertDialog
.
- 바깥 탭으로 닫히게 하려면
barrierDismissible: true
(머티리얼showDialog
에 존재).
- 팝업에서 결과값을 반환하려면
Navigator.pop(context, result)
사용 →await showCupertinoDialog(...)
로 값 수신.
마무리 요약
- 상태 변경은
setState()
: 바뀐 값만 의도적으로 갱신되도록 위젯 구조를 깔끔히 나누자.
- 겹쳐 배치는
Stack
+Positioned
: 절대 좌표/정렬로 정교하게 쌓아 그리는 패턴.
- 팝업은 Navigator 스택: 띄울 땐 push, 닫을 땐 pop—다이얼로그도 결국 Route다.
“선택형 헤더(UI 토글)에서
setState()
로 즉시 반응하는 이미지 교체, Stack/Positioned
로 오버레이 배지 구현, CupertinoAlertDialog
를 Route로 관리하여 push/pop 기반 팝업 라이프사이클을 설계했습니다.”Share article