방명록
- [Flutter] "모두가 할 수 있는 플러터 UI 입문" - 로그인 앱 만들기2022년 02월 03일 18시 31분 48초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
"최주호, 정호준, & 정동진. (2021). 모두가 할 수 있는 플러터 UI 입문. 앤써북"으로 플러터 공부를 하고 있다. 이번 챕터에서는 size.dart 파일에 값을 지정하여 활용, 외부 라이브러리 SvgPicture 설치 및 사용, Theme 적용한 TextButton, Navigator로 화면 이동, 그리고 ListView와 TextFormField 위젯을 중요하게 다뤘다.
책에서는 ListView 위젯을 사용해 화면 overflow 문제를 해결하였는데, 아래 코드에서는 SingleChildScrollView 위젯을 적용하였다. 그리고 배경 터치 시 키보드를 사라지게 하는 기능을 추가하였다. 기존에는 키보드 숨기기 버튼으로만 사라지게 할 수 있었다. GestureDetector 위젯을 사용하였는데, "FocusScope.of(context).unfocus();"를 적용하니 [그림 1]과 같이 키보드가 깜박이는 문제가 있었다. 구글링 끝에 "FocusScope.of(context).requestFocus(new FocusNode());"를 적용하였으며 [그림 2]와 같이 키보드 깜박임 문제가 해결되었다. [그림 3]은 완성된 로그인 앱이다.
// main.dart import 'package:flutter/material.dart'; import 'package:flutter_login/pages/home_page.dart'; import 'package:flutter_login/pages/login_page.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( textButtonTheme: TextButtonThemeData( // TextButton 재사용을 위해 components 패키지 생성하여 만들수 있지만, // 모든 TextButton 디자인이 동일하다면 테마를 지정하여 쉽게 재사용할 수 있다. style: TextButton.styleFrom( backgroundColor: Colors.black, primary: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), minimumSize: Size(400, 60), ), ), ), initialRoute: "/login", // LoginPage가 최초 실행된다. routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage(), }, ); } }
// size.dart // Expanded, Spacer 위젯은 화면의 남은 공간만큼 확장한다. ListView는 스크롤 있는 위젯으로 높이가 무한하기 때문에 Expanded, Spacer 사용불가하다. // SizeBox 이용하기 위해 size.dart 만들어 값을 정해둔다. const double small_gap = 5.0; const double medium_gap = 10.0; const double large_gap = 20.0; const double xlarge_gap = 100.0;
// pages/login_page.dart import 'package:flutter/material.dart'; import 'package:flutter_login/components/custom_form.dart'; import 'package:flutter_login/components/logo.dart'; import 'package:flutter_login/size.dart'; class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( // body: Container( // color: Colors.yellow, // ), body: Builder(builder: (context) { // GestureDetector, FocusScope 이용해 배경을 터치하면 키패드가 사라지게 만든다. return GestureDetector( onTap: () { // FocusScope.of(context).unfocus(); // 코딩셰프 조금 매운맛 강좌 참조함 FocusScope.of(context).requestFocus( new FocusNode()); // unfocus 적용시 TextFormField 터치시 키패드가 순식간에 사라지는 문제있어 FousNode 적용하여 해결함 }, child: Padding( padding: const EdgeInsets.all(16.0), // ListView로 전체화면 구성 이유(TextFormField 위젯 사용한다면 전체화면에 스크롤 다는 것이 좋다) // 1) 세로 방향이기 때문에 Column 뿐만 아니라 ListView 사용 가능 // 2) TextFormField를 터치하면 키보드가 올라옴. 이때 화면에 그림을 그릴 수 없는 inset 영역이 생기는데 이때 스크롤이 없으면 overflow 오류 발생 child: SingleChildScrollView( // SingleChildScrollView로 overflow 문제 해결 가능하다(코딩셰프 조금 매운맛 강좌 참조함). // child: ListView( child: Column( children: <Widget>[ SizedBox(height: xlarge_gap), Logo("Login"), SizedBox(height: large_gap), CustomForm(), ], ), ), ), ); // ); }), ); } }
// pages/home_page.dart import 'package:flutter/material.dart'; import 'package:flutter_login/components/logo.dart'; // 파일명에 대문자를 사용하지 않는 것이 flutter 규칙이다. 스네이크 표기법을 사용한다. class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( // body: Container( // color: Colors.red, body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ SizedBox(height: 200), Logo("Care Soft"), SizedBox(height: 50), TextButton( onPressed: () { Navigator.pop( context); // 스택의 가장 위에 쌓인 HomePage 위젯을 pop하여 LoginPage가 화면에 보여지게 된다. }, child: Text("Get Started"), ), ], ), ), ); } }
// components/custom_form.dart import 'package:flutter/material.dart'; import 'package:flutter_login/components/custom_text_form_field.dart'; import 'package:flutter_login/size.dart'; class CustomForm extends StatelessWidget { final _formKey = GlobalKey<FormState>(); // 글로벌 key @override Widget build(BuildContext context) { // Form 위젯은 데이터 전송 위해 여러 양식의 위젯을 그룹화하는 컨테이너이다. return Form( key: _formKey, // 글로벌 key를 Form 태그에 연결하여 Form 상태를 관리한다. child: Column( children: [ CustomTextFormField("Email"), SizedBox(height: medium_gap), CustomTextFormField("Password"), SizedBox(height: large_gap), TextButton( onPressed: () { // 유효성 검사, TextFormField가 비었는지 확인한다. 비었으면 false, 입력 값이 있으면 true 이다. if (_formKey.currentState!.validate()) { Navigator.pushNamed(context, "/home"); } }, child: Text("Login"), ), ], ), ); } }
// components/custom_text_form_field.dart import 'package:flutter/material.dart'; import 'package:flutter_login/size.dart'; class CustomTextFormField extends StatelessWidget { final String text; const CustomTextFormField(this.text); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text(text), SizedBox(height: small_gap), // TextFormField는 TextField와 유사하지만, validator 속성 활용해 유효성 검사를 가능하게 한다. TextFormField( // !는 null 이 절대 아님을 컴파일러에게 알려준다. validator: (value) => value!.isEmpty ? "Please enter some text" : null, // 비밀번호 입력 양식이면 * 처리한다. obscureText: text == "Password" ? true : false, decoration: InputDecoration( hintText: "Enter $text", enabledBorder: OutlineInputBorder( // 기본 디자인 borderRadius: BorderRadius.circular(20), ), focusedBorder: OutlineInputBorder( // 손가락 터치시 디자인 borderRadius: BorderRadius.circular(20), ), errorBorder: OutlineInputBorder( // 에러 발생시 디자인 borderRadius: BorderRadius.circular(20), ), focusedErrorBorder: OutlineInputBorder( // 에러 발생 후 손가락 터치시 디자인 borderRadius: BorderRadius.circular(20), ), ), ), ], ); } }
// components/logo.dart import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class Logo extends StatelessWidget { final String title; const Logo(this.title); @override Widget build(BuildContext context) { return Column( children: [ SvgPicture.asset( // SvgPicture 외부 라이브러리 이므로 pub.dev 사이트 통해 flutter_svg 검색 null-safety 적용된 라이브러리 설치해야 한다(pubspec.yaml의 라이브러리를 추가해야 dependencies에 추가해야 함). "assets/logo_eco.svg", height: 70, width: 70, ), Text( title, style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold), ), ], ); } }
728x90반응형'언어·프레임워크 > Flutter' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글