Flutter - 코딩마스터하기|인증페이지 만들기2
keyboard에 따른 화면 조절
키보드가 올라온 상태로 밑에 버튼이 올라옴. 이를 방지하기 위해서 auth_screen에서 Scaffold에서 resizeToAvoidBottomInset 옵션을 false로 해주면 키보드가 올라와도 하단의 버튼이 올라오지 않는다. 그런데 본문 부분이 아래까지 길 경우에는 보이지가 않는다. 이를 해결하기 위해서는 sign_up_form에서 해결해야 한다. Padding를 Scaffold로 감싸주고 같은 옵션을 주고 true로 해주면 된다.그리고 TextFormField border도 method로 같은 부분끼리 묶어준다.
sign_up_form.dart
InputDecoration _textInputDecor(String hint) {
return InputDecoration(
hintText: hint,
enabledBorder: _activeInputBorder(),
errorBorder: _errorInputBorder(),
focusedBorder: _activeInputBorder(),
focusedErrorBorder: _errorInputBorder(),
filled: true,
fillColor: Colors.grey[100],
);
}
OutlineInputBorder _errorInputBorder() {
return OutlineInputBorder(
borderSide: BorderSide(
color: Colors.redAccent,
),
borderRadius: BorderRadius.circular(common_s_gap),
);
}
OutlineInputBorder _activeInputBorder() {
return OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey[300],
),
borderRadius: BorderRadius.circular(common_s_gap),
);
}
auth_screen.dart
class _AuthScreenState extends State<AuthScreen> {
int selectedForm = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false, // 기본값은 true, 밑에꺼를 올려줌, 근데왜 작동이 안하지?
body: SafeArea(
child: Stack(
children: [
FadeStack(
selectedForm: selectedForm,
),
Positioned(
left: 0,
right: 0,
bottom: 0,
height: 40,
child: Container(
color: Colors.white,
child: FlatButton(
shape: Border(
top: BorderSide(
color: Colors.grey,
width: 1,
),
),
onPressed: () {
//이 하단의 코드가 문제점은 기존걸 변경시켜주는게 아닌 새로 생성해주는것임.
if (selectedForm == 0) {
setState(() {
selectedForm = 1;
});
} else {
setState(() {
selectedForm = 0;
});
}
},
child: RichText(
text: TextSpan(
text: (selectedForm == 0)
? 'Already have an account? '
: "Don't have an account",
style: TextStyle(color: Colors.grey),
children: [
TextSpan(
text: (selectedForm == 0) ? 'Sign In' : 'Sign Up',
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold),
),
]),
),
),
),
),
],
),
),
);
}
}
main, join화면 이동
Join 버튼에서 validate가 true시 페이지 이동을 하면 된다. Navigator.of(context)를 사용하여 .pushReplacement를 사용하여 페이지를 이동한다. .push를 사용하는 방법도 있지만 현재 페이지를 뒤로 보내고 새로 불러오는 방식인데 이 방식은 뒤로 돌아갈때 그대로 남아있어 여기서 쓰는것은 좋지 않다. 그래서 pushReplacement로 현재 페이지를 교체해주는 방식으로 사용하면 된다. ()안에 MaterialPageRoute()를 넣고 그 안에 build 옵션에 (context) => HomePage()로 원하는 페이지로 이동시킬 수 있다. join에서 main을 했으면 join으로도 가게 해줘야 한다. 사이드 메뉴에 로그아웃 버튼을 만들어 놨는데, 여기에 똑같이 만들고 AuthScreen으로 이동시켜주면 된다. 여기서 Log Out은 ListTile로 만들어 줬는데 버튼을 새로 만들필요없이 OnTap에 똑같이 작성해 주면 된다.
sign_up_form.dart
FlatButton(
onPressed: () {
if (_formKey.currentState.validate()) {
//3개다 true면 서버에 저장하거나 값 읽어오거나 하면됨.
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
}
},
child: Text(
'Join',
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(common_xxs_gap),
),
),
profile_side_menu.dart
ListTile(
leading: Icon(
Icons.exit_to_app,
color: Colors.black87,
),
title: Text('Log out'),
onTap: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => AuthScreen(),
),
);
},
),
커스텀 Divider
일반 가입이나 로그인 밑에 페이스북 가입과 나누는 or가 있는 divider 만들기. 같은 자리에 겹쳐서 만들기 위해 Stack로 만들어준다. 그리고 Container로 grey[300], height 1을 만들어주고 Positioned로 감싸주고 left 0, right 0, height 1,로 해준다. 그리고 가운데는 빈것 처럼 보여주기위해서 Positioned 하단에 Container로 배경과 같은 grey[100], height 3, width 60으로 만들어 준다. 그리고 그 하단에 Text로 OR을 해주고 색은 grey[500] bold 로 해주면 된다.
SizedBox(
height: common_s_gap,
),
Stack(
alignment: Alignment.center,
children: [
Positioned(
left: 0,
right: 0,
height: 1,
child: Container(
color: Colors.grey[300],
height: 1,
)),
Container(
color: Colors.grey[100],
height: 3,
width: 60,
),
Text(
'OR',
style: TextStyle(
color: Colors.grey[500],
fontWeight: FontWeight.bold,
),
),
],
),
flutter button icon 사용하기
페이스북 로그인 버튼을 만들어 보자. 우선 divider로 만든 stack 부분을 method로 refactor 해주자. 그리고 그 아래에 FlatButton.icon으로 아이콘모양의 버튼을 만들어 주면 된다. icon 옵션에 ImageIcon을 넣어주고 그 안에 AssetImage에 facebook 이미지를 넣어준다. 그리고 label에 Login with Facebook 이라 적고, flatbutton에 textColor를 blue로 해준다. Text 내에 style를 주면되지만 icon의 색은 안바뀌므로 textColor을 바꿔주면 된다. 그리고 join 버튼도 method로 refactor 해준다.
FlatButton.icon(
onPressed: () {},
textColor: Colors.blue,
icon: ImageIcon(AssetImage('assets/images/facebook.png'),),
label:Text('Login with Facebook',),
)
FlatButton _submitButton(BuildContext context) {
return FlatButton(
onPressed: () {
if (_formKey.currentState.validate()) {
//3개다 true면 서버에 저장하거나 값 읽어오거나 하면됨.
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
}
},
child: Text(
'Join',
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(common_xxs_gap),
),
);
}
Stack _orDivider() {
return Stack(
alignment: Alignment.center,
children: [
Positioned(
left: 0,
right: 0,
height: 1,
child: Container(
color: Colors.grey[300],
height: 1,
)),
Container(
color: Colors.grey[100],
height: 3,
width: 60,
),
Text(
'OR',
style: TextStyle(
color: Colors.grey[500],
fontWeight: FontWeight.bold,
),
),
],
);
}
Sign In 만들기
_textInputDecor과 _errorInputBorder, _activeInputBorder 을 sign in 에서도 사용하기 위해서 state 밖의 하단으로 빼고 _도 빼준다. 그 후 constants에 auth_inputdecor.dart를 만들고 이동시켜준다.orDivider는 메소드로 뺀 부분의 return에서 Stack를 다시한번 widget로 refactor해준다. 그 후 widgets 폴더에 or_divider 를 만들어주고 widget를 잘라내서 넣어준다. 메소드 부분은 삭제하고 OrDivider로 해당 부분을 바꿔주면 된다. 그리고 SignInForm을 statefulWidget으로 바꿔주고 state 클래스 내용을 복사해서 붙여넣어준다. 그리고 confirm password 관련 부분, controller과 TextFormField 부분을 삭제해 주면 된다. 그러면 정상 작동 하는 것을 볼 수 있다.
sign_in_form.dart
class _SignInFormState extends State<SignInForm> {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
TextEditingController _emailController = TextEditingController();
TextEditingController _pwController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_pwController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: Padding(
padding: const EdgeInsets.all(common_gap),
child: Form(
key: _formKey,
child: ListView(
children: [
SizedBox(
height: common_l_gap,
),
Image.asset('assets/images/insta_text_logo.png'),
TextFormField(
cursorColor: Colors.black54,
controller: _emailController,
decoration: textInputDecor('Email'),
validator: (text) {
if (text.isNotEmpty && text.contains("@")) {
return null;
} else {
return '정확한 이메일 주소를 입력해주세요!';
}
},
),
SizedBox(
height: common_xs_gap,
),
TextFormField(
obscureText: true,
cursorColor: Colors.black54,
controller: _pwController,
decoration: textInputDecor('Password'),
validator: (text) {
if (text.isNotEmpty && text.length > 5) {
return null;
} else {
return '제대로 된 비밀번호를 입력해주세요!';
}
},
),
SizedBox(
height: common_s_gap,
),
_submitButton(context),
SizedBox(
height: common_s_gap,
),
OrDivider(),
FlatButton.icon(
onPressed: () {},
textColor: Colors.blue,
icon: ImageIcon(AssetImage('assets/images/facebook.png'),),
label:Text('Login with Facebook',),
),
],
),
),
),
);
}
FlatButton _submitButton(BuildContext context) {
return FlatButton(
onPressed: () {
if (_formKey.currentState.validate()) {
//3개다 true면 서버에 저장하거나 값 읽어오거나 하면됨.
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
}
},
child: Text(
'Sign In',
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(common_xxs_gap),
),
);
}
}
sign_up_form.dart
class _SignUpFormState extends State<SignUpForm> {
GlobalKey<FormState> _formKey = GlobalKey<FormState>(); //폼의 상태를 저장하는 키
TextEditingController _emailController = TextEditingController();
TextEditingController _pwController = TextEditingController();
TextEditingController _cpwController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_pwController.dispose();
_cpwController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: Padding(
padding: const EdgeInsets.all(common_gap),
child: Form(
key: _formKey,
child: ListView(
children: [
SizedBox(
height: common_l_gap,
),
Image.asset('assets/images/insta_text_logo.png'),
TextFormField(
cursorColor: Colors.black54,
controller: _emailController,
decoration: textInputDecor('Email'),
validator: (text) {
if (text.isNotEmpty && text.contains("@")) {
return null;
} else {
return '정확한 이메일 주소를 입력해주세요!';
}
},
),
SizedBox(
height: common_xs_gap,
),
TextFormField(
obscureText: true,
cursorColor: Colors.black54,
controller: _pwController,
decoration: textInputDecor('Password'),
validator: (text) {
if (text.isNotEmpty && text.length > 5) {
return null;
} else {
return '제대로 된 비밀번호를 입력해주세요!';
}
},
),
SizedBox(
height: common_xs_gap,
),
TextFormField(
obscureText: true,
cursorColor: Colors.black54,
controller: _cpwController,
decoration: textInputDecor('Confirm Password'),
validator: (text) {
if (text.isNotEmpty && _pwController.text == text) {
return null;
} else {
return '입력한 값이 비밀번호와 일치하지 않습니다.';
}
},
),
SizedBox(
height: common_s_gap,
),
_submitButton(context),
SizedBox(
height: common_s_gap,
),
OrDivider(),
FlatButton.icon(
onPressed: () {},
textColor: Colors.blue,
icon: ImageIcon(
AssetImage('assets/images/facebook.png'),
),
label: Text(
'Login with Facebook',
),
),
],
),
),
),
);
}
FlatButton _submitButton(BuildContext context) {
return FlatButton(
onPressed: () {
if (_formKey.currentState.validate()) {
//3개다 true면 서버에 저장하거나 값 읽어오거나 하면됨.
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
}
},
child: Text(
'Join',
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(common_xxs_gap),
),
);
}
}
auth_input_decor.dart
InputDecoration textInputDecor(String hint) {
return InputDecoration(
hintText: hint,
enabledBorder: activeInputBorder(),
errorBorder: errorInputBorder(),
focusedBorder: activeInputBorder(),
focusedErrorBorder: errorInputBorder(),
filled: true,
fillColor: Colors.grey[100],
);
}
OutlineInputBorder errorInputBorder() {
return OutlineInputBorder(
borderSide: BorderSide(
color: Colors.redAccent,
),
borderRadius: BorderRadius.circular(common_s_gap),
);
}
OutlineInputBorder activeInputBorder() {
return OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey[300],
),
borderRadius: BorderRadius.circular(common_s_gap),
);
}
or_divider.dart
class OrDivider extends StatelessWidget {
const OrDivider({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned(
left: 0,
right: 0,
height: 1,
child: Container(
color: Colors.grey[300],
height: 1,
)),
Container(
color: Colors.grey[100],
height: 3,
width: 60,
),
Text(
'OR',
style: TextStyle(
color: Colors.grey[500],
fontWeight: FontWeight.bold,
),
),
],
);
}
}
forgotten password 만들기 align 위젯 사용하기.
FlatButton으로 만들어 주고 child에 Text로 Forgotten Password? 를 해주고 style에 색을 blue로 해준다. 근데 글씨가 중앙정렬이 되어있는데 FlatButton에는 정렬이 없다. 그래서 Text를 Align으로 감사준다. 그리고 alignment옵션에 centerRight를 주면 된다.
FlatButton(
onPressed: () {},
child: Align(
alignment: Alignment.centerRight,
child: Text(
'Forgotten Password?',
style: TextStyle(
color: Colors.blue,
),
),
),
),