Stack으로 위젯 겹치기와 위치 지정
웹에서 절대 위치 지정이 필요할 때 CSS의 position: absolute
를 사용하듯이, Flitter에서는 Stack과 Positioned 위젯을 사용해 위젯들을 겹치고 원하는 위치에 배치할 수 있습니다.
🎯 학습 목표
이 튜토리얼을 완료하면 다음을 할 수 있게 됩니다:
- Stack으로 위젯들을 겹쳐서 배치하기
- Positioned로 정확한 위치 지정하기
- Align으로 상대적 위치 지정하기
- z-index 개념 이해하고 적용하기
- 실용적인 오버레이 UI 만들기
📚 Stack 위젯 이해하기
Stack은 자식 위젯들을 겹쳐서 배치합니다. 나중에 추가된 위젯이 위에 그려집니다.
Stack의 기본 구조
Stack({
alignment: Alignment.center, // 기본 정렬
fit: StackFit.loose, // 크기 조정 방식
children: [
// 첫 번째 위젯 (맨 아래)
// 두 번째 위젯
// 세 번째 위젯 (맨 위)
]
})
StackFit 옵션
StackFit.loose
- Stack이 자식들의 크기에 맞춤 (기본값)StackFit.expand
- Stack이 부모의 크기를 모두 차지StackFit.passthrough
- 부모의 제약을 자식에게 전달
🎨 Positioned 위젯 이해하기
Positioned는 Stack 내에서 자식 위젯의 정확한 위치를 지정합니다.
Positioned의 속성
Positioned({
top: 10, // 위에서부터의 거리
right: 20, // 오른쪽에서부터의 거리
bottom: 30, // 아래에서부터의 거리
left: 40, // 왼쪽에서부터의 거리
width: 100, // 너비 지정 (선택사항)
height: 50, // 높이 지정 (선택사항)
child: Container({ /* ... */ })
})
🚀 실습 1: 기본 Stack 레이아웃
Stack({
children: [
// 배경
Container({
width: 300,
height: 200,
color: '#e3f2fd'
}),
// 중앙 요소
Positioned({
left: 50,
top: 50,
child: Container({
width: 100,
height: 100,
color: '#2196f3',
child: Center({ child: Text("중앙") })
})
}),
// 우측 상단 요소
Positioned({
right: 10,
top: 10,
child: Container({
width: 40,
height: 40,
color: '#f44336',
child: Center({ child: Text("!") })
})
})
]
})
🚀 실습 2: 프로필 카드 만들기
Container({
width: 300,
height: 180,
child: Stack({
children: [
// 배경 이미지 대체
Container({
decoration: new BoxDecoration({
gradient: new LinearGradient({
colors: ['#4a90e2', '#7b68ee'],
begin: Alignment.topCenter,
end: Alignment.bottomCenter
})
})
}),
// 프로필 이미지 위치
Positioned({
left: 20,
bottom: 20,
child: Container({
width: 80,
height: 80,
decoration: new BoxDecoration({
color: 'white',
shape: BoxShape.circle,
border: Border.all({ color: 'white', width: 3 })
}),
child: Center({ child: Text("👤", { style: new TextStyle({ fontSize: 40 }) }) })
})
}),
// 이름과 설명
Positioned({
left: 120,
bottom: 50,
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("홍길동", {
style: new TextStyle({
color: 'white',
fontSize: 24,
fontWeight: 'bold'
})
}),
Text("UI/UX 디자이너", {
style: new TextStyle({
color: 'rgba(255,255,255,0.8)',
fontSize: 16
})
})
]
})
}),
// 팔로우 버튼
Positioned({
right: 20,
bottom: 20,
child: Container({
padding: EdgeInsets.symmetric({ horizontal: 20, vertical: 10 }),
decoration: new BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(20)
}),
child: Text("팔로우", {
style: new TextStyle({
color: '#4a90e2',
fontWeight: 'bold'
})
})
})
})
]
})
})
🚀 실습 3: 알림 뱃지가 있는 아이콘
Container({
width: 60,
height: 60,
child: Stack({
children: [
// 아이콘 배경
Container({
width: 60,
height: 60,
decoration: new BoxDecoration({
color: '#f5f5f5',
shape: BoxShape.circle
}),
child: Center({
child: Text("🔔", { style: new TextStyle({ fontSize: 30 }) })
})
}),
// 알림 뱃지
Positioned({
right: 0,
top: 0,
child: Container({
width: 20,
height: 20,
decoration: new BoxDecoration({
color: '#f44336',
shape: BoxShape.circle,
border: Border.all({ color: 'white', width: 2 })
}),
child: Center({
child: Text("3", {
style: new TextStyle({
color: 'white',
fontSize: 12,
fontWeight: 'bold'
})
})
})
})
})
]
})
})
💡 실용적인 예제: 이미지 갤러리 오버레이
Container({
width: 350,
height: 250,
child: Stack({
children: [
// 배경 이미지 (색상으로 대체)
Container({
decoration: new BoxDecoration({
gradient: new RadialGradient({
colors: ['#ffd89b', '#19547b'],
center: Alignment.center,
radius: 1.5
})
})
}),
// 그라데이션 오버레이
Container({
decoration: new BoxDecoration({
gradient: new LinearGradient({
colors: ['transparent', 'rgba(0,0,0,0.7)'],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.5, 1.0]
})
})
}),
// 제목과 설명
Positioned({
left: 20,
bottom: 20,
right: 20,
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("아름다운 일몰", {
style: new TextStyle({
color: 'white',
fontSize: 24,
fontWeight: 'bold'
})
}),
SizedBox({ height: 8 }),
Text("제주도 협재 해변에서 촬영한 환상적인 일몰 풍경", {
style: new TextStyle({
color: 'rgba(255,255,255,0.8)',
fontSize: 14
})
})
]
})
}),
// 좋아요 버튼
Positioned({
right: 20,
top: 20,
child: Container({
width: 40,
height: 40,
decoration: new BoxDecoration({
color: 'rgba(255,255,255,0.3)',
shape: BoxShape.circle
}),
child: Center({
child: Text("❤️", { style: new TextStyle({ fontSize: 20 }) })
})
})
})
]
})
})
🎨 연습 과제
- 플로팅 액션 버튼: 우측 하단에 고정된 원형 버튼 만들기
- 툴팁 오버레이: 호버 시 나타나는 툴팁 구현하기
- 이미지 텍스트 오버레이: 이미지 위에 텍스트와 그라데이션 배치하기
🚨 주의사항
- Positioned는 Stack 내부에서만: Stack의 직접 자식이어야 함
- overflow 처리: Stack 밖으로 나가는 요소 주의
- z-index: children 배열의 순서가 z-index 역할
🔗 다음 단계
- Padding과 Margin - 여백 조절하기
- Expanded와 Flexible - 공간 분배하기