본문 바로가기
스파르타/Flutter

[Flutter] 클론 코딩(당근) - ( week 2 )

by bakcoding_sparta 2023. 4. 15.

당근마켓 애플리케이션을 따라 만들어 본다.

 

HomePage

StatelessWidget을 상속받은 HomePage 클래스를 선언하고 이 클래스 내부에서 속성들을 통해 화면을 그린다.

class HomePage extends StatelessWidget {
  const HomePage({super.key}); // 생성자, 클래스 객체를 만들때 처음 호출되는 함수(초기화 역할)

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text("home page")),
    );
  }
}

 

화면에 띄우기 위해서는 HomePage 클래스를 메인함수에서 호출해주어야 한다.

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(), // home이라는 이름지정 매개변수(named parameter)
                        // 첫 번째 페이지 위젯을 만들어 전달하는 역할
    );
  }
}

Stateless나 Stateful 위젯을 상속받아서 사용할 때 내부에서 리턴하는 객체는 반드시 MaterialApp으로 한번 래핑 해주어야 에러가 발생하지 않는다.

 

 

화면이 정상적으로 출력되는 것을 확인했으니 본격적으로 위젯들을 다루어본다.

 

 

우선 앱화면을 구조는 다음으로 정리할 수 있다.

 

AppBar부터 Bottom까지 순서대로 만들어본다.

 

1. Appbar

leading

텍스트와 아이콘을 나열되어 있기 때문에 우선 Row 안에서 위젯들이 선언되어야 한다.

leading 속성에 위젯을 만들어주면 해당 영역에 위젯들이 생성된다.

        leading: Row(
          children: [
            SizedBox(width: 16), // 왼쪽에 여백 주기위함
            Text(
              '중앙동',
              style: TextStyle(
                color: Colors.black,
                fontWeight: FontWeight.bold,
                fontSize: 20,
              ),
            ),
            Icon(
              Icons.keyboard_arrow_down_rounded,
              color: Colors.black,
            ),
          ],
        ),
        leadingWidth: 100, // leading의 기본 크기로 인해 overflow가 발생하지 않도록 너비 지정

SizeBox는 빈 공간으로 Leading 왼편에 공간을 주기 위해 추가한다.

Text에 필요한 문자를 넣고 스타일을 지정해 준다. 

Icon은 API 문서에서 필요한 디자인을 골라서 해당 이름으로 사용해 준다.

 

leadingWidth는 leading의 기본 크기가 한다면 내부의 위젯들이 leading의 크기보다 작아서 overflow가 발생하지 않게 하기 위해서 지정해 준다. 그래도 overflow가 발생한다면 이 값을 더 늘려준다.

 

actions

아이콘들이 추가되어 있고 버튼 기능을 가지고 있어야 한다.

actions 속성의 경우 내부적으로 Row 위젯으로 구성이 되기 때문에 별도로 Row위젯으로 래핑을 해주지 않아도 수평방향으로 정렬이 된다. 이 버튼 목록들은 AppBar 영역 내에서 최대한 공간을 차지하도록 만들어진다.

        actions: [
          IconButton(
            onPressed: () {},
            icon: Icon(CupertinoIcons.search, color: Colors.black),
          ),
          IconButton(
            onPressed: () {},
            icon: Icon(Icons.menu_rounded, color: Colors.black),
          ),
          IconButton(
            onPressed: () {},
            icon: Icon(CupertinoIcons.bell, color: Colors.black),
          ),
        ],

CupertionoIcons는 외부의 라이브러리에서 불러오는 아이콘이기 때문에 별도로 Import를 해주어야 사용이 가능한 클래스이다. Import는 코드 최상단에 해당 패키지를 선언하면 된다.

import 'package:flutter/cupertino.dart';

 

2. Body

가장 먼저 레이아웃부터 나누어야 한다.

만들고자 하는 위젯의 형태는 Row와 Column 즉 위젯이 가로, 세로 배치가 어떻게 되어있는지 구분하고 각 섹션 별로 나누어 큰 틀부터 작업을 하는 방식으로 진행하는 것이 좋다.

 

섹션을 나눌 때는 Refactor 기능을 사용해서 나누어준다. ( 단축키 : Refactor 할 위젯 클릭 후  Ctrl + Shift + R )

위에서 나누었던 레이아웃대로 틀을 만들어준다. 이렇게 틀을 만들고 각 구간마다 위젯을 넣어서 만들도록 한다.

body: Row(
        children: [
          // 이미지 들어갈 자리
          Column(
            children: [
              // 'M1 아이패드 프로 11형(3세대) 와이파이 128G 팝니다.'
              // '봉천동 · 6분 전'
              // '100만원'
              Row(
                children: [
                  // 빈 칸
                  // 하트 아이콘
                  // '1'
                ],
              ),
            ],
          ),
        ],
      ),

 

Image

좌측에 표시되는 이미지부터 만들어준다. 

          // 이미지 들어갈 자리
          // CilpRRect 를 통해 이미지에 곡선 border 생성
          ClipRRect(
            borderRadius: BorderRadius.circular(8),
            // 이미지
            child: Image.network(
              width: 100,
              height: 100,
              fit: BoxFit.cover,  // 일정비율을 유지하면서 이미지 출력
            ),
          ),

ClipRRect는 이미지의 마스킹 같은 역할을 한다. 이미지가 들어갈 영역의 테두리를 곡선으로 만든다.

BoxFit의 경우 여러 설정방법이 있는데 다음 문서에서 확인이 가능하다.

BoxFit Options

cover는 해당 공간에 이미지의 비율을 유지하면서 출력시킨다.

 

Text

우측에 텍스트를 생성한다. 텍스트들은 세로로 정렬되도록 Column안에 만들어준다.

          Column(
            children: [
              // 'M1 아이패드 프로 11형(3세대) 와이파이 128G 팝니다.'
              // '봉천동 · 6분 전'
              // '100만원'
              Text(
                'M1 아이패드 프로 11형(3세대) 와이파이 128G 팝니다.',
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.black,
                ),
                softWrap: false,
                maxLines: 2,
                overflow: TextOverflow.ellipsis, // 텍스트가 크기를 벗어나면 잘라냄
              ),
              SizedBox(height: 2),
              Text(
                '봉천동 · 6분 전',
                style: TextStyle(
                  fontSize: 12,
                  color: Colors.black45,
                ),
              ),
              SizedBox(height: 4),
              Text(
                '100만원',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Row(
                children: [
                  // 빈 칸
                  // 하트 아이콘
                  // '1'
                ],
              ),
            ],
          ),

 

overflow : TextOverflow.elipsis 는 텍스트가 영역의 크기를 벗어나는 경우 보이지 않게 처리한다.

그런데도 출력되는 화면을 확인해 보면 오버플로우가 발생한다.

 

이 문제는 Column 위젯의 폭이 설정되지 않아서 발생하는 문제로 Column이 차지하는 영역이 얼마나 차지해야 하는지 몰라서 무한대로 설정이 되어버려서 발생하는 문제이다.

 

이 문제는 앞서 과제를 하면서 해결해 봤던 문제로 Width 속성으로 Column의 크기를 직접 설정해 주거나 남은 공간만큼만 차지하도록 Expanded 위젯으로 감싸주면 된다.

Flexible과 Expanded

 

현재 이미지와 텍스트가 따로 놀고 있는데 이 요소를 정렬해 주어야 한다. 

정렬을 하기 위해서 우선 주축과 부축에 대한 개념을 정리하고 간다.

Row와 Column 위젯은 주축(main axis), 부축(cross axis)으로 정렬이 가능하다.

Main Axis & Cross Axis

 

이미지와 텍스트를 정렬은 Row의 CrossAxisAlignment start로 정렬하고 텍스트 요소들의 Column도 마찬가지로 start로 정렬한다. start는 요소들을 왼쪽 시작점으로 정렬시킨다.

      body: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
 ~~
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,

이미지와 텍스트를 정렬시키고 두 위젯 사이에도 공간을 넣어준다. 이때도 SizeBox를 사용해서 적절한 빈 공간을 넣어 띄워준다.

 

Like Button

좋아요 버튼을 생성한다.

텍스트 요소들의 장하단에 위치하며 우측에 위치해 있다.

                Row(
                  children: [
                    // 빈 칸
                    // 하트 아이콘
                    // '1'
                    Spacer(), // 요소외 공간 모두 차지
                    GestureDetector(  // 클릭시 반응하도록
                      onTap: () {},
                      child: Row(
                        children: [
                          Icon(
                            CupertinoIcons.heart,
                            color: Colors.black54,
                            size: 16,
                          ),
                          Text(
                            '1',
                            style: TextStyle(color: Colors.black54),
                          ),
                        ],
                      ),
                    )
                  ],
                ),

버튼은 Row 정렬시키는데 여기서 버튼을 우측으로 보내기 위해서 Spacer 위젯을 사용한다.

Spacer는 해당 영역 내에서 다른 요소들의 크기를 제외한 나머지 공간을 모두 차지한다.

Icon과 Text는 GestureDetector로 감싸서 클릭 이벤트를 받을 수 있도록 만든다.

 

 

Floating Action Button

요소들 위에 떠있는 버튼을 추가해 준다. 

해당 버튼은 Body 외부요소로 scaffold의 floatingActionButton 속성을 정의해서 만든다.

      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        backgroundColor: Color(0xFFFF7E36),
        elevation: 1,
        child: Icon(
          Icons.add_rounded,
          size: 36,
        ),
      ),

 

Bottom Navigation Bar

앱 하단에 생성되는 바이다.

Scaffold의 bottomNavigationBar 속성을 정의해서 만들 수 있다.

      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.black,
        unselectedItemColor: Colors.black,
        showUnselectedLabels: true,
        selectedFontSize: 12,
        unselectedFontSize: 12,
        iconSize: 28,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home_filled),
            label: '홈',
            backgroundColor: Colors.white,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.my_library_books_outlined),
            label: '동네생활',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.fmd_good_outlined),
            label: '내 근처',
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.chat_bubble_2),
            label: '채팅',
          ),
          BottomNavigationBarItem(
            icon: Icon(
              Icons.person_outline,
            ),
            label: '나의 당근',
          ),
        ],
        currentIndex: 0,
      ),

items 배열 안에 버튼들을 추가해 줄 수 있으며 currentIndex를 통해서 어떤 요소가 선택되었는지 정할 수 있다.