버튼과 입력 — ElevatedButton, TextField, Form
버튼과 입력 — ElevatedButton, TextField, Form
사용자 인터랙션의 기본은 버튼과 텍스트 입력입니다. Flutter의 Material Design 버튼 종류와 TextField, Form 위젯 사용법을 정리해보겠습니다.
버튼 종류
ElevatedButton (강조 버튼)
ElevatedButton(
onPressed: () {
print('버튼 클릭!');
},
child: const Text('확인'),
)
// 비활성화
ElevatedButton(
onPressed: null, // null이면 비활성화
child: const Text('비활성화'),
)
// 아이콘 포함
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('전송'),
)
버튼 스타일링
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
textStyle: const TextStyle(fontSize: 16),
),
child: const Text('커스텀 버튼'),
)
다른 버튼 종류들
// TextButton — 텍스트만 있는 가벼운 버튼
TextButton(
onPressed: () {},
child: const Text('텍스트 버튼'),
)
// OutlinedButton — 테두리만 있는 버튼
OutlinedButton(
onPressed: () {},
child: const Text('아웃라인 버튼'),
)
// IconButton — 아이콘만 있는 버튼
IconButton(
onPressed: () {},
icon: const Icon(Icons.favorite),
)
// FloatingActionButton — 플로팅 액션 버튼
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
)
// FilledButton — Material 3 채워진 버튼
FilledButton(
onPressed: () {},
child: const Text('채워진 버튼'),
)
| 버튼 | 용도 |
|---|---|
| ElevatedButton | 주요 액션 (저장, 확인) |
| TextButton | 부수적 액션 (취소, 더보기) |
| OutlinedButton | 중간 강조 |
| FilledButton | Material 3 기본 버튼 |
| IconButton | 아이콘 액션 (좋아요, 공유) |
| FAB | 화면의 가장 중요한 액션 1개 |
TextField — 텍스트 입력
기본 사용
class SearchBar extends StatefulWidget {
const SearchBar({super.key});
@override
State<SearchBar> createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
// 텍스트 제어를 위한 컨트롤러
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // 반드시 해제!
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
decoration: InputDecoration(
labelText: '검색어 입력',
hintText: '검색할 내용을 입력하세요',
prefixIcon: const Icon(Icons.search),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _controller.clear(),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
onChanged: (value) {
print('입력 중: $value');
},
onSubmitted: (value) {
print('검색 실행: $value');
},
);
}
}
TextField 주요 속성
TextField(
controller: _controller,
keyboardType: TextInputType.emailAddress, // 키보드 타입
textInputAction: TextInputAction.next, // 키보드 엔터키 동작
obscureText: true, // 비밀번호 숨김
maxLines: 5, // 여러 줄 입력
maxLength: 100, // 최대 글자 수
enabled: true, // 활성화 여부
autofocus: true, // 자동 포커스
readOnly: false, // 읽기 전용
onTap: () {}, // 탭 이벤트
)
InputDecoration 커스터마이징
InputDecoration(
labelText: '이메일', // 상단 라벨
hintText: 'email@example.com', // 플레이스홀더
helperText: '유효한 이메일을 입력하세요', // 하단 도움말
errorText: '이메일 형식이 아닙니다', // 에러 메시지
counterText: '', // 글자 수 카운터 숨김
filled: true,
fillColor: Colors.grey.shade100,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.red),
),
)
Form — 폼 관리
여러 입력 필드를 하나의 Form으로 묶어서 검증, 저장, 초기화를 한 번에 할 수 있습니다.
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submit() {
// 폼 검증
if (_formKey.currentState!.validate()) {
// 검증 통과 시 처리
final email = _emailController.text;
final password = _passwordController.text;
print('로그인: $email');
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
// 이메일 입력
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: '이메일',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return '이메일을 입력하세요';
}
if (!value.contains('@')) {
return '유효한 이메일을 입력하세요';
}
return null; // 검증 통과
},
),
const SizedBox(height: 16),
// 비밀번호 입력
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: '비밀번호',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) {
if (value == null || value.length < 6) {
return '비밀번호는 6자 이상이어야 합니다';
}
return null;
},
),
const SizedBox(height: 24),
// 제출 버튼
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _submit,
child: const Text('로그인'),
),
),
],
),
);
}
}
면접 포인트: GlobalKey<FormState>로 Form의 상태에 접근하고, validate()로 모든 필드를 한 번에 검증할 수 있습니다. TextFormField의 validator가 null을 반환하면 검증 통과입니다.
FocusNode — 포커스 관리
class FocusExample extends StatefulWidget {
const FocusExample({super.key});
@override
State<FocusExample> createState() => _FocusExampleState();
}
class _FocusExampleState extends State<FocusExample> {
final _emailFocus = FocusNode();
final _passwordFocus = FocusNode();
@override
void dispose() {
_emailFocus.dispose();
_passwordFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
focusNode: _emailFocus,
textInputAction: TextInputAction.next,
// 엔터 누르면 다음 필드로 이동
onSubmitted: (_) {
FocusScope.of(context).requestFocus(_passwordFocus);
},
),
TextField(
focusNode: _passwordFocus,
textInputAction: TextInputAction.done,
onSubmitted: (_) {
// 키보드 숨기기
FocusScope.of(context).unfocus();
},
),
],
);
}
}
정리
- Material Design 버튼은 용도에 맞게 ElevatedButton, TextButton, OutlinedButton 등을 선택하세요
- TextField에는 반드시
TextEditingController를 사용하고,dispose()에서 해제하세요 - 여러 입력 필드는
Form+TextFormField로 묶어 검증하는 것이 깔끔합니다 FocusNode로 입력 필드 간 포커스 이동을 관리할 수 있습니다validator가 null을 반환하면 검증 통과, String을 반환하면 에러 메시지입니다
댓글 로딩 중...