본문 바로가기

Flutter/App Making Learn

로그인화면 다시만들기

완성 화면

 

import 'dart:ffi';
import 'package:device_preview/device_preview.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          centerTitle: true,
          backgroundColor: Colors.black,
          title: Text(
            "HelloFlutter",
            style: TextStyle(
              fontSize: 30,
              color: Colors.amber,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(
              16.0,
            ),
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(32.0),
                  child: Image.network(
                    "https://i.ibb.co/nngK6j3/startup.png",
                    width: 200,
                  ),
                ),
                TextField(
                  enableSuggestions: true,
                  autocorrect: true,
                  decoration: InputDecoration(
                    labelText: ("e-mail"),
                    hintText: ("example@hint.com"),
                    alignLabelWithHint: true,
                  ),
                ),
                TextField(
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: ("password"),
                    hintText:
                        ("Contains capital letters and numbers as required."),
                    alignLabelWithHint: true,
                  ),
                ),
                Container(
                  width: double.infinity,
                  margin: EdgeInsets.symmetric(
                    vertical: 20,
                  ),
                  child: ElevatedButton(
                    onPressed: () {
                      print("login");
                    },
                    style: ButtonStyle(
                      backgroundColor: MaterialStatePropertyAll(
                        Colors.black,
                      ),
                    ),
                    child: Text(
                      "login",
                      style: TextStyle(
                        color: Colors.amber,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

 

예제랑은 최대한 다르게 하고싶었습니다. 그래야 내가 이것저것 바꿔가면서 적응해야하니까
일단 우선적으로

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    debugShowCheckedModeBanner: false,
      home: Scaffold(

위는 매터리얼(Android style widget)을 사용한 앱이란 걸 알수있었습니다.
메인함수인 void main()에선 현재 어떠한 기능을 할수 없는 앱이란걸 알 수 있고, 정말 어떠한 기능도 하지않는, 정말 보여지는 화면만 만들었다. 앱을 만들기 위한 가장 기본적인 뼈대 Scaffold를 설치하고 앱을 만들 준비를 하도록 합니다.

 

첫번째
appBar

화면에 보이는 가장 위의 영역을 차지하며 appbar없이 Body만 있다면 말 그대로 AppBar의 영역까지 body가 차지합니다.

appBar

        appBar: AppBar(
          centerTitle: true,
          backgroundColor: Colors.black,
          title: Text(
            "HelloFlutter",
            style: TextStyle(
              fontSize: 30,
              color: Colors.amber,
              fontWeight: FontWeight.bold,
            ), // TextStyle
          ), // Text
        ), // AppBar

매터리얼 버전이 올라오면서 뒷배경화면에 기본으로 주어지던 파란색 배경이 없어지고 아무것도 없는 투명 배경이 되었다.
그럼으로 "backgroundColor"와 "Text(color)"는 내 마음대로 변경해주었습니다.
이 appBar에서도 왼쪽에는 "leading : IconButton"이라는 변수로, 오른쪽에는 "Iconbutton"이라는 함수를 사용 하여 아이콘 및 액션을 넣을 수 있습니다.

 

두번째
body

앱의 가장 큰영역을 차지함과 동시에 사용자가 가장 많이 활동하는 공간입니다.

body

        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(
              16.0,
            ),
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(32.0),
                  child: Image.network(
                    "https://i.ibb.co/nngK6j3/startup.png",
                    width: 200,
                  ),
                ),
                TextField(
                  enableSuggestions: true,
                  autocorrect: true,
                  decoration: InputDecoration(
                    labelText: ("e-mail"),
                    hintText: ("example@hint.com"),
                    alignLabelWithHint: true,
                  ),
                ),
                TextField(
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: ("password"),
                    hintText:
                        ("Contains capital letters and numbers as required."),
                    alignLabelWithHint: true,
                  ),
                ),
                Container(
                  width: double.infinity,
                  margin: EdgeInsets.symmetric(
                    vertical: 20,
                  ),
                  child: ElevatedButton(
                    onPressed: () {
                      print("login");
                    },
                    style: ButtonStyle(
                      backgroundColor: MaterialStatePropertyAll(
                        Colors.black,
                      ),
                    ),
                    child: Text(
                      "login",
                      style: TextStyle(
                        color: Colors.amber,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),

왜 맨 아래 여백까지 포함하지 않았는가 하면, 포함하지 않은 저 아래의 영역은 "bottomNavigtionBar"라는 영역입니다.
물론 현재 화면에서는 "BottomNavigtionBar"를 사용하지 않았으니 "body"의 영역이 맞습니다.

 

        body: SingleChildScrollView(

제일 윗줄 부터 "SingleChildScrollView"란 함수는 "body"의 영역에 스크롤할수 있는 기능을 부여해주는 것입니다.
그렇게 해야만 "Textfield"를 사용하려 하였을때 아래에서 올라온 키보드가 "Textfield"를 가려버리는 상황을 만들지 않을 수 있습니다.

 

          child: Padding(
            padding: const EdgeInsets.all(
              16.0,
            ),
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(32.0),
                  child: Image.network(
                    "https://i.ibb.co/nngK6j3/startup.png",
                    width: 200,
                  ),
                ),

그리고 보이는 "child"는 "body"영역의 하위 폴더라고 생각하면 될것 같습니다.

"Padding"은 안쪽 여백 (반댓말 "Margin" 바깥쪽 여백)을 주어서
위젯과 위젯사이의 간격을 넓혀 사용자의 사용감 상향을 바랄 수 있습니다.

"const(값이 변하지  않는) EdgeInsets.all(모든 구간의 변)" 을 32값을 주어 넉넉함을 줄 수있습니다.
또한 "EdgeInsets.all이 아닌 "EdgeInsets.symetric(vertical : x)" 또는"EdgeInsets.symetric(horizontal : x)"를 사용하여 세로에만, 가로에만에도 값을 줄 수 있으며 "EdgeInests.only(top : x)" 처럼 한 변에만에도 패딩을 줄 수 있습니다.

아래에 "Image.network" 를 통해 웹에서 이미지를 가져 왔고 물론 프로젝트 내부에 저장된 사진도 "Assets"라는 폴더와 "pubspec.yaml" 파일을 통해 사용할수 있습니다.
이미지에 적혀진 문자열을 보면 끝에 .jpg 또는 .png 같은 웹문서들은 언제든 당겨와서 사용할 수 있습니다.

"width"를 주어 원본 만큼의 크기를 한눈에 보기 좋게 압축시켜준다. 단 width가 아닌 Boxfit이라는 함수로 원본 비율을 유지하며, 또는 유지하지 않으며를 설정할 수 있습니다.

 

세번째
TextField

Text를 써넣을 수 있는 공간입니다.

TextField

                TextField(
                  enableSuggestions: true,
                  autocorrect: true,
                  decoration: InputDecoration(
                    labelText: ("e-mail"),
                    hintText: ("example@hint.com"),
                    alignLabelWithHint: true,
                  ),
                ),
                TextField(
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: ("password"),
                    hintText:
                        ("Contains capital letters and numbers as required."),
                    alignLabelWithHint: true,
                  ),
                ),

"enableSuggestion : bool" 은 자동제안 기능. "autocorrect : bool" 은 자동완성기능.
궁금하여 직접 입력하여 넣어봤지만 아직은 작동하지 않았습니다. 아마 파이어베이스와 연동하면 되지 않을까... 싶은 저의 생각입니다...

아래에 "decoration: InputDecoration()"으로 해당 "TextField"에 라벨 텍스트, 힌트 텍스트를 넣어 사용할 수 있습니다.

labelText, hintText

그리고 "alignLabelWithHint : bool"은 "true"일때 TextField 가운데로 정렬이 되고 "false"일때는 기존 라벨의 위치의 있게 됩니다.

alignLabelWithHint

엄청 미묘한 차이인것 같다...

그리고 아래에 똑같은 "TextField" 는 password는 입력하였을때 문자열이 그대로 노출되면 보안에 위험이 있기 때문에
"obscureText : bool" 은 true일때 문자열이 가려지게 되고 false일때 문자열이 노출이 됩니다. 

obscureText

 

네번째
Container

눈에 보이지 않는 상자를 둡니다.

Container

                Container(
                  width: double.infinity,
                  margin: EdgeInsets.symmetric(
                    vertical: 20,
                  ),
                  child: ElevatedButton(
                    onPressed: () {
                      print("login");
                    },
                    style: ButtonStyle(
                      backgroundColor: MaterialStatePropertyAll(
                        Colors.black,
                      ),
                    ),
                    child: Text(
                      "login",
                      style: TextStyle(
                        color: Colors.amber,
                      ),
                    ),
                  ),
                ),

컨테이너의 폭은 "double(실수).infinity(무한)"으로 주었고, 여기서의 무한은 정말 무한대로 넓어지는게 아닌 "body에 걸려있는 container"의 크기 까지만 커지게 됩니다.

그리고 여기서 말한 "Padding"의 반대 개념인 "margin"으로 위아래 20정도의 간격을 주어 "Textfield"와 간격을 주었고.
"ElevatedButton()"으로 올라와있는듯한 버튼을 만들어 주었다. 그 외에도 버튼의 종류는 "outlinedButton", "TextButton", "IconButton" 등으로 다양하게 사용할 수 있습니다.

버튼 역시 각자의 스타일을 지정해 줄 수 있으며 텍스트도 색깔을 지정하여서 변경할 수 있습니다.