본문 바로가기

앱 개발/Flutter

Flutter 앱 개발 (4) : Flutter 추가 설정 및 To-Do List Layout 구성

Flutter 추가 설정

추가 설정 관련 내용은 nomadcoders의 flutter 강의를 참고하여 작성하였다.

#3.3 VSCode Settings (06:18) – 노마드 코더 Nomad Coders

 

All Courses – 노마드 코더 Nomad Coders

초급부터 고급까지! 니꼬쌤과 함께 풀스택으로 성장하세요!

nomadcoders.co

좌측 하단 설정 버튼을 눌러 command palette에 들어간다.

그 후, open user settings(JSON)을 클릭해 JSON 파일을 연다.

그 후, 원하는 내용을 JSON 파일에 추가한다.

1.2번은 굉장히 유용하니 추가하도록 하자.

 

1.

"editor.codeActionsOnSave": {
        "source.fixAll": true
    },

이 내용을 추가하면, flutter에서 const를 적용해야 하는 곳에 자동으로 const를 적용시켜 준다.

 

2.

"dart.previewFlutterUiGuides": true,

이 내용을 추가하면, 요소들의 부모가 어떤 것인지 알려주는 가이드라인이 생성된다.

확인을 위해서는 vscode를 재시작해야 한다.

 

3.

"dart.openDevTools": "flutter"

Flutter 앱의 UI 레이아웃 및 상태를 검사해 주거나, Flutter 앱의 UI jank 성능 문제를 진단해 준다.

디버깅, 일반 로그 및 진단 정보도 지원한다.

 

4.

"dart.debugExternalPackageLibraries": true,

external pub package library(flutter 포함)를 디버깅하는 동안 라이브러리로 이동할 수 있도록 debuggable로 표시할지 여부.

 

5.

"dart.debugSdkLibraries": false,

Dart SDK 라이브러리(dart:*)를 debuggable로 표시하여 디버깅 중에 라이브러리에 진입할 수 있는지 여부.

 

6.

"emmet.excludeLanguages" :["markdown", "jsx"],

Emmet 확장을 보고 싶지 않은 언어가 있을 경우 [] 안에 입력해 주면 된다.

*Emmet : 코드 에디터 툴을 사용하여 코드를 작성할 때 효율적으로 시간을 단축시킬 수 있도록 도와주는 확장 기능. vscode, Atom 등 여러 에디터 툴에서 대부분 지원함.

 

 

 

 

Layout 구성하기

To-Do LIst의 레이아웃을 구성해 보자.

 

새로운 프로젝트를 만들었을 때 기본적으로 제공하는 Layout이다.

최종적으로 구현하려고 하는 레이아웃은 다음과 같다.

 

진행도를 나타내는 회색 progress bar 아래에 할 일 목록이 나타나게 할 예정이다.

 

Scaffold

기본적인 material design의 시각적인 레이아웃 구조를 실행한다.

Scaffold Widget을 통해 간단한 AppBar와 기본적인 뼈대를 만들어 보자.

 

AppBar의 제목을 중앙정렬 해주기 위해서 centerTitle 옵션을 true로 지정해 주었다.

 

Body

Container로 공간을 분할하였다. 분할한 곳에 각각 날짜, 할 일을 기록할 수 있는 input과 할 일 진행도, 할 일 목록을 나타낼 예정이다.

 

 

DateTime

날짜를 화면에 보여주기 위해 DateTime을 사용해 보자.

우선 원하는 날짜 표현 format을 만들어주기 위해 intl을 추가해 주자.

 

intl | Dart Package (pub.dev)

 

intl | Dart Package

Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.

pub.dev

그 후, _MyHomePageState 클래스에 getToday 메서드를 작성해 보자.

 

String getToday() {
    DateTime now = DateTime.now();
    String strToday;
    DateFormat formatter = DateFormat('yyyy-MM-dd');
    strToday = formatter.format(now);
    return strToday;
  }

이 함수를 통해 현재 날짜를 String 형태로 가져올 수 있다.

이를 이용해 날짜를 화면에 띄워보자.

 

Text Widget을 통해 화면에 날짜를 나타내었다.

 

TextController

할 일을 입력할 수 있는 textField를 만들어보자.

우선, _MyHomePageState Class에 textController를 선언해 준다.

final _textController = TextEditingController();

그 후, TextField를 만들어주자.

TextField를 만들었다. controller 옵션으로는 우리가 선언한 textController를 줬다.

ElevatedButton으로 눌렀을 때 할 일을 추가할 수 있는 add 버튼도 만들어 주었다.

Row Widget을 통해 textField와 button이 한 줄에 있도록 만들었다.

그 후, padding widget으로 간격을 주어 보기 좋게 만들었다.

 

 

할 일 진행도

할 일 진행도를 보여줄 수 있는 progress bar를 만들어보자.

progress bar를 나타내기 위해 percent_indicator package를 사용할 것이다.

percent_indicator | Flutter Package (pub.dev)

 

percent_indicator | Flutter Package

Library that allows you to display progress widgets based on percentage, can be Circular or Linear, you can also customize it to your needs.

pub.dev

textField를 감싸고 있는 Padding widget 아래에 다음 코드를 추가하자.

LinearPercentIndicator를 통해 진척도를 나타낼 수 있는 막대를 만들었다.

넓이는 사용자 기기에 맞게 사이즈를 조절할 수 있도록 설정해 주었다.

사용자가 보기 좋게 하기 위해 padding을 적용하였다.

 

할 일 목록

이제 사용자가 추가하면 나타날 할 일 목록을 만들 차례다.

초록색 container가 있던 Flexable 자리에 다음 코드를 넣어주자.

 

TextButton을 이용해할 일 목록을 구성할 할 일 요소를 만들어주었다. 이 요소를 누르면 할 일 완료 처리를 할 예정이다.

수정과 삭제 기능을 담당할 TextButton을 만들어 주었다.

이 요소들이 한 줄에 있도록 Row Widget을 통해 묶어주었다.

 

완성하고 나니 레이아웃이 마음에 들지 않는다.

 

레이아웃 및 간단한 디자인 조정

1. 현재 요소들이 화면 중앙에서 시작되고 있다.

body옵션에 들어간 Center의 child인 Column의 옵션 mainAxisAlignment을 수정하면 해결할 수 있을 것 같다.

현재 이 mainAxisAlignment의 옵션값이 center라서 화면 가운데서부터 위젯이 그려지고 있다.

이 옵션을 제거하도록 하자.

 

코드 수정 후(일부)

 해결 완료.

 

2. 할 일 요소를 눌렀을 때, 모션이 타원형으로 나타난다. 터치 시 모션을 사각형으로 만들어보자.

할 일 요소를 담당하는 TextButton에 style 옵션을 다음과 같이 추가하면 된다.

해결 완료.

 

3. 불필요한 Flexible widget이 날짜 부분에 사용되었다. 이를 제거하자.

해결 완료.

 

이렇게 해서 대략적인 레이아웃 구성을 마쳤다. 추가적인 레이아웃 구성은 개발을 진행하면서 수정 또는 보완하도록 하자. 

 

전체 코드

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TO-DO List',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'TO-DO List'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _textController = TextEditingController();

  String getToday() {
    DateTime now = DateTime.now();
    String strToday;
    DateFormat formatter = DateFormat('yyyy-MM-dd');
    strToday = formatter.format(now);
    return strToday;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Text(getToday()),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                children: [
                  Flexible(
                    child: TextField(
                      controller: _textController,
                    ),
                  ),
                  ElevatedButton(
                    onPressed: () {},
                    child: const Text("Add"),
                  )
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 20.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  LinearPercentIndicator(
                    width: MediaQuery.of(context).size.width - 50,
                    lineHeight: 14.0,
                    percent: 0.3,
                  ),
                ],
              ),
            ),
            Row(
              children: [
                Flexible(
                  child: TextButton(
                    style: TextButton.styleFrom(
                      shape: const RoundedRectangleBorder(
                        borderRadius: BorderRadius.all(Radius.zero),
                      ),
                    ),
                    onPressed: () {},
                    child: const Padding(
                      padding: EdgeInsets.all(8.0),
                      child: Row(
                        children: [
                          Icon(Icons.check_box_outline_blank_rounded),
                          Text("todo1"),
                        ],
                      ),
                    ),
                  ),
                ),
                TextButton(
                  onPressed: () {},
                  child: const Text("수정"),
                ),
                TextButton(
                  onPressed: () {},
                  child: const Text("삭제"),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}