
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 this later.",
style: TextStyle(
fontSize: Sizes.size16,
color: Colors.black54,
),
),
Gaps.v16,
TextField(
controller: _usernameController,
decoration: InputDecoration(
hintText: "Username",
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
),
cursorColor: Theme.of(context).primaryColor,
),
Gaps.v28,
FractionallySizedBox(
widthFactor: 1,
child: AnimatedContainer(
padding: const EdgeInsets.symmetric(
vertical: Sizes.size16,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Sizes.size5),
color: _username.isEmpty
? Colors.grey.shade300
: Theme.of(context).primaryColor,
),
duration: const Duration(milliseconds: 500),
child: const Text(
'Next',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
}
}
위 코드는 UsernameScreen 위젯을 Flutter에서 정의하고 있습니다. 해당 화면의 주요 내용은 다음과 같습니다:
초기화:
TextField의 텍스트를 관리하기 위해 _usernameController라는 TextEditingController이 정의되어 있습니다._username이라는 private 변수가 있습니다.initState() 메서드 내에서, _usernameController에 리스너가 추가되어 있습니다. 컨트롤러의 텍스트가 변경될 때마다 _username 변수가 업데이트되고, setState가 호출되어 위젯을 다시 빌드합니다.Scaffold:
Scaffold 위젯으로 구성되어 있습니다.AppBar가 설정되어 있습니다.Body:
Padding 위젯입니다.Column 위젯 내부에서 위젯들이 수직으로 배열됩니다.Text 위젯으로 사용자는 사용자명을 생성하는 과정을 소개받습니다.TextField는 사용자가 사용자명을 입력할 수 있습니다.AnimatedContainer는 버튼 역할을 합니다. _username이 비어있는 경우 버튼의 색상은 회색입니다. 그렇지 않으면 앱의 주요 색상을 사용합니다. 사용자명이 입력되거나 제거될 때 버튼의 색상 전환은 500 밀리초 동안 애니메이션됩니다.TextField:
TextField는 _usernameController와 연결된 컨트롤러를 가지고 있습니다. 이 컨트롤러는 변경 사항을 감지하고 _username 변수를 그에 따라 업데이트합니다.TextField의 장식은 "Username"라는 힌트와 사용자 정의 밑줄 테두리를 보여줍니다. 커서 색상은 앱의 주제의 주요 색상으로 설정됩니다.AnimatedContainer (Next 버튼):
FractionallySizedBox는 AnimatedContainer(버튼)이 부모의 전체 너비로 늘어나도록 합니다._username이 비어 있는지 여부에 따라 동적으로 변경됩니다. 비어 있으면 버튼은 Colors.grey.shade300 색상이 되고, 그렇지 않으면 앱의 주제의 주요 색상을 사용합니다.이 화면은 사용자가 사용자명을 입력할 수 있는 회원가입 과정의 일부로 보입니다. Next 버튼의 색상은 사용자가 진행할 수 있는지(버튼이 주요 색상일 때) 아니면 사용자명이 아직 입력되지 않았는지(버튼이 회색일 때)를 나타냅니다.
initState 메서드는 Flutter의 StatefulWidget 수명 주기 중에서 위젯이 State 객체에 최초로 만들어질 때 한 번만 호출됩니다. 이 메서드는 State 객체가 위젯 트리에 삽입될 때 초기 설정이 필요한 경우 사용합니다.
제공해주신 initState 내용을 살펴보면:
super.initState();: 상위 클래스의 initState를 호출합니다. 이 호출은 항상 initState의 시작 부분에서 이루어져야 합니다.
_usernameController.addListener(() { ... });: _usernameController에 리스너를 추가합니다. TextEditingController의 값이 변경될 때마다 이 리스너는 호출됩니다.
setState(() { ... });: _usernameController의 텍스트가 변경될 때마다 setState를 사용하여 State 객체를 업데이트합니다. setState 내부에서 _username 변수의 값을 _usernameController.text로 업데이트합니다. setState를 호출하면 build 메서드가 다시 호출되어 UI가 업데이트됩니다.
이렇게 하면 사용자가 TextField에 텍스트를 입력할 때마다 _username 변수의 값이 실시간으로 업데이트되고, 필요한 경우 UI도 업데이트됩니다.
import 'package:flutter/material.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
import 'package:tiktok_clone/features/authentication/password_screen.dart';
import 'package:tiktok_clone/features/authentication/widgets/form_button.dart';
class EmailScreen extends StatefulWidget {
const EmailScreen({super.key});
State<EmailScreen> createState() => _EmailScreenState();
}
class _EmailScreenState extends State<EmailScreen> {
final TextEditingController _emailController = TextEditingController();
String _email = "";
void initState() {
super.initState();
_emailController.addListener(() {
setState(() {
_email = _emailController.text;
});
});
}
void dispose() {
_emailController.dispose();
super.dispose();
}
String? _isEmailValid() {
if (_email.isEmpty) return null;
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.push(
context,
MaterialPageRoute(
builder: (context) => const PasswordScreen(),
),
);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onScaffoldTap,
child: 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(
"What is your email?",
style: TextStyle(
fontSize: Sizes.size24,
fontWeight: FontWeight.w700,
),
),
Gaps.v16,
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
onEditingComplete: _onSubmit,
decoration: InputDecoration(
hintText: "Email",
errorText: _isEmailValid(),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
),
cursorColor: Theme.of(context).primaryColor,
),
Gaps.v28,
GestureDetector(
onTap: _onSubmit,
child: FormButton(
disabled: _email.isEmpty || _isEmailValid() != null,
),
),
],
),
),
),
);
}
}
위의 코드는 Flutter에서 사용되는 TextField 위젯입니다. 여기서 사용자는 이메일 주소를 입력할 수 있습니다. 해당 위젯의 각 속성에 대해 간략히 설명드리겠습니다.
controller:
TextEditingController의 인스턴스인 _emailController를 사용합니다. keyboardType:
TextInputType.emailAddress를 사용하여 키보드 타입을 이메일 주소 입력용으로 설정합니다. 따라서 키보드에 '@' 기호 등이 우선적으로 표시됩니다.onEditingComplete:
_onSubmit 메서드가 호출됩니다. 즉, 사용자가 키보드에서 '완료'나 '다음' 버튼을 누르면 _onSubmit이 실행됩니다.decoration:
TextField의 디자인 및 모양을 정의하는 InputDecoration을 사용합니다.hintText를 사용하여 텍스트 필드에 힌트 텍스트 "Email"을 표시합니다.errorText는 _isEmailValid() 함수를 호출하여 이메일 주소의 유효성을 검사하고, 유효하지 않은 경우 에러 메시지를 표시합니다. errorText 속성은 문자열 값을 필요로 합니다. 따라서 errorText: _isEmailValid와 같이 함수를 직접 참조하는 것은 올바르지 않습니다. 대신, errorText: _isEmailValid()와 같이 함수를 호출하여 그 결과값 (이 경우 문자열 또는 null)을 errorText 속성에 제공해야 합니다.
정리하면, 현재 코드에서 errorText: _isEmailValid(),는 올바르게 작성된 것입니다.
enabledBorder와 focusedBorder를 사용하여 텍스트 필드의 아래쪽 경계선의 디자인을 정의합니다.Theme.of(context).primaryColor로 설정합니다.이 TextField 위젯은 사용자에게 이메일 주소를 입력하도록 안내하고, 입력된 이메일 주소의 유효성을 실시간으로 검사하여 에러 메시지를 표시하는 역할을 합니다.
autocorrect: false,는 TextField 위젯의 속성 중 하나입니다. 이 속성은 자동 수정 기능을 활성화 또는 비활성화하는 데 사용됩니다.
예를 들어, 사용자가 이메일 주소나 비밀번호와 같은 특정 형식의 텍스트를 입력해야 할 때 자동 수정 기능이 활성화되어 있으면, 사용자가 원하는 텍스트가 아닌 다른 텍스트로 변경될 수 있습니다. 이러한 경우에 autocorrect: false를 설정하여 자동 수정 기능을 비활성화할 수 있습니다.
따라서, 위에서 제공한 EmailScreen 예제에서 이메일 주소를 입력하는 TextField에 autocorrect: false를 추가하는 것은 좋은 아이디어입니다. 다음과 같이 코드에 추가할 수 있습니다:
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
onEditingComplete: _onSubmit,
autocorrect: false, // 자동 수정 기능 비활성화
decoration: InputDecoration(
hintText: "Email",
errorText: _isEmailValid(),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade400,
),
),
),
cursorColor: Theme.of(context).primaryColor,
),
이렇게 하면 사용자가 이메일 주소를 입력할 때 자동 수정 기능에 의해 텍스트가 변경되지 않습니다.
TextField(
cursorColor: Theme.of(context).primaryColor,
decoration: InputDecoration(
hintText: "Add comment...",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
Sizes.size12,
),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey.shade200,
contentPadding: const EdgeInsets.symmetric(
vertical: Sizes.size10,
horizontal: Sizes.size12,
),
),
)
cursorColor: 이 속성은 텍스트 필드가 포커스를 가질 때 깜빡이는 커서의 색상을 결정합니다. 여기서는 현재 테마의 주요 색상이 커서 색상으로 사용됩니다.
decoration: InputDecoration은 입력 필드의 모양과 느낌을 정의하는 다양한 방법을 제공합니다. 그 속성들의 세부 정보는 다음과 같습니다:
OutlineInputBorder가 사용됩니다.Sizes.size12 상수(앱의 상수에서 가져온 값)가 사용됩니다.BorderSide.none은 테두리 자체가 어떤 선도 표시하지 않을 것임을 의미합니다.true는 채우기 색상이 있음을 의미합니다.Colors.grey.shade200)이 사용됩니다.요약하면, 이 TextField 위젯은 사용자가 댓글을 추가할 수 있는 입력 필드를 나타냅니다. 주요 색상의 커서, 사용자를 안내하는 힌트 텍스트, 눈에 보이지 않는 테두리와 둥근 및 채워진 디자인, 그리고 내부의 콘텐츠가 적절하게 간격을 둔 상태로 표시되도록 특정 패딩이 있습니다.
이 TextField 위젯의 속성들에 대해 설명드리겠습니다:
TextField(
onTap: _onStartWriting,
expands: true,
minLines: null,
maxLines: null,
textInputAction: TextInputAction.newline,
)
onTap: onTap 속성은 사용자가 해당 TextField를 탭할 때 호출되는 콜백 함수입니다. 여기서는 _onStartWriting라는 함수가 호출됩니다.
expands: expands 속성이 true로 설정되면 TextField의 크기가 부모 위젯의 사용 가능한 공간을 최대한 확장하려고 합니다. 이 속성을 사용하면 minLines 및 maxLines 속성은 무시됩니다.
minLines와 maxLines: 이 두 속성은 TextField의 최소 및 최대 줄 수를 정의합니다. 둘 다 null로 설정되어 있으면, TextField는 부모 위젯의 사용 가능한 공간에 따라 크기가 자동으로 조절됩니다.
textInputAction: 이 속성은 소프트 키보드에서의 특정 동작을 정의합니다. 여기서 TextInputAction.newline가 설정되어 있으므로, 사용자는 엔터(줄바꿈) 키를 통해 새 줄을 시작할 수 있습니다.
이 TextField 설정은 주로 여러 줄의 텍스트를 입력하도록 설정된 경우에 유용합니다. onTap 콜백을 통해 사용자가 입력을 시작할 때 특정 동작을 실행하도록 설정할 수 있습니다.