TextField
는 간단한 텍스트 입력 필드로, 사용자가 텍스트를 입력할 수 있는 기본적인 UI 컴포넌트입니다.TextEditingController
), 스타일링, 입력 형식 지정(TextInputType
), 장식(InputDecoration
) 등의 기본적인 텍스트 입력 기능을 제공합니다.전체코드
import 'package:flutter/material.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
class UsernameScreen extends StatefulWidget {
const UsernameScreen({super.key});
State<UsernameScreen> createState() => _UsernameScreenState();
}
class _UsernameScreenState extends State<UsernameScreen> {
final TextEditingController _usernameController = TextEditingController();
String _username = '';
void initState() {
super.initState();
_usernameController.addListener(() {
setState(() {
_username = _usernameController.text;
});
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Sign up',
),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: Sizes.size36),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Gaps.v40,
const Text(
'Create username',
style: TextStyle(
fontSize: Sizes.size24,
fontWeight: FontWeight.w700,
),
),
Gaps.v8,
const Text(
'You can always change it later.',
style: TextStyle(
fontSize: Sizes.size16,
color: Colors.black54,
),
),
Gaps.v16,
TextField(
controller: _usernameController,
cursorColor: Theme.of(context).primaryColor,
decoration: InputDecoration(
hintText: 'Username',
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
),
),
Gaps.v16,
FractionallySizedBox(
widthFactor: 1,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(vertical: Sizes.size16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Sizes.size5),
color: _username.isEmpty
? Colors.grey.shade300
: Theme.of(context).primaryColor,
),
child: const Text(
'Next',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: Sizes.size16,
fontWeight: FontWeight.w600,
),
),
),
)
],
),
),
);
}
}
TextField Widget을 사용하기 위해서는 Stateful Widget에서 Controller를 멤버 변수로 선언해야한다.
class _UsernameScreenState extends State<UsernameScreen> {
final TextEditingController _usernameController = TextEditingController();
String _username = '';
void initState() {
super.initState();
_usernameController.addListener(() {
setState(() {
_username = _usernameController.text;
});
});
}
다음과 같이 _usernameController
를 TextEditingController()
로 선언한 후 [[GDSC스터디/initState]]를 통해 addListener로 setState를 정의해준다.
FractionallySizedBox(
widthFactor: 1,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(vertical: Sizes.size16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Sizes.size5),
color: _username.isEmpty
? Colors.grey.shade300
: Theme.of(context).primaryColor,
),
child: const Text(
'Next',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: Sizes.size16,
fontWeight: FontWeight.w600,
),
),
),
)
final TextEditingController _emailController = TextEditingController();
String _email = '';
String? _isEmailValid() {
if (_email.isEmpty) return 'Not valid';
final regExp = RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
if (!regExp.hasMatch(_email)) return 'Email not valid';
return null;
}
void _onScaffoldTap() {
FocusScope.of(context).unfocus();
}
void _onSubmit() {
if (_email.isEmpty || _isEmailValid() != null) return;
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const PasswordScreen(),
),
);
}
TextField(
onEditingComplete: _onSubmit,
autocorrect: false,
keyboardType: TextInputType.emailAddress,
controller: _emailController,
cursorColor: Theme.of(context).primaryColor,
decoration: InputDecoration(
hintText: 'Email',
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
errorText: _isEmailValid(),
),
),
onEditingComplete
void Function()? onEditingComplete
autocorrect
bool
keyboardType
TextInputType? keyboardType
errorText
String? errorTex
enabled
prefixIcon: const Icon(Icons.ac_unit),
suffixIcon: const Icon(Icons.ac_unit),
다음과 같이 InputDecoration
내부에서 설정할 수 있다.
textfield 앞 뒤로 아이콘을 넣을 수 있다.
icon만이 아닌 위젯을 넣고 싶다면 prefix
, suffix
parameter를 사용하면 된다.
suffix: const Row(
children: [
FaIcon(FontAwesomeIcons.circleXmark),
Gaps.h5,
FaIcon(FontAwesomeIcons.eye),
],
),
다음과 같이 suffix를 설정하면 screen이 다음과 같이 된다.
그 이유는 Row는 최대한 많은 너비를 가지려는 성질이 있기 때문이다.
이를 해결하기 위해서는 Row()
의 가로 길이인 MainAxisSize.min
을 설정해야한다.
[[Row & Column 공간 문제]]
하지만 한 화면에 입력칸이 두 개 이상이거나, 로그인 화면, 사용자 프로필 설정과 같은 화면에서는 강력한 유효성 검사가 중요하기 때문에 TextField
위젯이 아닌 다른 위젯을 사용한다.