본문 바로가기
개발일지/GoodWishes

[굿위시 제작기] 4. 굿즈 메인 페이지 제작하기

by 박기린 2024. 8. 27.

 


페이지 디자인 의도

피그마에서 위와 같은 디자인을 만들었습니다.

 

 

 

사실 이 디자인은

애플뮤직의 UI를 대부분 갖고 온 것입니다.

그러다보니 안드로이드 폰을 쓰는 친구들에게는 생소한 느낌이라는 이야기를 많이 들었고, 아이폰 유저이면서 특히 애플뮤직 유저인 친구들에게는 애플뮤직 갖다 썼다는 소리를 들었습니다.

 

제가 보기에는 애플 뮤직의 디자인이 본 앱에 꽤 어울린다는 생각이 들어서, 위처럼 제작하고자 했습니다.

 

 

 


제작 결과물

제법 무난하게 제작됐습니다.

 

 

 


상단 : TopWithProfile

화면 최상단에 페이지의 이름과 프로필 이미지가 나오는 부분입니다.

왼쪽은 그냥 페이지에 따라 페이지 이름이 나오게 끔 만들었고, 오른쪽 프로필 아이콘버튼을 누르면 계정관리 페이지로 넘어가는 구조입니다.

 

SizedBox(
      height: 50,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          // page title
          PageTitle(title: title),

          // profile icon
          ProfileIcon(
            profile: profileProvider.profile,
            onClick: profileClickHandler,
          ),
        ],
      ),
    );

SizedBox로 상단 구역의 크기를 지정한 후, (Height : 50)

Row 위젯을 통해 페이지 타이틀과 프로필 아이콘을 수평으로 둡니다.

mainAxisAlignment를 spaceBetween으로 둠으로써, 페이지 타이틀과 프로필 아이콘이 붙어있지 않고 양쪽으로 떨어지게 끔 지정합니다.

 

 

 


중단 : 최근에 추가된 굿즈들

중단의 '최근에 추가된 굿즈' 영역입니다.

이때 특징은 가로로 스크롤이 되어야 한다는 점입니다.

 

SizedBox(
      height: 300,
      width: MediaQuery.of(context).size.width,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        shrinkWrap: true,
        itemCount: goodsList.length,
        itemBuilder: (context, index) {
          int curIdx = goodsList.length - index - 1; // 최신순으로 보게 끔 조정
          return HorizonListEl(
            // imageRoute: 'assets/goods.jpeg',
            image: goodsList.elementAt(curIdx).thumbnail,
            goodsName: goodsList.elementAt(curIdx).goodsName,
            date: goodsList.elementAt(curIdx).date,
            id: goodsList.elementAt(curIdx).id,
          );
        },
      ),
    );

그래서 ListView.builder() 위젯을 이용합니다.

ListView.builder()scrollDirection 속성에 Axis.horizontal을 지정해주면, 가로스크롤이 가능해집니다.

 

 

 

그리고 itemBuilder 속성에 goodsList의 갯수에 따라 HorizonListEl() 위젯을 각각 출력해주는 builder 함수를 전달합니다.

HorizonListEl() 위젯은 위 사진에 보이는 타일 한 개를 의미합니다.

 

 

 


하단 : 굿즈 카테고리 리스트

마지막으로 굿즈의 카테고리를 출력하는 하단부분입니다.

 

 

저렇게 2개의 아이템 씩 출력하는 방식을 구현하는 데에 복잡한 코드들이 들어갔습니다.

ListView.builder(
      physics: const NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      itemCount: (categoryList.length / 2).ceil(),
      itemBuilder: (context, index) {
        Goods leftGoods = goodsList.goodsList.firstWhere(
            (element) =>
                element.category ==
                categoryList.elementAt(index * 2).categoryName,
            orElse: () => Goods.createEmptyGoods());

        if (index * 2 + 1 >= categoryList.length) {
          return CategoryListRow(
            firstImage: leftGoods.id == '' ? null : leftGoods.thumbnail,
            firstItemName: categoryList.elementAt(index * 2).categoryName,
            // secondItemName: categoryList.elementAt(index * 2 + 1).categoryName,
          );
        } else {
          Goods rightGoods = goodsList.goodsList.firstWhere(
              (element) =>
                  element.category ==
                  categoryList.elementAt(index * 2 + 1).categoryName,
              orElse: () => Goods.createEmptyGoods());

          return CategoryListRow(
            firstImage: leftGoods.id == '' ? null : leftGoods.thumbnail,
            firstItemName: categoryList.elementAt(index * 2).categoryName,
            secondImage: rightGoods.id == '' ? null : rightGoods.thumbnail,
            secondItemName: categoryList.elementAt(index * 2 + 1).categoryName,
          );
        }
      },
    );

코드 전문은 이러하지만, 우선 천천히 하나씩 살펴보겠습니다.

 

 

우선 ListView.builder()의 속성들을 살펴보겠습니다.

physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: (categoryList.length / 2).ceil(),

physics : NeverScrollableScrollPhysics()를 넣어주면, 스크롤 기능이 막힙니다. 이미 화면 전체에 스크롤 기능이 있기 때문에, 이중 스크롤을 방지하고자 위 속성을 넣었습니다.

shrinkWrap : 이 속성에 true를 넣어주면, 자동으로 위젯 리스트의 길이를 지정해줍니다.

itemCount : 2개의 타일을 보여주는 형식이기 때문에, 카테고리 리스트의 길이에 2를 나눕니다. 근데 만약 5개의 카테고리가 있을 경우, 2개/2개/1개로 총 3줄로 구성된 위젯 리스트가 필요합니다. 그렇기 때문에, 2로 나눈 후 ceil()을 통해 반올림을 하는 코드를 넣어줍니다.

 

 

이제 대망의 itemBuilder()를 살펴보겠습니다.

itemBuilder: (context, index) {
        Goods leftGoods = goodsList.goodsList.firstWhere(
            (element) =>
                element.category ==
                categoryList.elementAt(index * 2).categoryName,
            orElse: () => Goods.createEmptyGoods());

        if (index * 2 + 1 >= categoryList.length) {
          return CategoryListRow(
            firstImage: leftGoods.id == '' ? null : leftGoods.thumbnail,
            firstItemName: categoryList.elementAt(index * 2).categoryName,
          );
        } else {
          Goods rightGoods = goodsList.goodsList.firstWhere(
              (element) =>
                  element.category ==
                  categoryList.elementAt(index * 2 + 1).categoryName,
              orElse: () => Goods.createEmptyGoods());

          return CategoryListRow(
            firstImage: leftGoods.id == '' ? null : leftGoods.thumbnail,
            firstItemName: categoryList.elementAt(index * 2).categoryName,
            secondImage: rightGoods.id == '' ? null : rightGoods.thumbnail,
            secondItemName: categoryList.elementAt(index * 2 + 1).categoryName,
          );
        }

 

 대충 설명을 드리자면,

만약 카테고리 리스트의 갯수가 2로 딱 나눠진다면 CategoryListRow()에서 총 두 개의 타일이 출력되게 끔 

 

 

 

만약 카테고리 리스트의 갯수가 2로 딱 나눠지지 않는다면, 마지막 줄의 CategoryListRow()에서 총 한 개의 타일이 출력되게 끔 합니다.

 

 

 

 

위 상황의 경우, 카테고리가 총 3개이기 때문에 2로 나눠지지 않습니다. 그러므로 첫 번째 줄은 정상적으로 2개의 타일이 생성되지만, 마지막 줄은 1개의 타일만 보입니다.

 

 

 

 

 

또 고려해야 할 점이, 카테고리 타일에는 썸네일이 있습니다.

이 썸네일은 카테고리를 생성하면서 따로 지정하는 것이 아니라, '가장 최근에 카테고리에 들어간 굿즈의 썸네일'을 가져와서 보여주는 것입니다.

 

 

 

        Goods leftGoods = goodsList.goodsList.firstWhere(
            (element) =>
                element.category ==
                categoryList.elementAt(index * 2).categoryName,
            orElse: () => Goods.createEmptyGoods());
            
            
 		// 카테고리 갯수가 짝수일 경우
           Goods rightGoods = goodsList.goodsList.firstWhere(
              (element) =>
                  element.category ==
                  categoryList.elementAt(index * 2 + 1).categoryName,
              orElse: () => Goods.createEmptyGoods());

굿즈리스트에서 해당 카테고리에 포함된 카테고리 중 가장 최근에 만들어진 굿즈들을 불러옵니다.

그리고 그것들을 leftGoods와 rightGoods 변수에 담습니다.

 

만약 카테고리만 있고, 그 카테고리 안에 아무런 굿즈가 아직 안 담겨 있다면,

              orElse: () => Goods.createEmptyGoods());

Goods의 static 메소드인 createEmptyGoods()를 이용해서 가상의 빈 굿즈를 만들어줍니다. (Goods 클래스와 메소드는 본 프로젝트에서 생성된 클래스입니다.)

그리고 빈 굿즈에 담긴 텅 빈 썸네일을 출력하는 방식으로, 빈 카테고리의 썸네일을 출력합니다.

 

 


다음 편은 바텀 내비게이션에 대해 이야기하기

 

이제 메인페이지를 만들었으니, 다른 페이지들과 징검다리 역할을 해줄 바텀 내비게이션 바를 만들어야 합니다.

다음 편에서 바텀 내비게이션을 만든 과정에 대해 이야기해보겠습니다.

 

 

 

 

반응형