[Flutter] 스나이퍼팩토리 6일차

KWANWOO·2023년 2월 1일
1
post-thumbnail

스나이퍼팩토리 플러터 6일차

6일차에서는 Custom Widget을 학습하고 실제로 존재하는 앱의 UI를 구현해 보았다.

학습한 내용

  • Custom 위젯
  • 스타벅스 앱 화면 따라 만들기

추가 내용 정리

Custom Widget

플러터에서는 직접 Custom Widget을 생성하여 사용할 수 있다.

이러한 Custom Widget은 반복되는 코드를 작성해야 할 때, 미리 하나의 위젯으로 생성해 놓으면 같은 코드를 여러번 작성하지 않아도 돼서 코드가 간결해진다.

const 와 final

코딩을 진행하다 보면 에러는 아니지만 const에 대한 경고 메세지가 자주 발생한다. 그리고 이번에 Custom Widget을 생성하면서 final을 사용해보았는데 이 두가지에 대해 정리해 보고자 한다.

constfinal 모두 값을 한 번 지정하면 바꿀 수 없다는 공통적인 속성을 가지고 있다. 하지만 생성 시점이 다르다는 차이가 있다.

const로 선언된 변수 또는 위젯의 경우 생성을 앱이 시작될 때 메모리에 같이 등록하여 해당 시점에 새로 생성되는 것이 아닌 기존의 메모리에 올라가 있는 인스턴스를 계속 재사용 하게 된다.

즉, const를 통한 재사용 시 사용하지 않는 인스턴스가 메모리에 남게되는 메모리 낭비를 효율적으로 관리할 수 있다.

const는 선언부에서 무조건 값을 부여해야 하지만 final은 선언 시 값을 부여하지 않아도 된다. final 선언 시 값을 부여하지 않은 경우 이후에 최초 1번만 값을 부여할 수 있다. 그렇기 때문에 선언부의 코드만으로 컴파일러가 해당 변수의 값을 알 수 없어서 const처럼 앱 실행 시 메모리에 넣어 관리를 할 수는 없다.


6일차 과제

  1. 스타벅스 앱 화면 제작

1. 스타벅스 앱 화면 제작

아래의 화면과 같은 스타벅스 앱 화면을 제작하고자 한다.

  • 예시 결과

사용할 데이터와 요구사항은 다음과 같다.

  • Data
  1. 골든 미모사 그린 티, Golden Mimosa Green Tea
  2. 블랙 햅쌀 고봉 라떼, Black Rice Latte
  3. 아이스 블랙 햅쌀 고봉 라떼, Iced Black Rice Latte
  4. 스타벅스 튜메릭 라떼, Starbucks Turmeric Latte
  5. 아이스 스타벅스 튜메릭 라떼, Iced Starbucks Turmeric Latte
  • Requirements
  1. 음료 이미지는 CircleAvatar를 사용하며 48의 반지름크기를 갖는다.
  2. 음료 영문명의 font size는 14pt이며 회색으로 w200의 굵기를 가진다.
  3. 음료의 정보를 보여주는 위젯을 만들고, 이름은 DrinkTile로 한다.
  • 코드

DrinkTile.dart

import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';

class DrinkTile extends StatelessWidget {
  const DrinkTile(
      {super.key,
      required this.drinkImg,
      required this.korDrinkName,
      required this.engDrinkName,
      required this.price});

  final String drinkImg; // 음료 이미지
  final String korDrinkName; // 음료 한글명
  final String engDrinkName; // 음료 영문명
  final String price; // 음료 가격

  
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
      // 음료 1개의 Row
      child: Row(
        children: [
          Padding(
            padding: const EdgeInsets.only(right: 16.0),
            child: CircleAvatar(
              radius: 48,
              backgroundImage: AssetImage(drinkImg),
            ),
          ),
          // 음료 1개의 텍스트 정보 Column
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
                korDrinkName,
              ),
              Text(
                style: TextStyle(
                  color: Colors.grey,
                  fontSize: 14,
                  fontWeight: FontWeight.w200,
                ),
                engDrinkName,
              ),
              SizedBox(height: 4),
              Text(
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
                price,
              ),
            ],
          ),
        ],
      ),
    );
  }
}

main.dart

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        //앱바
        appBar: AppBar(
          elevation: 0,
          backgroundColor: Colors.transparent,
          leading: Icon(
            color: Colors.black,
            Icons.arrow_back_ios,
          ),
          actions: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Icon(
                color: Colors.black,
                Icons.search,
              ),
            ),
          ],
        ),
        // 내부 요소 Column
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
              child: Text(
                style: TextStyle(
                  fontSize: 32,
                  fontWeight: FontWeight.bold,
                ),
                'NEW',
              ),
            ),
            // 음료 리스트
            Expanded(
              child: ListView(
                children: [
                  DrinkTile(
                    drinkImg: 'assets/images/item_drink1.jpeg',
                    korDrinkName: '골든 미모사 그린 티',
                    engDrinkName: 'Golden Mimosa Green Tea',
                    price: '6100원',
                  ),
                  DrinkTile(
                    drinkImg: 'assets/images/item_drink2.jpeg',
                    korDrinkName: '블랙 햅쌀 고봉 라떼',
                    engDrinkName: 'Black Rice Latte',
                    price: '6300원',
                  ),
                  DrinkTile(
                    drinkImg: 'assets/images/item_drink3.jpeg',
                    korDrinkName: '아이스 블랙 햅쌀 고봉 라떼',
                    engDrinkName: 'Iced Black Rice Latte',
                    price: '6300원',
                  ),
                  DrinkTile(
                    drinkImg: 'assets/images/item_drink4.jpeg',
                    korDrinkName: '스타벅스 튜메릭 라떼',
                    engDrinkName: 'Starbucks Turmeric Latte',
                    price: '6100원',
                  ),
                  DrinkTile(
                    drinkImg: 'assets/images/item_drink5.jpeg',
                    korDrinkName: '아이스 스타벅스 튜메릭 라떼',
                    engDrinkName: 'Iced Starbucks Turmeric Latte',
                    price: '6100원',
                  ),
                ],
              ),
            ),
          ],
        ),
        // 하단 sheet
        bottomSheet: Container(
          height: 60,
          padding: EdgeInsets.all(16.0),
          color: Colors.black,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                style: TextStyle(color: Colors.white),
                '주문할 매장을 선택해 주세요.',
              ),
              Icon(color: Colors.white, Icons.expand_more),
            ],
          ),
        ),
        // 하단 네비게이션 바
        bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed, //네비게이션 타입
          selectedItemColor: Colors.green,
          currentIndex: 2,
          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), label: 'Shop'),
            BottomNavigationBarItem(
                icon: Icon(Icons.more_horiz), label: 'Other'),
          ],
        ),
      ),
    );
  }
}
  • 결과

DrinkTile.dart

각 음료의 정보를 제공하는 DrinkTile 위젯을 보면 매개 변수로 dringImg korDrinkName engDrinkName price 를 설정해 음료 이미지, 음료 한글명, 음료 영문명, 음료 가격을 String으로 받도록 했다.

Row위젯을 Padding으로 감싸 return 하도록 작성했는데 먼저 원형의 이미지를 띄우기 위해 CircleAvatar를 사용했고, 반지름을 48로 설정했다.

다음으로 음료의 텍스트 정보들을 담기 위해 Column 위젯을 사용했으며 3개의 Text 위젯을 children 리스트에 넣어주었다. 글씨 크기는 모두 14pt로 설정했고, 음료의 영문명에는 color: Colors.greyfontWeight: FontWeight.w200을 설정했다. 나머지 텍스트는 fontWeight: FontWeight.bold로 설정해 주었다.

main.dart

메인 파일에서는 먼저 앱바를 생성해 주었는데 배경을 투명하게 하고 그림자를 제거했다. 또한 leading 속성으로 뒤로가기 아이콘을 삽입하고, actions 속성으로 검색하기 아이콘을 넣어주었다.

아래의 제목 텍스트와 음료의 정보는 하나의 Column으로 생성했다.

NEW라는 텍스트를 보여주는 Text 위젯은 Padding으로 감싸 위치시켰다.

Column 위젯 안에서 ListView 위젯을 사용하면 ListView의 높이가 무한이 되어 에러가 발생하기 때문에 Expanded안에 ListView를 생성했다. ListView의 요소로는 앞에서 만든 DrinkTile 커스텀 위젯을 사용했다.

화면 아래쪽의 주문할 매장을 선택하는 레이아웃은 bottomSheet 속성을 사용했는데 이 속성에서 BottomSheet 위젯을 사용하면 화면 아래에 고정되거나 크기가 변동하는 시트를 생성할 수 있다. 하지만 이번에는 전체적인 화면만 구성하고 주문의 기능까지는 구현하지 않기 때문에 간단히 ContainerRow 위젯을 사용하여 UI만 구현해 주었다.

하단 네비게이션 바는 bottomNavigationBar 속성에 BottomNavigationBar 위젯을 사용하여 생성했다. 아이템은 총 5개를 삽입했고, 선택된 아이템은 초록색으로 보이도록 했다. 기존에 만들어 봤던 하단 네비게이션 바와 다른점은 현재 선택되어 있는 아이템을 세 번째로 설정하는 것이었는데 currentIndex 속성을 2로 설정하여 해결하였다.


블로그 내용은 짧지만 빨리 끝나진 않았다. ㅠㅠ

이번에는 학습을 진행한 내용은 커스텀 위젯 하나이고, 이 위젯과 지금까지 배웠던 위젯들을 이용해 스타벅스 앱 화면을 만드는 것이 메인이었다. 그래서 이번 블로그 내용은 짧긴 하지만 생각보다 좀 걸렸다. 가장 막힌 부분은 하단에 주문할 매장을 선택하라고 하는 저 검은색 박스...ㅠ 처음에는 FloatingActionButton을 사용해서 만들어 보려고 했는데 어떤 방법을 써봐도 가로 길이가 화면에 딱 맞게 확장이 안됐다ㅠㅠ 그리고 하단 네비게이션 바 바로 위에 위치도 안됐고 ㅋㅋㅋㅋ 그래서 찾아본 것이 PersistentFooterButtons 인데 이 위젯은 위치는 딱 맞았지만 크기 조절이 안됐고 (내가 못한거 일수도..?) 배경 색도 컨테이너를 사용하면 화면에 딱 맞게 채워지지 않았다. 결국 찾은 것이 bottomSheet 이다.ㅎㅎ 기능 구현까지는 아직 못했지만 원하던 UI가 만들어져서 이걸 사용했다. 오늘은 블로그 글은 짧은데 후기가 기네 ㅋㅋㅋㅋ

📄 Reference

profile
관우로그

0개의 댓글

관련 채용 정보