Input 위젯 대표적인 5가지
- Checkbox: 여러 개 항목을 true/false로 선택
- Radio: 여러 옵션 중 1개만 선택
- Slider: 0~100 같은 연속 값(또는 구간 값)을 드래그로 선택
- Switch / CupertinoSwitch: on/off 토글
- PopupMenuButton: 메뉴에서 항목을 선택
// - Checkbox: 다중 선택
// - Radio: 단일 선택
// - Slider: 연속 값 선택
// - Switch/CupertinoSwitch: ON/OFF 토글
// - PopupMenuButton: 메뉴 선택
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
// 앱 실행 진입점
runApp(
MaterialApp(
// MaterialApp은 테마, 라우팅 등 머티리얼 앱 전반 설정을 담당
home: Scaffold(
// Scaffold는 기본 화면 레이아웃(앱바, 바디 등)을 제공
appBar: AppBar(
title: Text("Let's Study Container"),
),
body: Body(),
),
),
);
}
/// 화면에 여러 입력 위젯 데모를 세로로 배치하는 위젯
class Body extends StatelessWidget {
const Body({super.key});
@override
Widget build(BuildContext context) {
// Column: 자식 위젯들을 세로로 배치
return const Column(
children: [
TestCheckBox(),
TestRadioButton(),
TestSlider(),
TestSwitch(),
TestPopupMenu(),
],
);
}
}
// ------------------------------------------------------------
// 1) Checkbox (다중 선택)
// ------------------------------------------------------------
class TestCheckBox extends StatefulWidget {
const TestCheckBox({super.key});
@override
State<TestCheckBox> createState() => _TestCheckBoxState();
}
class _TestCheckBoxState extends State<TestCheckBox> {
// 체크 상태를 저장하는 리스트
// 예: 3개의 체크박스 상태를 [false, false, false]로 관리
late List<bool> values;
@override
void initState() {
super.initState();
// initState는 위젯이 처음 생성될 때 딱 1번 호출됨
// 여기서 상태 초기화를 해두면 build마다 초기화되는 실수를 방지할 수 있음
values = [false, false, false];
}
@override
Widget build(BuildContext context) {
// Row: 자식 위젯들을 가로로 배치
return Row(
children: [
Checkbox(
// 현재 체크 상태
value: values[0],
// 사용자가 체크/해제할 때 호출되는 콜백
// value는 bool? (nullable)로 들어오므로 안전 처리 필요
onChanged: (value) => changeValue(0, value: value),
),
Checkbox(
value: values[1],
onChanged: (value) => changeValue(1, value: value),
),
Checkbox(
value: values[2],
onChanged: (value) => changeValue(2, value: value),
),
],
);
}
/// 특정 index의 체크 상태를 갱신하는 함수
///
/// - setState 내부에서 상태를 변경해야 화면이 다시 렌더링됨
/// - Checkbox의 onChanged는 bool?을 전달하기 때문에 파라미터를 nullable로 받음
void changeValue(int index, {bool? value = false}) {
setState(() {
// value가 null일 가능성에 대비해서 !로 단언
// (실전에서는 value ?? false 같이 널 병합 연산자를 더 자주 사용함)
values[index] = value!;
});
}
}
// ------------------------------------------------------------
// 2) Radio (단일 선택)
// ------------------------------------------------------------
class TestRadioButton extends StatefulWidget {
const TestRadioButton({super.key});
@override
State<TestRadioButton> createState() => _TestRadioButtonState();
}
/// 라디오 버튼에서 선택 가능한 값들을 enum으로 정의
///
/// - enum을 쓰면 문자열/정수로 관리할 때보다 안정적으로 선택값을 다룰 수 있음
enum TestValue {
test1,
test2,
test3,
}
class _TestRadioButtonState extends State<TestRadioButton> {
// 현재 선택된 값
// null이면 아직 아무것도 선택되지 않은 상태
TestValue? selectValue;
@override
Widget build(BuildContext context) {
return Column(
children: [
// ListTile을 활용하면 라디오 + 텍스트 + 탭 영역을 한 줄로 구성하기 편함
ListTile(
leading: Radio<TestValue>(
// 해당 Radio가 대표하는 값
value: TestValue.test1,
// 현재 그룹에서 선택된 값 (이 값과 value가 같으면 체크됨)
groupValue: selectValue,
// 라디오를 직접 눌렀을 때 실행
onChanged: (value) => setState(() {
selectValue = value!;
}),
),
// enum의 name을 사용하면 "test1" 같은 문자열을 바로 얻을 수 있음
title: Text(TestValue.test1.name),
// 타일 전체를 탭해도 선택되게 만들기
onTap: () => setState(() {
if (selectValue != TestValue.test1) {
selectValue = TestValue.test1;
}
}),
),
Radio<TestValue>(
value: TestValue.test2,
groupValue: selectValue,
onChanged: (value) => setState(() {
selectValue = value!;
}),
),
Radio<TestValue>(
value: TestValue.test3,
groupValue: selectValue,
onChanged: (value) => setState(() {
selectValue = value!;
}),
),
],
);
}
}
// ------------------------------------------------------------
// 3) Slider (연속 값 선택)
// ------------------------------------------------------------
class TestSlider extends StatefulWidget {
const TestSlider({super.key});
@override
State<TestSlider> createState() => _TestSliderState();
}
class _TestSliderState extends State<TestSlider> {
// 슬라이더 값은 double
// (Slider 위젯이 double 기반)
double value = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
// 현재 값을 화면에 표시 (반올림해서 정수처럼 보이도록)
Text('${value.round()}'),
Slider(
// 현재 값
value: value,
// 드래그해서 값이 바뀔 때마다 호출
onChanged: (newValue) => setState(() => value = newValue),
// divisions를 지정하면 "구간" 단위로 끊기는 슬라이더가 됨
// 0~100을 100분할 → 사실상 정수 단위처럼 사용 가능
divisions: 100,
// 최댓값/최솟값
max: 100,
min: 0,
// 사용자가 드래그 중일 때 보이는 라벨
label: value.round().toString(),
),
],
);
}
}
// ------------------------------------------------------------
// 4) Switch / CupertinoSwitch (토글)
// ------------------------------------------------------------
class TestSwitch extends StatefulWidget {
const TestSwitch({super.key});
@override
State<TestSwitch> createState() => _TestSwitchState();
}
class _TestSwitchState extends State<TestSwitch> {
// 토글 상태 (on/off)
bool value = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Material 디자인 Switch
Switch(
value: value,
onChanged: (newValue) => setState(() => value = newValue),
),
// iOS 스타일 Switch (Cupertino)
// 같은 value를 공유해서 두 스위치가 동시에 동일 상태를 반영
CupertinoSwitch(
value: value,
onChanged: (newValue) => setState(() => value = newValue),
),
],
);
}
}
// ------------------------------------------------------------
// 5) PopupMenuButton (메뉴 선택)
// ------------------------------------------------------------
class TestPopupMenu extends StatefulWidget {
const TestPopupMenu({super.key});
@override
State<TestPopupMenu> createState() => _TestPopupMenuState();
}
class _TestPopupMenuState extends State<TestPopupMenu> {
// 기본 선택값을 test1로 설정
TestValue selectValue = TestValue.test1;
@override
Widget build(BuildContext context) {
return Column(
children: [
// 현재 선택된 값을 텍스트로 표시
Text(selectValue.name),
// PopupMenuButton을 누르면 드롭다운 메뉴가 뜸
PopupMenuButton<TestValue>(
// 메뉴 아이템 구성
itemBuilder: (context) {
// enum 전체 값을 순회하면서 메뉴 아이템 리스트 생성
return TestValue.values
.map(
(value) => PopupMenuItem<TestValue>(
value: value,
child: Text(value.name),
),
)
.toList();
},
// 사용자가 메뉴에서 항목을 선택하면 호출
onSelected: (newValue) => setState(() => selectValue = newValue),
),
],
);
}
}