[Flutter] 4. Recipe App v1 : Prototype

문정준's avatar
May 26, 2025
[Flutter] 4. Recipe App v1 : Prototype
 

1. Google Fonts 라이브러리 설치

notion image
 
 

2. Method Refactoring

  • 재사용하고 싶은 위젯들을 해당 클래스 내에서 사용할 수 있도록 Refactoring
    • 위젯 우클릭 → Refactor → Extract Method 클릭 후 변경할 메서드명 입력
    • 앞에 _(언더바) 입력 시 private 설정
notion image
 

3. Class Refactoring (Component)

  • 재사용하고 싶은 위젯들을 모아서 하나의 클래스로 Refactoring 후 사용 가능
    • 위젯 우클릭 → Refactor → Extract Flutter Widget 클릭 후 클래스명 입력
    • 이와 같이 Refactor를 적용한 클래스를 컴포넌트(Component)라고 함
notion image
 

4. 파일 분리

  • Convention : 소문자 + 언더바
notion image
 

5. 코드 작성

  • main.dart
    • 기본 구조 : HomePage 내부에서 화면 작성
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } // 기본 구조 : 추후 작성하고 옮길 것 class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Placeholder(), ); } }
 
  • homepage.dart
    • 페이지를 구분하는 파일을 따로 만드는 것이 좋음
    • main에서는 각종 이벤트 및 기초 설정을 담당하는 코드들이 많음
    • 스타일을 지정하는 코드가 있으면 가독성을 해침

Widgets

  • ListView : 스크롤 바가 있는 Column 또는 Row
    • 기본 방향은 Column이나, Row로 바꿀 수 있음
  • AppBar : 각종 메뉴 또는 아이콘들을 표시할 수 있는 화면 상단에 표시되는 Navigation Bar
    • 내부의 Icon들은 직접 넣어야 함
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_prac_recipe/component/list_item.dart'; import 'package:flutter_prac_recipe/component/menu.dart'; import 'package:flutter_prac_recipe/component/my_title.dart'; class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ListView( children: [ _appBar(), SizedBox(height: 25), MyTitle("Recipes"), SizedBox(height: 25), Menu(), SizedBox(height: 25), ListItem("coffee"), SizedBox(height: 25), ListItem("burger"), SizedBox(height: 25), ListItem("pizza"), ], ), ), ); } AppBar _appBar() { return AppBar( actions: [ Icon(Icons.search), SizedBox(width: 16), Icon( CupertinoIcons.heart, color: Colors.redAccent, ), SizedBox(width: 16), ], ); } }
 
  • menu.dart
    • 재사용이 가능한 위젯들을 모아서 하나의 클래스로 만든 컴포넌트 (Component)
    • 자바와 동일하게 컴포넌트 내부에 컴포넌트를 포함할 수 있음
    • 사용 시 해당 클래스를 import 해야 함
 

Widgets

  • Container : 특정 방향(Column, Row)으로 정렬된 내부 item들을 담는 상자
    • 위젯들을 하나로 묶기 위해 사용 : 클래스로 선언하여 재사용 가능
    • 매직 키 (Alt + Enter) → wrap with widget 선택 후 이름 변경으로 쉽게 생성 가능
import 'package:flutter/material.dart'; import 'package:flutter_prac_recipe/component/menu_item.dart'; class Menu extends StatelessWidget { @override Widget build(BuildContext context) { return Container( child: Row( children: [ MenuItem(Icons.food_bank, "ALL"), SizedBox(width: 25), MenuItem(Icons.emoji_food_beverage, "Coffee"), SizedBox(width: 25), MenuItem(Icons.fastfood, "Burger"), SizedBox(width: 25), MenuItem(Icons.local_pizza, "Pizza"), ], ), ); } }
 
  • menu_item.dart
    • 메뉴 안에 들어갈 아이템을 모아서 하나의 클래스로 재사용할 수 있는 컴포넌트
    • Container는 직접 크기 지정 가능
    • 클래스 내부에 상태를 만들어서 생성자로 주입 가능, 주입된 상태를 내부에서 활용
 

Widgets

  • Icon : 아이콘을 출력하는 위젯
  • Container
    • decoration : 컨테이너의 서식 지정 : BoxDecoration 내부에서 지정
      • border : 외곽선 지정
      • borderRadius : 둥근 모서리 지정
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class MenuItem extends StatelessWidget { IconData mIcon; String mText; MenuItem(this.mIcon, this.mText); @override Widget build(BuildContext context) { return Container( width: 60, height: 80, decoration: BoxDecoration( border: Border.all(color: Colors.black12), borderRadius: BorderRadius.circular(30), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( mIcon, size: 30, color: Colors.redAccent, ), Text( mText, style: GoogleFonts.patuaOne(), ), ], ), ); } }
 
  • my_title.dart
    • 제목과 같은 범용성이 높은 위젯은 따로 만들어두는 것이 재사용 및 관리에 용이
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class MyTitle extends StatelessWidget { String title; MyTitle(this.title); @override Widget build(BuildContext context) { return Text( title, style: GoogleFonts.patuaOne(fontSize: 30), ); } }
 
  • list_item.dart
    • 이미지, 제목, 내용을 하나로 묶어서 하나의 컴포넌트로 관리
    • 이미지는 assets 폴더 내부의 이미지 경로를 받아서 사용
    • 이미지의 배율을 조절하여 사진이 잘리지 않도록 배치
 

Widgets

  • AspectRatio : 내부 Item의 비율 조절
    • aspectRatio : double 값을 받아서 사용하나, n / m의 형식으로 사용할 수 있음 (n : m)
  • ClipRRect : 내부 Item을 모서리가 둥근 사각형에 담음
  • Image.asset : URL을 통해 이미지를 불러옴
    • fit : 이미지 맞춤 설정
      • BoxFit.cover : 늘리기
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class ListItem extends StatelessWidget { String title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 16 / 9, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.asset( "$title.jpeg", fit: BoxFit.cover, ), ), ), Text( title, style: GoogleFonts.patuaOne(fontSize: 25), ), Text( "Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back.", style: GoogleFonts.patuaOne(), ), ], ); } }
 

6. Result

  • 화면 스크롤 가능
notion image
 
notion image
Share article

sxias