관리 메뉴

cococo-coding

[flutter/투두리스트 분석] todo_list.dart 프로젝트 및 코드 분석 본문

[Flutter] Todolist/프로젝트 분석글

[flutter/투두리스트 분석] todo_list.dart 프로젝트 및 코드 분석

_dani 2024. 2. 15. 21:23

우선 todo_list.dart는 lib디렉터리>widgets디렉터리 안에 있는 다트파일이다.


전체 코드

import 'package:flutter/material.dart';
import 'package:flutter_todolist_app/model/todo.dart';
import 'package:flutter_todolist_app/constants/colors.dart';

class ToDoItem extends StatelessWidget {
  final ToDo todo;
  final onToDoChanged;
  final onDeleteItem;

  const ToDoItem({
    Key? key,
    required this.todo,
    required this.onToDoChanged,
    required this.onDeleteItem
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(bottom:20),
      child: ListTile(
        onTap: () {
          //print('Clicked on Todo Item.');
          onToDoChanged(todo);
        },
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
        ),
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
        tileColor: Colors.white,
        leading: Icon(
          todo.isDone?Icons.check_box : Icons.check_box_outline_blank,
          color:toBlue,
        ),
        title: Text(
          todo.todoText!,
          style: TextStyle(
            fontSize: 16,
            color: toBlack,
            decoration: todo.isDone? TextDecoration.lineThrough : null,
          ),
        ),
        trailing: Container(
          padding: EdgeInsets.all(0),
          margin: EdgeInsets.symmetric(vertical: 12),
          height: 35,
          width: 35,
          decoration: BoxDecoration(
            color: toRed,
            borderRadius: BorderRadius.circular(5),
          ),
          child: IconButton(
            color: Colors.white,
            iconSize: 18,
            icon: Icon(Icons.delete),
            onPressed: () {
              //print('Clicked on delete icon');
              onDeleteItem(todo.id);
            },
          ),
        ),
      ),
    );
  }
}

위는 전체코드이고 이제 각 부분 코드를 분석해보겠다.


부분 코드 분석

import 'package:flutter/material.dart';
import 'package:flutter_todolist_app/model/todo.dart';
import 'package:flutter_todolist_app/constants/colors.dart';

기본적으로 material.dart를 임포트해야 material design( MaterialApp()) 을 사용할 수 있다. 

플러터의 기본 패키지이다. 

 

//ToDoItem 클래스 선언
class ToDoItem extends StatelessWidget {
  //멤버변수들 
  //ToDo 클래스도 멤버변수로 받는다.
  final ToDo todo;
  final onToDoChanged;
  final onDeleteItem;

  //생성자 선언
  const ToDoItem({
    Key? key,
    required this.todo,
    required this.onToDoChanged,
    required this.onDeleteItem
  }) : super(key: key);

 

StatelessWidget 속성을 상속받은 ToDoItem 클래스를 생성한다. 

StatelessWidget는 변경가능한 상태가 필요하지 않은 위젯이다. (플러터 API 문서에는 다음과 같이 나와있다. A widget that does not require mutalbe state.)

 

final은 한번 설정한 값을 변경할 수 없도록 하는 속성(불변 상수st)인데, 위에서처럼 final 로 선언된 변수들은 새로운 값으로 설정불가하다. 그러나 객체나 배열에 값을 추가하는 행위는 가능하다는 점에서 const와 차이가 있다. 

 

그리고 이전에 우리는 todo.dart 파일에서 class ToDo를 정의했었다. 

ToDo클래스에서는 id, todoText, isDone 이라는 멤버변수가 있다.

이 클래스를 ToDoItem 클래스의 멤버변수로 받아와서 또 사용한다. 


 ToDoItem 클래스에서는 ToDo클래스, onToDoChanged, onDeleteItem를 멤버변수 선언했다.

 

그리고 const ToDoItem({});으로 생성자를 선언한다. 

여기서  const ToDoItem({ Key? key : super(key: key) 를 설명하자면

ToDoItem생성자의 인자인 Key는 : 상속해준 클래스의 key를 value로 갖는다. 이때 key는 null일 수 있다. 

 

flutter의 위젯은 생성자에서 key매개변수를 받을 수 있는데

위젯이 위젯트리에서 위치를 변경해도 key정보는 유지되므로 상태를 보존할 때 key를 사용한다. 

 

생성자내부를 보면 required this라는 문구를 볼 수 있다. 

required는 dart 2.12부터 @required주석을 대체하는 용도로 쓰이고, 다른사람에게 특정값을 전달하는것이 필수인 경우에 해당필드에 꼭 required 키워드를 써줘야한다. 


@override
  Widget build(BuildContext context) {
    //container위젯을 리턴함
    return Container(
      margin: EdgeInsets.only(bottom:20),
      //container위젯의 하위속성
      child: ListTile(
        onTap: () {
          //print('Clicked on Todo Item.');
          onToDoChanged(todo);
        },
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
        ),
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
        tileColor: Colors.white,
        leading: Icon(
          todo.isDone?Icons.check_box : Icons.check_box_outline_blank,
          color:toBlue,
        ),
        title: Text(
          todo.todoText!,
          style: TextStyle(
            fontSize: 16,
            color: toBlack,
            decoration: todo.isDone? TextDecoration.lineThrough : null,
          ),
        ),
        trailing: Container(
          padding: EdgeInsets.all(0),
          margin: EdgeInsets.symmetric(vertical: 12),
          height: 35,
          width: 35,
          decoration: BoxDecoration(
            color: toRed,
            borderRadius: BorderRadius.circular(5),
          ),
          child: IconButton(
            color: Colors.white,
            iconSize: 18,
            icon: Icon(Icons.delete),
            onPressed: () {
              //print('Clicked on delete icon');
              onDeleteItem(todo.id);
            },
          ),
        ),
      ),
    );
  }
}

 

@override는 상속받은 클래스의 메소드를 재정의한다. 

여기서는 ToDoItem클래스가 StatelessWidget의 Widget build()를 재정의한다. 

 

Widget build() 함수

대부분의 위젯은 build()함수를 갖는다. 여기서는 Container를 리턴하고있으며 이 Container가 화면에 나오게 된다. 

Container 클래스

위젯 중의 하나로 하위요소 위젯과 구성,장식, 위치를 지정할 수 있다. 

return Container(
	  //margin 속성: empty space to surround the decoration and child(장식과 하위속성을 둘러싸는 빈공간)
      //EdgeInsets 클래스: 네가지 기본방향에 대해 변경할 수 없는 오프셋 집합
      //only(bottom:20) : 아래 여백 들여쓰기 20픽셀 
      margin: EdgeInsets.only(bottom:20), 
      //child: 트리에서 위젯 아래에 있는 위젯 
      //child위젯은 하나의 아이만 가질 수 있으며, 여러 아이(하위항목)가 필요한 경우는 row column stak 을 이용한다. 
      //ListTile 클래스: 텍스트와 아이콘을 포함하는 단일고정높이 행
      child: ListTile(

 

Container 클래스는 아래쪽으로 20픽셀의 여백을 갖고, 하위속성으로 ListTile을 갖는다. 

child: ListTile(
  		//onTap 속성: 기본버튼을 탭한 것을 인식함
        onTap: () {
          //print('Clicked on Todo Item.');
          onToDoChanged(todo);
        },
        //shape: 타일의 속성(맞춤테두리나 장식속성)을 정의함
        //RoundedRectangleBorder: 둥근 모서리의 직사각형 테두리 
        //borderRadius 클래스: boxshape이 rectangle일 때 boxdecoration에서 사용되는 클래스
        //직사각형의 각 모서리에 대한 반경을 뜻하고 불변이다.
        //BorderRadius.circular: 모든반경이 Radius.circular(radius)인 테두리반경을 생성함
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
        ),
        //contentPadding: tile's internal padding 
        //if null, EdgeInsets.symmetric(horizontal: 16.0) is used.
        //여기서는 horizontal 수평은 20픽셀
        //vertical 수직은 5픽셀
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
        //tileColor: false로 선택되었을 때의 ListTile의 배경색을 지정함
        tileColor: Colors.white,

 

ListTile()이라는 하위속성은

클릭했을때 onTochanged(todo); 를 실행하고 onTap : () {onToDoChanged(todo); 

모양은 테두리가 20픽셀 원형으로

내부패딩은 수평으로는 20픽셀, 수직으로는 5픽셀정도로 설정하고

타일컬러는 화이트로 설정한다. 

leading: Icon(
          todo.isDone?Icons.check_box : Icons.check_box_outline_blank,
          color:toBlue,
),

 

leading은 타이틀앞에 표시할 위젯속성이며 보통 아이콘이나 circleAvatar를 나타낸다.

여기서는 icon을 나타냈다. 

만약 todo의 isDone 속성이 true라면 아이콘에 check_box로 체크표시가 되도록, false라면 check_box_outline_blank 로 체크상자안에 체크가 없도록 설정한다. 

https://api.flutter.dev/flutter/material/Icons/check_box_outline_blank-constant.html

 

 title: Text(
          todo.todoText!,
          style: TextStyle(
            fontSize: 16,
            color: toBlack,
            decoration: todo.isDone? TextDecoration.lineThrough : null,
     ),
 ),

title 속성에는 Text위젯을 넣고, 전에 만들었던 todo클래스의 todoText를 넣어준다.

텍스트스타일을 style: TextStyle 로 지정해준다. 

사이즈는 16, 컬러는 블랙, decoration 속성으로 해당 투두가 끝났다면 밑줄을 그어준다. 

 

https://api.flutter.dev/flutter/dart-ui/TextDecoration/lineThrough-constant.html

 


두 번째 child속성은 trailing : Contaner이다.

 

trailing 속성

title 뒤에 display되는 위젯, 보통 아이콘 위젯을 많이 쓴다. 

여기에서는 컨테이너 위젯을 사용했다. 

trailing: Container(
          padding: EdgeInsets.all(0),
          margin: EdgeInsets.symmetric(vertical: 12),
          height: 35,
          width: 35,
          decoration: BoxDecoration(
            color: toRed,
            borderRadius: BorderRadius.circular(5),
          ),
          child: IconButton(
            color: Colors.white,
            iconSize: 18,
            icon: Icon(Icons.delete),
            onPressed: () {
              //print('Clicked on delete icon');
              onDeleteItem(todo.id);
            },
          ),
        ),
      ),
    );

 

위 코드를 다시 분석해보겠다.

 

trailing: Container(
          //padding 클래스는 주어진 패딩만큼 제약조건을 만들어 자식을 더 작은 크기로 레이아웃함.
          //즉 자식 주위에 빈 공간을 생성하는것 
          //여기에서는 자식주위의 빈 공간을 설정하지 않았음
          padding: EdgeInsets.all(0),
          //margin 속성은 decoration과 child를 둘러싸는 빈공간
          margin: EdgeInsets.symmetric(vertical: 12),
          height: 35,
          width: 35,
          decoration: BoxDecoration(
            color: toRed,
            borderRadius: BorderRadius.circular(5),
),

우선 trailing 으로 컨테이너 위젯을 사용했고

해당 컨테이너 위젯의 패딩은 사방 모두 0으로 지정했으며

margin은 수직으로만 12로 설정하고

컨테이너의 높이와 넓이는 모두 35로 지정했다.

 

이 컨테이너의 데코는 박스데코레이션 속성을 사용했고

컬러는 레드색상,

박스테두리는 모서리가 5만큼 둥근 테두리로 지정했다. 

 

child 속성으로 IconButton을 받는다. 

IconButton 클래스

material 디자인의 아이콘 버튼이며 터치에 반응한다.

onPressed가 null이면 버튼이 비활성화되고 터치에 반응하지 않는다. 

child: IconButton(
            color: Colors.white,
            iconSize: 18,
            icon: Icon(Icons.delete),
            onPressed: () {
              //print('Clicked on delete icon');
              onDeleteItem(todo.id);
       },
 ),

하위속성으로 아이콘버튼을 선언하고

버튼의 컬러는 화이트, 사이즈는 18, 아이콘은 delete아이콘으로 사용했다.

만약 버튼이 눌리면 todo클래스의 id를 지우는 onDeleteItem 을 실행하여 해당 투두가 삭제되도록 한다. 


 

출처

1. 전체적으로 다 참고

https://velog.io/@1984/Flutter-%EA%B8%B0%EB%B3%B8-%EC%98%88%EC%A0%9C-%EC%84%A4%EB%AA%85-%EB%B0%8F-%ED%95%B4%EC%84%A4

 

Flutter : 기본 예제 설명 및 해설

일부러 주석까지 다 가지고 왔다. (사실 지금까지 잘 읽어보지도, 알려고 하지도 않았다.)기본 예제는 처음에 실행해보고, 끝이었는데 조금 더 깊이 파고들고 알고 싶어져서 보며 공부하려 이 글

velog.io

2. final

https://zoiworld.tistory.com/703

 

[Flutter] final & const

final & const 공통점 한 번 설정한 값을 변경할 수 없다. 다른 값으로 변경하려고 시도하면 컴파일 오류가 발생한다. final & const 차이점 const의 경우, 컴파일 타임에서 상수를 정의할 수 있다. 즉, cons

zoiworld.tistory.com

https://news.dartlang.org/2012/06/const-static-final-oh-my.html

 

Const, Static, Final, Oh my!

Posted by Seth Ladd (This is an "oldie but a goodie" misc@dartlang.org post  originally written by Bob Nystrom. It is being posted here as...

news.dartlang.org

3. required 키워드 

https://stackoverflow.com/questions/54181838/flutter-required-keyword

 

Flutter required keyword

I don't really understand how required works. For example I've seen this code: class Test{ final String x; Test({ required this.x }); factory Test.initial(){ return Test(x: "&...

stackoverflow.com