7. (실습) 레시피 앱 만들기

박은서's avatar
Apr 29, 2026
7. (실습) 레시피 앱 만들기

Chapter05. 레시피 앱 만들기 실습

0️⃣ 실습 개요

개요

이 실습은 Flutter로 레시피 앱의 메인 화면을 컴포넌트 단위로 분리해서 만드는 예제다.
앱의 시작점인 main.dart에서 MaterialApp을 설정하고, 실제 화면은 RecipePage가 담당한다.
화면 내부는 제목(RecipeTitle), 메뉴(RecipeMenu), 레시피 카드(RecipeListItem)로 분리되어 있어서 Flutter의 위젯 조합 방식과 재사용 가능한 컴포넌트 구조를 함께 연습할 수 있다.

🎯 핵심 학습 포인트

  • main()runApp()으로 Flutter 앱이 시작되는 흐름 이해하기
  • MaterialApp, ThemeData, Scaffold, AppBar의 역할 이해하기
  • ListView로 세로 스크롤 레이아웃 만들기
  • Padding, Row, Column, SizedBox로 간격과 배치 구성하기
  • Container, BoxDecoration, BorderRadius, Border로 메뉴 카드 스타일링하기
  • AspectRatio, ClipRRect, Image.asset()로 카드형 이미지 UI 만들기
  • 파일을 작은 위젯으로 분리하는 이유와 장점 이해하기
  • pubspec.yaml에 등록된 에셋 이미지와 커스텀 폰트가 실제 코드와 연결되는 방식 이해하기

1️⃣ 전체 구조 한눈에 보기

1) 전체 구조

runApp(MyApp) └─ MaterialApp ├─ theme: ThemeData(fontFamily: "PatuaOne") └─ home: RecipePage └─ Scaffold ├─ AppBar │ └─ Icon(CupertinoIcons.heart) └─ body: Padding └─ ListView ├─ RecipeTitle ├─ RecipeMenu ├─ RecipeListItem("coffee", "Made Coffee") ├─ RecipeListItem("burger", "Made Burger") └─ RecipeListItem("pizza", "Made Pizza")

2) 구조 해설

  • MyApp은 앱 전체 설정을 담당한다.
  • RecipePage는 한 화면 전체 레이아웃을 담당한다.
  • RecipeTitle, RecipeMenu, RecipeListItem은 화면을 의미 있는 단위로 분리한 재사용 컴포넌트다.
  • ListView를 사용했기 때문에 레시피 항목이 늘어나도 세로 스크롤이 가능하다.

2️⃣ main.dart

1) 전체 코드

  • 파일 경로 : lib/main.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:recipe_app/pages/recipe_page.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), home: RecipePage(), ); } }

2) 역할 정리

  • 앱의 시작점 역할을 한다.
  • MaterialApp으로 앱의 전역 설정을 담당한다.
  • 첫 화면을 RecipePage()로 지정한다.
  • 기본 폰트를 PatuaOne으로 지정해서 앱 전체 텍스트 스타일의 분위기를 맞춘다.

3) 코드 포인트

  • main()
    • Dart 프로그램의 시작 함수다.
    • Flutter에서는 보통 여기서 runApp()을 호출한다.
  • runApp(MyApp())
    • MyApp 위젯을 화면의 루트로 올린다.
  • MaterialApp
    • Material Design 기반 앱의 최상위 껍데기다.
    • 테마, 라우팅, 디버그 배너, 첫 화면 등을 관리한다.
  • debugShowCheckedModeBanner: false
    • 우측 상단의 DEBUG 배너를 숨긴다.
    • 실습 화면을 더 실제 앱처럼 보이게 할 때 자주 사용한다.
  • theme: ThemeData(fontFamily: "PatuaOne")
    • 앱 전체 기본 폰트를 PatuaOne으로 설정한다.
    • 이 설정이 동작하려면 pubspec.yaml에 폰트 등록이 되어 있어야 한다.
  • home: RecipePage()
    • 앱 실행 직후 보여줄 첫 화면을 지정한다.

4) 참고할 점

  • import 'package:flutter/cupertino.dart';는 이 파일 안에서는 직접 사용되지 않는다.
  • 현재 코드 기준으로는 제거해도 동작에는 문제가 없다.

3️⃣ recipe_page.dart

1) 전체 코드

  • 파일 경로 : lib/pages/recipe_page.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:recipe_app/components/recipe_list_item.dart'; import 'package:recipe_app/components/recipe_menu.dart'; import 'package:recipe_app/components/recipe_title.dart'; class RecipePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: _buildRecipeAppBar(), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: ListView( children: [ RecipeTitle(), RecipeMenu(), RecipeListItem("coffee", "Made Coffee"), RecipeListItem("burger", "Made Burger"), RecipeListItem("pizza", "Made Pizza"), ], ), ), ); } AppBar _buildRecipeAppBar() { return AppBar( backgroundColor: Colors.white, elevation: 1.0, actions: [ Icon( CupertinoIcons.heart, color: Colors.redAccent, ), SizedBox( width: 15, ), ], ); } }

2) 역할 정리

  • 실제 레시피 메인 화면을 구성하는 핵심 페이지다.
  • Scaffold로 화면의 기본 틀을 만들고, 상단 앱바와 본문을 나눈다.
  • 본문에서는 ListView를 사용해 제목, 메뉴, 레시피 카드 목록을 세로로 배치한다.

3) 코드 포인트

  • Scaffold
    • 한 화면의 기본 구조를 담당한다.
    • appBarbody를 분리해서 페이지 구조를 명확하게 만든다.
  • backgroundColor: Colors.white
    • 화면 전체 배경을 흰색으로 고정한다.
  • appBar: _buildRecipeAppBar()
    • 앱바 생성 코드를 메서드로 분리해서 build()를 읽기 쉽게 만든다.
  • Padding(padding: EdgeInsets.symmetric(horizontal: 20))
    • 본문 양옆에 20의 여백을 준다.
    • 카드와 메뉴가 화면 가장자리에 너무 붙지 않게 해 준다.
  • ListView(children: [...])
    • 자식 위젯들을 세로로 배치하면서 스크롤까지 가능하게 만든다.
    • Column과 비슷하게 보이지만, 내용이 길어질 때 overflow를 줄이고 스크롤을 지원한다.
  • RecipeTitle(), RecipeMenu(), RecipeListItem(...)
    • 화면을 작은 컴포넌트로 분리해서 관리한다.
    • 나중에 수정할 때 한 파일씩 책임이 분명해진다.

4) _buildRecipeAppBar() 메서드 역할

  • 상단 앱바 UI를 따로 빼서 재사용성과 가독성을 높인다.
  • CupertinoIcons.heart를 사용해서 iOS 느낌의 하트 아이콘을 넣었다.
  • actions는 앱바 오른쪽 영역에 위젯들을 배치하는 옵션이다.
  • SizedBox(width: 15)는 아이콘 오른쪽 끝 여백을 주기 위한 빈 공간이다.

5) 알아둘 점

  • actionsList<Widget> 타입이므로 여러 아이콘이나 버튼을 순서대로 넣을 수 있다.
  • 현재 앱바에는 title이 없어서 비어 있는 상단 바처럼 보인다.
  • ListView를 선택한 것은 이 실습에서 중요한 포인트다. 카드가 늘어나도 레이아웃이 깨지지 않는다.

4️⃣ recipe_title.dart

1) 전체 코드

  • 파일 경로 : lib/components/recipe_title.dart
import 'package:flutter/material.dart'; class RecipeTitle extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 20), child: Text( "Recipes", style: TextStyle(fontSize: 30), ), ); } }

2) 역할 정리

  • 화면 상단의 큰 제목 텍스트를 담당하는 컴포넌트다.
  • 단순한 코드라도 별도 위젯으로 분리하면 페이지 파일이 더 읽기 쉬워진다.

3) 코드 포인트

  • Padding
    • 위쪽에만 20의 여백을 준다.
    • EdgeInsets.only(top: 20)은 특정 방향만 선택해서 여백을 줄 때 사용한다.
  • Text("Recipes")
    • 화면 제목을 표시한다.
  • style: TextStyle(fontSize: 30)
    • 글자 크기를 30으로 키워서 헤더 역할을 명확히 한다.

4) 이 파일에서 배우는 점

  • Flutter에서는 매우 작은 UI 조각도 별도 위젯으로 뺄 수 있다.
  • 이런 분리는 코드 길이보다 책임 분리에 더 큰 의미가 있다.

5️⃣ recipe_menu.dart

1) 전체 코드

  • 파일 경로 : lib/components/recipe_menu.dart
import 'package:flutter/material.dart'; class RecipeMenu extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 20), child: Row( children: [ _buildMenuItem(Icons.food_bank, "ALL"), SizedBox(width: 25), _buildMenuItem(Icons.emoji_food_beverage, "Coffee"), SizedBox(width: 25), _buildMenuItem(Icons.fastfood, "Burger"), SizedBox(width: 25), _buildMenuItem(Icons.local_pizza, "Pizza"), ], ), ); } } Widget _buildMenuItem(IconData mIcon, String text) { return Container( width: 60, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), border: Border.all( color: Colors.black12, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( mIcon, color: Colors.redAccent, size: 30, ), SizedBox(height: 5), Text( text, style: TextStyle(color: Colors.black87), ), ], ), ); }

2) 역할 정리

  • 레시피 카테고리 메뉴를 가로로 보여주는 컴포넌트다.
  • 메뉴 아이템 UI를 _buildMenuItem() 함수로 공통화해서 반복 코드를 줄였다.

3) 코드 포인트

  • Row
    • 메뉴 카드들을 가로 방향으로 배치한다.
  • SizedBox(width: 25)
    • 메뉴 카드 사이 간격을 일정하게 맞춘다.
  • _buildMenuItem(아이콘, 문자열)
    • 같은 모양의 메뉴 박스를 재사용 가능하게 만드는 헬퍼 함수다.
    • 이 패턴은 반복되는 UI를 빠르게 정리할 때 유용하다.
  • Container(width: 60, height: 80)
    • 메뉴 아이템의 크기를 고정한다.
  • decoration: BoxDecoration(...)
    • 배경, 테두리, 모서리 둥글기 같은 시각 스타일을 설정한다.
  • borderRadius: BorderRadius.circular(30)
    • 메뉴 박스를 둥근 카드처럼 보이게 만든다.
  • Border.all(color: Colors.black12)
    • 연한 테두리를 넣어 영역을 구분한다.
  • Column(mainAxisAlignment: MainAxisAlignment.center)
    • 아이콘과 텍스트를 세로로 쌓고, 중앙 정렬한다.
  • Icon(mIcon, color: Colors.redAccent, size: 30)
    • 전달받은 아이콘 데이터를 실제 아이콘 UI로 렌더링한다.
  • Text(text)
    • 메뉴 이름을 표시한다.

4) 이 파일에서 배우는 점

  • 반복되는 위젯 패턴은 함수나 별도 위젯으로 추출하면 관리가 훨씬 쉬워진다.
  • Container + BoxDecoration 조합은 Flutter에서 카드형 UI를 만들 때 매우 자주 등장한다.

5) 주의할 점

  • 현재 Row는 가로 스크롤이 없고 각 항목의 너비가 고정되어 있다.
  • 화면 폭이 더 좁은 기기에서는 메뉴가 빡빡해질 수 있다.
  • 실무에서는 SingleChildScrollView(scrollDirection: Axis.horizontal)이나 Wrap을 검토하기도 한다.

6️⃣ recipe_list_item.dart

1) 전체 코드

  • 파일 경로 : lib/components/recipe_list_item.dart
import 'package:flutter/material.dart'; class RecipeListItem extends StatelessWidget { final String imageName; final String title; const RecipeListItem(this.imageName, this.title); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 2 / 1, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.asset( "assets/images/$imageName.jpeg", fit: BoxFit.cover, ), ), ), SizedBox(height: 10), Text( title, style: TextStyle(fontSize: 20), ), Text( "Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back.", style: TextStyle( color: Colors.grey, fontSize: 12, ), ), ], ), ); } }

2) 역할 정리

  • 레시피 카드 1개를 표현하는 재사용 컴포넌트다.
  • 이미지, 제목, 설명 문장을 하나의 묶음으로 관리한다.
  • imageNametitle만 바꾸면 같은 UI 구조로 여러 카드 생성이 가능하다.

3) 코드 포인트

  • final String imageName; final String title;
    • 외부에서 전달받을 데이터를 필드로 선언했다.
    • final이므로 생성 후 값이 바뀌지 않는다.
  • const RecipeListItem(this.imageName, this.title);
    • 생성자다.
    • 위치 인자 방식으로 값을 받는다.
    • const 생성자는 불변 위젯을 더 효율적으로 다룰 수 있게 해 준다.
  • Padding(EdgeInsets.symmetric(vertical: 20))
    • 카드 위아래 여백을 준다.
  • Column(crossAxisAlignment: CrossAxisAlignment.start)
    • 이미지, 제목, 설명을 세로로 쌓고 왼쪽 정렬한다.
  • AspectRatio(aspectRatio: 2 / 1)
    • 카드 이미지 비율을 2:1로 고정한다.
    • 기기 폭이 달라도 비율이 유지되어 UI가 안정적으로 보인다.
  • ClipRRect(borderRadius: BorderRadius.circular(20))
    • 자식 위젯을 둥근 사각형 형태로 잘라낸다.
    • 여기서는 이미지 모서리를 둥글게 만드는 역할이다.
  • Image.asset("assets/images/$imageName.jpeg")
    • 문자열 보간을 사용해 이미지 경로를 동적으로 만든다.
    • 예를 들어 imageNamecoffeeassets/images/coffee.jpeg를 읽는다.
  • fit: BoxFit.cover
    • 이미지가 비율을 유지한 채 영역을 꽉 채우도록 한다.
    • 일부 이미지가 잘릴 수 있지만, 카드형 썸네일 UI에서는 자주 쓰는 선택이다.
  • 두 번째 Text
    • 설명 문장도 title 값을 재사용해서 동적으로 만든다.

4) 이 파일에서 배우는 점

  • 재사용 가능한 카드 컴포넌트는 Flutter UI 설계의 기본 단위다.
  • 문자열 보간, 생성자 인자, 레이아웃 위젯을 함께 사용하는 연습이 된다.
  • AspectRatioClipRRect를 조합하면 반응형 카드형 UI를 쉽게 만들 수 있다.

5) 참고할 점

  • 현재 title 값이 Made Coffee, Made Burger처럼 들어가서 설명 문장도 그대로 연결된다.
  • 그래서 문장이 Have you ever made your own Made Coffee?처럼 약간 어색해질 수 있다.
  • 더 자연스럽게 만들려면 titledescription용 이름을 분리하거나, title을 Coffee처럼 더 간단히 전달하는 방식도 가능하다.

7️⃣ 코드 실행 흐름 정리

1) 앱 시작

  • main()이 실행된다.
  • runApp(MyApp())이 호출되면서 Flutter가 루트 위젯을 화면에 붙인다.

2) 앱 전역 설정 적용

  • MyApp.build()가 실행된다.
  • MaterialApp이 생성된다.
  • 디버그 배너 숨김, 기본 폰트 설정, 첫 화면 설정이 적용된다.

3) 첫 화면 렌더링

  • home으로 지정된 RecipePage()가 화면에 올라간다.
  • RecipePage.build()가 호출된다.

4) 페이지 골격 생성

  • Scaffold가 앱바와 본문 구조를 만든다.
  • _buildRecipeAppBar()가 호출되어 상단 바가 만들어진다.

5) 본문 구성

  • Padding이 좌우 여백을 만든다.
  • ListView가 세로 스크롤 가능한 레이아웃을 만든다.
  • RecipeTitle, RecipeMenu, 여러 개의 RecipeListItem이 순서대로 배치된다.

6) 개별 컴포넌트 렌더링

  • RecipeTitle이 제목을 그린다.
  • RecipeMenu가 메뉴 카드를 가로로 그린다.
  • RecipeListItem 각각이 이미지와 텍스트를 포함한 카드 UI를 그린다.

8️⃣ 이 실습에 나온 주요 위젯과 옵션 정리

1) MaterialApp

  • 앱 전체의 최상위 Material 구조를 담당한다.
  • 자주 쓰는 옵션
    • home: 첫 화면 지정
    • theme: 공통 테마 설정
    • darkTheme: 다크 테마 설정
    • themeMode: 시스템/라이트/다크 모드 선택
    • routes: 이름 기반 라우팅 등록
    • debugShowCheckedModeBanner: DEBUG 배너 표시 여부

2) ThemeData

  • 앱 전체 스타일을 묶어서 관리한다.
  • 현재 실습에서는 fontFamily만 사용한다.
  • 자주 쓰는 옵션
    • fontFamily: 기본 폰트 설정
    • primaryColor: 주 색상
    • scaffoldBackgroundColor: 기본 배경색
    • appBarTheme: 앱바 공통 스타일
    • textTheme: 텍스트 공통 스타일

3) Scaffold

  • 한 화면의 기본 골격을 만든다.
  • 자주 쓰는 옵션
    • appBar: 상단 바
    • body: 본문
    • floatingActionButton: 우하단 버튼
    • bottomNavigationBar: 하단 탭 바
    • drawer: 왼쪽 메뉴 패널
    • backgroundColor: 배경색

4) AppBar

  • 페이지 상단 바를 만든다.
  • 자주 쓰는 옵션
    • title: 중앙 또는 시작 지점 제목
    • leading: 왼쪽 아이콘 또는 버튼
    • actions: 오른쪽 위젯 목록
    • backgroundColor: 앱바 배경색
    • elevation: 그림자 깊이
    • centerTitle: 제목 중앙 정렬 여부

5) ListView

  • 여러 자식 위젯을 세로 또는 가로로 스크롤 가능하게 배치한다.
  • 현재 실습에서는 children 생성자를 사용한다.
  • 자주 쓰는 옵션
    • children: 직접 나열한 자식 목록
    • scrollDirection: 스크롤 방향
    • padding: 내부 여백
    • physics: 스크롤 동작 설정
    • shrinkWrap: 필요한 크기만 차지할지 여부
  • 주의해야 할 점
    • 스크롤 방향(main axis)으로는 최대한 확장하려고 함
    • 부모가 해당 방향에 대한 크기 제약을 주지 않으면 에러 발생
    • 반대 방향(cross axis)은 부모의 크기를 따름
    • 따라서 Column 같은 위젯 안에서 사용할 경우 반드시 높이를 제한해야 함 (Expanded, SizedBox 등 사용)

6) Padding

  • 자식 바깥쪽 여백을 준다.
  • 자주 쓰는 옵션
    • padding: EdgeInsets로 여백 지정
    • child: 감쌀 자식 위젯
  • 자주 쓰는 EdgeInsets
    • EdgeInsets.all(x): 사방 동일
    • EdgeInsets.symmetric(horizontal: x, vertical: y): 수평/수직 분리
    • EdgeInsets.only(...): 특정 방향만 지정

7) Row

  • 자식들을 가로 방향으로 배치한다.
  • 자주 쓰는 옵션
    • children: 자식 목록
    • mainAxisAlignment: 가로축 정렬 방식
    • crossAxisAlignment: 세로축 정렬 방식
    • mainAxisSize: 주축에서 차지할 크기 방식

8) Column

  • 자식들을 세로 방향으로 배치한다.
  • 자주 쓰는 옵션
    • children: 자식 목록
    • mainAxisAlignment: 세로축 정렬 방식
    • crossAxisAlignment: 가로축 정렬 방식
    • mainAxisSize: 주축 크기 사용 방식

9) SizedBox

  • 고정된 간격 또는 고정 크기 박스를 만든다.
  • 자주 쓰는 옵션
    • width: 가로 간격 또는 가로 크기
    • height: 세로 간격 또는 세로 크기
    • child: 내부 자식 위젯

10) Icon

  • 아이콘 데이터를 실제 화면 아이콘으로 출력한다.
  • 자주 쓰는 옵션
    • size: 아이콘 크기
    • color: 아이콘 색상
    • semanticLabel: 접근성 설명
  • 이 실습에서는 Material 아이콘과 Cupertino 아이콘을 함께 사용했다.

11) Container

  • 크기, 여백, 배경, 테두리 같은 시각적 박스 역할을 한다.
  • 자주 쓰는 옵션
    • width, height: 크기 지정
    • padding, margin: 내부/외부 여백
    • alignment: 내부 자식 정렬
    • decoration: 배경, 테두리, radius 설정
    • child: 내부 자식

12) BoxDecoration

  • Container의 외형을 꾸민다.
  • 자주 쓰는 옵션
    • color: 배경색
    • border: 테두리
    • borderRadius: 둥근 모서리
    • boxShadow: 그림자
    • image: 배경 이미지

13) BorderRadius.circular()

  • 네 모서리를 같은 값으로 둥글게 만든다.
  • 버튼, 카드, 이미지 모서리를 부드럽게 보이게 할 때 자주 사용한다.

14) Border.all()

  • 사방에 동일한 테두리를 적용한다.
  • 자주 쓰는 옵션
    • color: 테두리 색
    • width: 테두리 두께

15) AspectRatio

  • 자식 위젯의 가로세로 비율을 강제로 유지한다.
  • 자주 쓰는 옵션
    • aspectRatio: 가로/세로 비율
    • child: 내부 자식
    • 현재 실습에서는 2 / 1이라서 가로가 세로보다 2배 긴 카드 이미지가 만들어진다.

16) ClipRRect

  • 자식 위젯을 둥근 직사각형 형태로 잘라낸다.
  • 자주 쓰는 옵션
    • borderRadius: 둥근 정도
    • child: 잘라낼 대상 위젯
  • 이미지 모서리를 둥글게 만들 때 자주 사용한다.

17) Image.asset

  • 프로젝트 내부 에셋 이미지를 화면에 표시한다.
  • 자주 쓰는 옵션
    • fit: 공간에 맞추는 방식
    • width, height: 크기 지정
    • alignment: 정렬 기준
    • repeat: 반복 여부
  • 자주 쓰는 fit
    • BoxFit.cover: 영역 꽉 채움, 일부 잘릴 수 있음
    • BoxFit.contain: 이미지 전체 보이게 함, 빈 공간 생길 수 있음
    • BoxFit.fill: 강제로 꽉 채움, 비율 깨질 수 있음
    • BoxFit.fitWidth: 너비 기준 맞춤
    • BoxFit.fitHeight: 높이 기준 맞춤

18) Text

  • 문자열을 화면에 보여주는 기본 위젯이다.
  • 자주 쓰는 옵션
    • style: 폰트 크기, 색상, 두께
    • textAlign: 정렬
    • maxLines: 최대 줄 수
    • overflow: 넘칠 때 처리 방식
    • softWrap: 줄바꿈 여부

9️⃣ 이 실습에서 꼭 알아야 하는 중요한 내용

1) StatelessWidget을 여러 개로 나눈 이유

  • 이 실습의 모든 화면 조각은 StatelessWidget이다.
  • 지금 화면은 사용자 입력에 따라 내부 상태가 바뀌지 않기 때문에 상태 관리가 필요 없다.
  • 대신 역할별로 파일을 분리해서 읽기 쉽고 유지보수하기 쉬운 구조를 만든다.

2) 화면을 컴포넌트로 쪼개는 습관

  • RecipePage에 모든 코드를 몰아넣지 않고 제목, 메뉴, 카드 항목을 나눈 점이 중요하다.
  • Flutter는 위젯 트리 구조라서 화면을 작게 쪼갤수록 코드 이해와 재사용이 쉬워진다.

3) pubspec.yaml 설정과 실제 코드 연결

  • Image.asset("assets/images/...")가 동작하려면 pubspec.yamlassets/images/ 등록이 필요하다.
  • ThemeData(fontFamily: "PatuaOne")가 동작하려면 fonts 설정에 PatuaOne-Regular.ttf 등록이 필요하다.
  • 현재 프로젝트에는 아래 설정이 들어 있다.
flutter: uses-material-design: true assets: - assets/images/ fonts: - family: PatuaOne fonts: - asset: assets/fonts/PatuaOne-Regular.ttf

4) Material과 Cupertino를 섞어 쓰는 방식

  • 앱 전체는 MaterialApp 기반이다.
  • 하지만 앱바의 하트 아이콘은 CupertinoIcons.heart를 사용했다.
  • Flutter에서는 이렇게 Material 스타일 앱 안에서도 필요한 부분에 Cupertino 요소를 섞을 수 있다.

5) ListView를 선택한 이유

  • 이 화면은 제목, 메뉴, 카드 목록이 세로로 길어진다.
  • 이런 구조를 Column으로만 만들면 항목이 많아졌을 때 overflow가 날 수 있다.
  • ListView는 스크롤이 가능해서 실제 목록 화면에 더 적합하다.

6) const 생성자의 의미

  • RecipeListItem 생성자는 const로 선언되어 있다.
  • 불변 위젯을 컴파일 타임 상수로 취급할 수 있을 때 성능과 안정성 측면에서 유리하다.
  • 실습 단계에서는 "이 위젯은 상태 없이 고정된 구조다"라는 의미로 이해해도 좋다.

7) 문자열 보간

  • "assets/images/$imageName.jpeg"
  • "Have you ever made your own $title?..."
  • Dart에서는 $변수명 형태로 문자열 안에 값을 쉽게 삽입할 수 있다.
  • Flutter UI 코드에서 매우 자주 쓰인다.

🔟 실습 코드 개선 아이디어

  • 메뉴를 눌렀을 때 선택 상태가 바뀌도록 StatefulWidget 또는 상태 관리 적용해 보기
  • RecipeMenu를 가로 스크롤 가능하게 바꿔서 작은 화면 대응하기
  • RecipeListItem에 탭 이벤트를 추가해서 상세 페이지 이동 만들기
  • ListView.builder로 바꿔서 데이터 리스트 기반으로 카드 생성하기
  • 모델 클래스를 만들어 title, imageName, description을 구조적으로 관리하기
  • AppBar에 제목이나 검색 아이콘을 넣어 실제 앱 화면처럼 확장하기
  • TextTheme, ColorScheme까지 사용해서 테마를 더 체계적으로 관리해 보기
Share article