본문 바로가기
Flutter

[Flutter] 당근마켓 퍼블리싱 클론 코딩 정리 (1)

by jungha_k 2023. 6. 15.

* 해당 코드의 모든 출처는 '모두가 할 수 있는 플러터 UI - 실전편' 입니다.

상세적인 전체 코드는 올리지 않았으며,
나중에 실무에서 코드를 작성할 때 참고하거나 기억하면 좋을 위젯, 코드들을 개인 정리 용으로 적어놓았습니다.


* 디렉토리 구성

lib
models : 퍼블리싱에 쓰일 스텁 데이터들 
- screens : 화면들 (탭 단위로 디렉토리 나뉨, 각각 디렉토리안 components 폴더 존재)
     - chatting 

     - components : 앱바, 이미지 컨테이너 등 앱 전반적으로 쓰이는 것들 
     - home 
     - my_carrot 
     - near_me 
     - neighborhood_life
     - main_screen.dart : 각각 화면을 모두 포함 (네비게이션 바)
- main.dart : runApp(), home 설정(main_screen)
- theme.dart : 전역 테마 설정 

 


1. MainScreen

: main.dart 파일에서 home 으로 사용되는 클래스이다.

(* main.dart 에서 routes{} 속성을 이용하는 것이 더 일반적 같은데 home 과 route의 차이점은 글을 새로 작성할 예정이다.)

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _selectedIndex,
        children: [
          HomeScreen(),
          NeighborhoodLifeScreen(),
          NearMeScreen(),
          ChattingScreen(),
          MyCarrotScreen(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.white,
        type: BottomNavigationBarType.fixed,
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.black,
        unselectedItemColor: Colors.black54,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        
        ... 생략 ...

상태가 바뀌므로 stateful 위젯이며,

Body 로 IndexedStack 을 사용하여 만든 5개의 화면 위젯들을 추가한다.

 

5개의 위젯들을 그냥 추가할 수도 있으나, 이렇게 IndexedStack에 담아주는 이유는 리소스 낭비를 막기 위함이라고 한다.

매번 페이지를 새로 렌더링하기보다, stack에 렌더링이 필요한 화면을 최상단에 담아주고, 
그 이전에 존재하는 위젯을 다른곳에 보관하는 방식으로 사용하기 때문이다. 

 

또한 이 탭들에 한해서 화면 간 이동(route) 가 필요하기 때문에

BottomNavigationBar를 사용해주었다. (Index를 주고, onTap 에 setState를 통해서 이동하는 방식)


2. HomeScreen

 

가장 먼저 보이는 Home 화면

home_screen 화면에서 전체적인 그림을 그리고,

product_item에서 하나의 아이템에 대한 전체적인 위치 및 크기와, 하나의 아이템 사진을 담아주었다.

product_detail에서 오른쪽에 있는 상세 내용에 관한 UI를 그려주었다.

 

하나의 상품을 그려내는 컴포넌트들을 제작했다고 했을때,

home_screen 에서 이를 등록된 더미데이터로 반복적으로 그려주었던 코드는 아래와 같다.

 

body: ListView.separated(
        separatorBuilder: (context, index) => const Divider(
            height: 0, indent: 16, endIndent: 16, color: Colors.grey),
        itemBuilder: (context, index) {
          return ProductItem(
            product: productList[index],
          );
        },
        itemCount: productList.length,
      ),

* ListView.seperated : 각 항목을 구분할 수 있는 구분선이 들어간 ListView 를 만든다!

 

separatorBuilder : 구분선에 대한 설정 지정

itemBuilder : (context, index) ➡️ index 번에 해당하는 항목에 그려질 view를 반환하는 함수 (index는 0부터 시작한다)

itemCount : ListView 항목들의 총 갯수! int 값이다.

 

이 코드 내에서는 Product 라는 상품 클래스와 샘플 데이터가 들어간 리스트 productList 를 만들어주었기 때문에

productList의 index 값으로 활용할 수 있었다.

 


3. MyCarrotScreen

 

플러터를 공부하면서 느낀점은 

UI 화면을 구성할때 크고 작은 부분을 잘 나누어 레이아웃을 구성하고,

또 겹치는 부분이 있으면 무조건 공통 컴포넌트로 뽑아내어 만든다는 점이었다.

 

예를 들어 해당 화면의 헤더 부분만 해도 

appBar / 프로필 이미지 / 프로필 정보 / 프로필 보기 버튼 / 주황색 상세 버튼 
으로 나누어서 구성을 하였다.

 

 

Card 위젯 : 입체감과 모서리에 곡선이 필요한 위젯을 만들 때 사용했다. (기본적으로 margin 有)

 

프로필 사진 위에 카메라 이모지가 올라간 부분 - 

Stack 위젯 : 이미지 위에 다른 위젯을 쌓고 꾸며 줄 수 있다. (+Positioned 위젯)

 

하단의 내 동네 설정 ~ 동네생활 주제목록 부분은 이모지 + 텍스트가 반복되는 리스트였다.

이를 구성해준 코드는 다음과 같다.

... 상단 생략 ...
child: Column(
          children: List.generate(
            iconMenuList.length,
            (index) => _buildRowIconItem(
                iconMenuList[index].title, iconMenuList[index].iconData),
          ),
        ),
      ),
    );
  }

  Widget _buildRowIconItem(String title, IconData iconData) {
    return Container(
      height: 50,
      child: Row(
        children: [
          Icon(iconData, size: 17),
          const SizedBox(width: 20),
          Text(title, style: TextTheme().labelLarge)
        ],
      ),
    );
  }

List.generate : 리스트를 만들어주는 생성자이다. 

length의 길이만큼 0 부터 index-1 까지 범위의 인덱스를 오름차순으로 호출하여 만든 값으로, 리스트를 생성한다.

 

멤버변수 IconMenuList (=더미데이터 IconMenu 클래스의 List 형태) 에 들어오는 값으로
_buildRowIconItem 메서드를 호출시켜 위젯을 만들어 주었다.

 


4. Chatting

 

* appbar의 하단 구분선, 이미지 콘테이너 같이
공통되는 component는 꼭 Class 파일이 아닌 별도의 파일에 전역 메서드로 만들어 사용할 수도 있다. 

 

PreferredSize 위젯 : 자식 위젯에게 어떤 제약도 부과하지 않고 부모 위젯에게 공간을 차지하는 크기만을 알려주는 위젯

 

Text.rich 위젯 : 문단 단위로 텍스트를 꾸며줄 수 있다.

 

Visibility 위젯 : 이미지가 null이 아닐 경우의 위젯을 보여준다.

Visibility(
              visible: chatMessage.imageUri != null,
              child: Padding(
                padding: const EdgeInsets.only(left: 8.0),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(10),
                  child: ImageContainer(
                    width: 50,
                    height: 50,
                    borderRadius: 8,
                    imageUrl: chatMessage.imageUri ?? '',
                  ),
                ),
              ),
            ),

댓글