-
Flutter - 코딩마스터하기|인증페이지 만들기1개발/Flutter 2021. 11. 1. 19:08반응형
auth screen 생성
screens 폴더에 auth_screen 파일을 만들고 statefulWidget로 만들어 준다. 그리고 import를 시켜준다.
animated switcher (sign up form, sign in form)
바로바로 확인하기 위해서 main.dart에서 home을 AuthScreen()으로 바꿔준다. Scaffold안에 SafeArea를 만들고 그 안에 Container을 만들어 준다. 그리고 우선 sign in 과 sign up을 토글 시켜주기위한 버튼을 만들어 준다. FlatButton을 만들어 주고 그 안에 Text로 go to Sign up으로 해준다. 여기서 2가지 위젯을 만들어서 토글 시켜줄 것이다. widgets 폴더에 sign_up_form과 sign_in_form을 만들어 준다. stl로 만들어 주고 Container에 다른색을 넣어 준다. 그리고 다시 auth_screen으로 돌아와서 Container을 Stack로 감싸주고 Container 위에 AnimatedSwitcher을 해준다. child에 SignUpForm과 SignInForm을 바뀌게 넣어줄 것인데 이를 위해 클래스 상단에 Widget currentW
idget로 SignUpForm();을 만들어 준다. 그리고 하단에 FlatButton에 if문으로 currentWidget is SignUpForm 이면 SignInForm()으로 해주고 아니면 SignUpForm()으로 해준다. 하지만 이거에 단점은 기존 부분을 교체하는게 아닌 새로 생성을 한다는 것이다. 그래서 상단에 위젯을 먼저 만들어 둔다. 클래스 안에 상단에 Widget 일대 signUpFrom, signInForm을 각각 SignUpForm(), SignInForm()으로 생성 한다. 그리고 기존 currentWidget는 그냥 생성만 해주고 initState를 만들고 그안에서 if문으로 currentWidget == null 이면 currentWidget = signUpForm; 으로 하게 해준다. 실행해보면 정상적으로 바뀌는 것을 알 수 있다.(현재 왜 색이 안바뀌는지 모르겠음..... 버튼누르고 핫 리로드 할때만 바뀜.....) 추가 강의 영상에서 이렇게 불러와서 rebuild가 된다고 함. 그래서 AnimatedSwitcher에서 IndexedStack로 바꿈. 일단 그냥 해봄. List<widget> form를 클래스 상단에 만들어 주고 그 안에 SignUpForm(), SignInForm()을 넣어준다. 그리고 int selectedFrom = 0으로 줘서 첫 인덱스가 0으로 시작되게 해준다. 그리고 IndexedStack에 index에 selectedForm을 넣어주고 children에 forms를 넣어준다. 버튼의 if문도 selectedForm == 0 일때 1로 else에는 0으로 변경 시켜준다. 똑같이 정상적으로 작동 하는 것을 알 수 있다. 이제 바뀌는 애니메이션을 주기 위해서 FadeTransition을 IndexedStack로 감싸준데 opacity를 주기위해 상단의 클래스에 with SingleTickerProviderStateMixin을 준다. opacity에 컨트롤러를 주기 위해서다. 여기서는 한가지 애니메이션만 사용해서 SingleTickerProviderStateMixin로 하고 여러개를 사용하려면 다른것을 사용 하면 된다. 클래스내 상단에 AnimationController _animationController을 만들고 init안에 _animationController에 AnimationController(vsync: this)를 준다. this는 이 스테이스의 인스턴스이다. 그리고 duration에 duration을 넣어주고 didUpdateWidget를 만들어준다. 과거 위젯과 업데이트 위젯을 비교해준다. 여기서 비교되는 부분이 selectedForm인데 이게 state 내에 있어서 접근이 불가능하다. 그래서 하단의 애니메이션 부분을 위젯으로 빼줘서 하는게 제일 좋은 방법이다. widgets폴더에 fade_stack파일을 만들어주고 stf로 생성해준뒤 auth_screen의 with 부분과 init, didUpdateWidget를 옮겨와준다. FadeTransition은 복사해서 fade_stack의 return에 옮겨준다. 그리고 forms도 잘라내기로 옮겨준다. _FadeStackState에 넣어주고 FadaStack 클래스 안에는 final int selectedForm을 만들어주고 structur로 만들어 준다. 그리고 하단에 state클래스 안에 selectedFrom을 받아오는 부분을 widget.selectedForm으로 바꿔준다. FadeTrasition으로 만들어주면 처음엔 하얀 바탕으로 나옴. 그러나 왜 아직도 버튼을 눌러도 창이 안바뀌는지 모르겠음
sign_up_form.dart class SignUpForm extends StatelessWidget { const SignUpForm({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Container(color: Colors.greenAccent,); } }
sign_in_form.dart import 'package:flutter/material.dart'; class SignInForm extends StatelessWidget { const SignInForm({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Container(color: Colors.greenAccent,); } }
auth_screen.dart class AuthScreen extends StatefulWidget { @override _AuthScreenState createState() => _AuthScreenState(); } class _AuthScreenState extends State<AuthScreen> { int selectedForm = 0; @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Stack( children: [ FadeStack(selectedForm: selectedForm,), Container( child: FlatButton( onPressed: () { //이 하단의 코드가 문제점은 기존걸 변경시켜주는게 아닌 새로 생성해주는것임. if (selectedForm == 0) { selectedForm = 1; } else { selectedForm = 0; } }, child: Text('go to Sign Up'), ), ), ], ), ), ); } }
fade_stack.dart class FadeStack extends StatefulWidget { final int selectedForm; const FadeStack ({Key key, this.selectedForm}) : super(key: key); @override _FadeStackState createState() => _FadeStackState(); } class _FadeStackState extends State<FadeStack> with SingleTickerProviderStateMixin { AnimationController _animationController; List<Widget> forms = [ SignUpForm(), SignInForm(), ]; @override void initState() { _animationController = AnimationController(vsync: this, duration: duration,); _animationController.forward(); super.initState(); } @override void didUpdateWidget(covariant FadeStack oldWidget) { if(widget.selectedForm != oldWidget.selectedForm){ _animationController.forward(from: 0.0,); } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return FadeTransition( opacity: _animationController, child: IndexedStack( index: widget.selectedForm, children: forms, ), ); } }
richtext 버튼 바꾸기
FlatButton 안쪽에 Text를 보면 2가지 스타일이 다르단걸 알 수 있는데 이걸 할 수 있는 위젯이 RichText이다. RichText로 바꿔주고 text에 TextSpan을 넣어준다. 그리고 다시한번 text에 글자를 넣어준후 children에 TextSpan을 list로 여러개 쓸수 있다. 그안에도 TextSpan으로 써준후 style를 주면 된다. 우선 위에꺼에는 (selectedForm == 0) ?'Already have an account? ' : "Don't have an account"로 해주고 색을 grey로 해준다. 아래에는 (selectedForm == 0)?'Sign in':'Sign Up'로 써주고 blue에 bold로 해준다. 그러면 화면이 바뀔때마다 글씨도 바뀌는 것을 알 수 있다.
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), ), ] ), ),
make border from flat button
다른 선을 그어주는 방법이 있지만 여기서는 FlatButton에 shape옵션을 활용해보자. shape에 Border를 주고 top에 BorderSide위젯을 준다. 여기에 color과 width를 주면 흰선이 생기는 것을 알 수 있다. 그런에 자세히 보면 선위에도 흰색이 보이는데 여기서는 Positioned에 heigth를 주면 선까지만 흰색이 되는 것을 알 수 있다.
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), ), ]), ), ),
SignUpForm 레이아웃
기기마다 길이가 달라서 아래가 잘려 보일 수도 있으므로 스크롤이 가능한 ListView를 사용할 것임. 우선 stf로 바꿔준다. 그리고 Form을 사용하기 위해서는 GlobalKey<FormState>를 사용해야 한다. 폼의 상태를 저장하는 위젯이다. GlobalKey<FormState> _formKey = GlobalKey<FormState>(); 이렇게 생성 해준뒤, return에 Form을 생성하고 key에 _formKey, child에 ListView()를 생성해준다. 우선 상단의 로고부터 만들어 준다.
class _SignUpFormState extends State<SignUpForm> { GlobalKey<FormState> _formKey = GlobalKey<FormState>(); //폼의 상태를 저장하는 키 @override Widget build(BuildContext context) { return Form( key: _formKey, child: ListView( children: [ SizedBox(height: common_l_gap,), Image.asset('assets/images/insta_text_logo.png'), TextFormField( ), TextFormField(), TextFormField(), ], ), ); } }
TextFormField 사용해서 email input 만들자
첫번째 TextFormField에 decoration옵션에 InputDecoration을 사용해보자. 우선 누르면 없어지는 hintText에 'Email'이라 해주고 enabledBorder 옵션에는 OutlineInputBorder을 준다. 그 안에 BorderSide에 색을 grey[300]을 준다. 그리고 borderRadius넨 circular로 common_s_gap만큼 준다. common_s_gap을 screen_size에 12로 만들어 준다. 그리고 속의 색을 위해서 filled를 true로 해주고 fillColor을 grey[100]으로 준다. 옆과 위아래의 공간을 위해 Form을 Padding으로 감싸고 common_gap을 준다. 그리고 이 TextFormField에서 사용할 TextEditingController을 state클래스 내의 상단에 만들어 준다. 그리고 dispose도 시켜줄 수 있게 만들어 준다. 안해주면 메모리 leak이 발생한다. 컨트롤러를 사용하게 된다면 dispose를 꼭 사용하자. 그리고 TextFormField에 controller에 _emailController을 넣어주면 된다. 그리고 TextFormField에서 validator를 사용 할 수 있는데 텍스트를 추출해서 submit할때 string을 주는데 이거를 받아와서 우리가 정한 기준에서 메시지를 보여줄 수 있다.
GlobalKey<FormState> _formKey = GlobalKey<FormState>(); //폼의 상태를 저장하는 키 TextEditingController _emailController = TextEditingController(); @override void dispose() { _emailController.dispose(); super.dispose(); } TextFormField(controller: _emailController, decoration: InputDecoration( hintText: 'Email', enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey[300], ), borderRadius: BorderRadius.circular(common_s_gap), ), filled: true, fillColor: Colors.grey[100], ), validator: (text){ if(text.isNotEmpty && text.contains("@")){ return null; }else{ return '정확한 이메일 주소를 입력해주세요!'; } }, ),
password와 confirm password 만들기
우선 이메일 부분의 InputDecoration이 비슷하게 사용되기 때문에 _textInputDecor라는 method로 refactor해준다. 그리고 hintText에 들어갈 부분을 받아서 쓰면 된다. 그 다음 TextFormField를 3개를 만들어 주고 _textInputDecor()에 Email, Password, Confirm Password를 넣어주고 각 부분에 맞게 컨트롤러를 2개 추가 생성해주고 dispose에도 넣어준다. 각각의 controller에 맞게 넣어준다. 그리고 password에 validator에는 inNotEmpty와 text.length > 5 로 해준다. confirm password에는 isNotEmpty와 pw와의 일치성을 따져야 하는데 이때 pw의 컨트롤러를 이용하면 된다. _pwController.text == text로 해주면 된다. 그리고 각 입력창이 너무 위아래가 붙어있으므로 패딩보다 여기서는 SizedBox로 height를 줘보자. common_xs_gap = 10.0;으로 만들어 주고 TextFormField 사이에 각각 넣어주면 된다.
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 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( controller: _pwController, decoration: _textInputDecor('Email'), validator: (text) { if (text.isNotEmpty && text.contains("@")) { return null; } else { return '정확한 이메일 주소를 입력해주세요!'; } }, ), SizedBox(height: common_xs_gap,), TextFormField( controller: _cpwController, decoration: _textInputDecor('Password'), validator: (text) { if (text.isNotEmpty && text.length > 5) { return null; } else { return '제대로 된 비밀번호를 입력해주세요!'; } }, ),SizedBox(height: common_xs_gap,), TextFormField( controller: _emailController, decoration: _textInputDecor('Confirm Password'), validator: (text) { if (text.isNotEmpty && _pwController.text == text) { return null; } else { return '입력한 값이 비밀번호와 일치하지 않습니다.'; } }, ), ], ), ), ); } InputDecoration _textInputDecor(String hint) { return InputDecoration( hintText: hint, enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey[300], ), borderRadius: BorderRadius.circular(common_s_gap), ), filled: true, fillColor: Colors.grey[100], ); } }
입력창 입력값 정확성 여부 확인
우선 버튼을 만들어 보자. 마지막 TextFormField 아래에 FlatButton을 만든다. 색을 blue, 글씨는 Join에 색을 흰색으로 해준다. 그리고 모서리를 둥글게 하기 위해서 shape에 RoundedRectangleBorder를 주고 borderRadius 옵션에 BorderRadius.circular에 common_xxs_gap만큼 주자. onPressed는 필수인데, 눌렀을 때 validator를 확인하게 해주자. onPressed안에 if문에 _formKey.currentState.validate()를 주자. 현재 상태중 validate를 확인해서 true나 false를 반환해준다.
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 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( obscureText: true, cursorColor: Colors.black54, controller: _pwController, 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: _cpwController, decoration: _textInputDecor('Password'), validator: (text) { if (text.isNotEmpty && text.length > 5) { return null; } else { return '제대로 된 비밀번호를 입력해주세요!'; } }, ), SizedBox( height: common_xs_gap, ), TextFormField( cursorColor: Colors.black54, controller: _emailController, decoration: _textInputDecor('Confirm Password'), validator: (text) { if (text.isNotEmpty && _pwController.text == text) { return null; } else { return '입력한 값이 비밀번호와 일치하지 않습니다.'; } }, ), FlatButton( onPressed: () { if (_formKey.currentState.validate()) { //3개다 true면 서버에 저장하거나 값 읽어오거나 하면됨. print('Validation Success!!'); } }, child: Text( 'Join', style: TextStyle(color: Colors.white), ), color: Colors.blue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(common_xxs_gap), ), ), ], ), ), ); } InputDecoration _textInputDecor(String hint) { return InputDecoration( hintText: hint, enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey[300], ), borderRadius: BorderRadius.circular(common_s_gap), ), errorBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.redAccent, ), borderRadius: BorderRadius.circular(common_s_gap), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey[300], ), borderRadius: BorderRadius.circular(common_s_gap), ), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey[300], ), borderRadius: BorderRadius.circular(common_s_gap), ), filled: true, fillColor: Colors.grey[100], ); } }
반응형'개발 > Flutter' 카테고리의 다른 글
Flutter - 코딩마스터하기|카메라 페이지 만들기 (0) 2021.11.03 Flutter - 코딩마스터하기|인증페이지 만들기2 (0) 2021.11.02 Flutter - 코딩마스터하기|프로필스크린만들기2 (0) 2021.10.28 Flutter - 코딩마스터하기|피드스크린 만들기2 (0) 2021.10.26 Flutter - 코딩마스터하기|피드스크린 만들기 (0) 2021.10.25 댓글