매일 땡기는 마라 코딩

[9주 완성 프로젝트 캠프 : 플러터(유데미x스나이퍼팩토리)] 3일차 과제 - 스타벅스 클론코딩 본문

Flutter

[9주 완성 프로젝트 캠프 : 플러터(유데미x스나이퍼팩토리)] 3일차 과제 - 스타벅스 클론코딩

cmkoi1 2023. 9. 21. 03:33

요구사항

  • 음료 이미지는 CircleAvatar를 사용하며 48의 반지름크기를 갖는다.
  • 음료 영문명의 font size는 14pt이며 회색으로 w200의 굵기를 가진다.
  • 음료의 정보를 보여주는 위젯을 만들고, 이름은 DrinkTile로 한다.

 

 

사전 지식

"bottomSheet"

하단 메뉴나 액션을 표시하기 위해 사용한다.

스크롤에 따라 위치가 변하지 않는다.

항상 볼 수 있는 Persistent BottomSheet와 일시적으로 사용하는 Modal BottomSheet로 나눌 수 있다.

높이, 색상 등을 지정할 수 있다.

 

 

 

"mainAxisAlignment, crossAxisAlignment"

Row, Column을 정렬하기 위해 사용한다.

 

Row(행)은 가로가 main이 된다.

Column(열)은 세로가 main이 된다.

 

mainAxisAlignment의 속성은?

  • center: 가운데 정렬
  • start: Row 기준 왼쪽 정렬, Column 기준 위쪽 정렬
  • end: Row 기준 오른쪽 정렬, Column 기준 아래쪽 정렬
  • spaceEvently: 자식 위젯 사이의 공간을 균등하게 배분
  • spaceBetween: 처음, 끝 위젯을 시작과 끝에 배치하고, 사이 위젯을 균일하게 배치
  • spaceAround: 처음, 끝 위젯의 앞뒤 공간을, 나머지 위젯 사이 공간의 반만큼 설정하여 배치

 

crossAxisAlignment의 속성은?

  • center, start, end는 같음(화면에 출력되는 게 같다는 게 아닙니당)
  • stretch: 좌우를 꽉 차게 배치
  • baseline: 베이스 라인 기준으로 Row는 수평 정렬, Column은 수직 정렬(폰트 크기가 다를 때 유용)

 

 

 

bottomNavigationBar의 속성

  • BottomNavigationBarType.fixed: item 4개 이상 추가 시 문제 발생을 방지하기 위해 아이템의 너비 고정(모두 동일한 너비)
  • currentIndex: 현재 선택된 아이템의 인덱스를 지정
  • fixedColor: 선택된 아이템의 색상 지정

 

 

 

그 외

  • foregroundColor: Colors.색상명 - 글씨 색상 지정
  • elevation: 0 - 그림자를 0으로 지정

 

ListView에 Padding을 지정해 주면 자식 위젯에 하나씩 Padding을 지정해 줄 필요가 없다. 꿀팁!

 

 

코드

main.dart

import 'package:assigment1/DrinkTile.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          elevation: 0,
          backgroundColor: Colors.transparent,
          foregroundColor: Colors.black,
          leading: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Icon(Icons.keyboard_arrow_left),
          ),
          actions: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Icon(Icons.search),
            ),
          ],
        ),
        body: Padding(
          //여기다 걸면 하나하나 안 해 줘도 됨!
          padding: const EdgeInsets.all(16),
          child: ListView(
            children: [
              Text(
                'new'.toUpperCase(), //소문자를 대문자로 변환
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 32,
                ),
              ),
              SizedBox(
                height: 16,
              ),
              DrinkTile(
                title: '골든 미모사 그린 티',
                subTitle: 'Golden Mimosa Greern Tea',
                price: 6100,
                imgUrl: 'assets/images/item_drink1.jpeg',
              ),
              DrinkTile(
                title: '블랙 햅쌀 고봉 라떼',
                subTitle: 'Black Rice Latte',
                price: 6300,
                imgUrl: 'assets/images/item_drink2.jpeg',
              ),
              DrinkTile(
                title: '아이스 블랙 햅쌀 고봉 라떼',
                subTitle: 'Iced Black Rice Latte',
                price: 6300,
                imgUrl: 'assets/images/item_drink3.jpeg',
              ),
              DrinkTile(
                title: '스타벅스 튜메릭 라떼',
                subTitle: 'Starbucks Turmeric Latte',
                price: 6100,
                imgUrl: 'assets/images/item_drink4.jpeg',
              ),
              DrinkTile(
                title: '아이스 스타벅스 튜메릭 라떼',
                subTitle: 'Iced Starbucks Turmeric Latte',
                price: 6100,
                imgUrl: 'assets/images/item_drink5.jpeg',
              ),
            ],
          ),
        ),
        bottomSheet: Container(
          height: 64,
          color: Colors.black87,
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  '주문할 매장을 선택해 주세요.',
                  style: TextStyle(color: Colors.white),
                ),
                Container(
                  child: Icon(
                    Icons.keyboard_arrow_down,
                    color: Colors.white,
                  ),
                ),
              ],
            ),
          ),
        ),
        bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed, //형태 고정
          currentIndex: 2, //2번 인덱스로 고정(Order)
          fixedColor: Colors.green, //선택된 item의 색상 지정
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: ('Home')),
            BottomNavigationBarItem(
                icon: Icon(Icons.credit_card), label: ('Pay')),
            BottomNavigationBarItem(
                icon: Icon(Icons.local_cafe), label: ('Order')),
            BottomNavigationBarItem(
                icon: Icon(
                  Icons.shop,
                  // color: Colors.black, 노가다할 뻔
                ),
                label: ('Shop')),
            BottomNavigationBarItem(
                icon: Icon(Icons.more_horiz), label: ('Other')),
          ],
        ),
      ),
    );
  }
}

 

DrinkTile.dart

import 'package:flutter/material.dart';

class DrinkTile extends StatelessWidget {
  const DrinkTile(
      {super.key,
      required this.title,
      required this.subTitle,
      required this.price,
      required this.imgUrl});

  final String imgUrl;
  final String title;
  final String subTitle;
  final int price;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(bottom: 16),
      child: Row(
        children: [
          CircleAvatar(
            radius: 48,
            backgroundImage: AssetImage(imgUrl),
          ),
          SizedBox(
            width: 16,
          ),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start, //시작점 정렬
            children: [
              Text(
                title,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
              Text(
                subTitle,
                style: TextStyle(
                  color: Colors.grey,
                  fontWeight: FontWeight.w200,
                ),
              ),
              SizedBox(
                height: 8,
              ),
              Text(
                // price.toString() + '원', //int를 string으로 치환
                "$price원",
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

 

 

 

결과

 

 

 

회고

다른 부분들은 다 만들었는데, 이미지가 확대된 채로 작게 잘려 나와서 왜 그런가 했더니 ListTitle을 사용하지 않고 만드는 거였다니... 예시 사진을 보고 무슨 위젯을 써야 하는지 파악하는 연습도 많이 해 봐야 할 것 같다.

 

아이콘도 이름 몰라서 계속 사이트에 찾아 보고, 속성도 모르는 게 많아 헤매고 ㅠ.

과정 따라가다 보면 익숙해질 거라고 믿는다...!

 

Bottom sheets 부분도 Material Design 사이트에서 찾긴 했으나... 사용 방법을 몰라 헤맸다는 후문.

Bottom sheets의 아이콘이 텍스트 옆에 배치되는 것도 해결 못 해서 나중에 올라온 강의 영상 보고 알았다.

 

근데 과제 해결할 시간 주고, 나중에 해설 영상 올라오는 거 진짜 좋은 듯.

이전에 해 본 클론 코딩은 강의 영상을 따라하는 수준 정도라, 내가 직접 생각해서 코딩하는 게 아니라 얻는 게 적은 느낌이었는데, 이런 방식이 나한테 맞는 것 같다.

 

 

 


 

 

 

추가로, 강의 중에 VSCode로 직접 쳐 본 코드는 DartPad의 기능인 Git Gists 업로드를 사용해서 기록 중이다.

 

cmkoi1’s gists

GitHub Gist: star and fork cmkoi1's gists by creating an account on GitHub.

gist.github.com

 

 

참고 자료

 

본 후기는 유데미-스나이퍼팩토리 9주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

 

#유데미 #udemy #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #웹개발 #앱개발 #플러터 #flutter #개발 #안드로이드 #ios #단기캠프

728x90