Flutter

Flutter Day 4-2: Flutter recipe app (2)

@leem 2025. 1. 7. 17:36

주요 위젯

Container

  • 정의: 레이아웃을 구성하는 가장 기본적인 위젯.
  • 특징: 크기, 색상, 테두리, 모서리 둥글기 등 다양한 스타일을 설정할 수 있습니다.
Container(
  width: 100, // 가로 길이
  height: 100, // 세로 길이
  decoration: BoxDecoration(
    color: Colors.blue, // 배경 색상
    borderRadius: BorderRadius.circular(10), // 모서리를 둥글게
    border: Border.all(color: Colors.black, width: 2), // 테두리 설정
  ),
);

AppBar

  • 정의: 화면 상단에 고정되는 네비게이션 바입니다.
  • 특징: 제목, 아이콘, 버튼 등을 추가할 수 있습니다.
AppBar(
  title: Text("My App"), // 앱바 제목
  backgroundColor: Colors.blue, // 앱바 배경색
  actions: [ // 앱바 오른쪽에 추가할 아이콘들
    Icon(Icons.search), // 검색 아이콘
    Icon(Icons.more_vert), // 더보기 아이콘
  ],
);

Icon

  • 정의: 작은 크기의 아이콘을 표시하는 데 사용합니다.
  • 특징: 다양한 기본 제공 아이콘을 활용할 수 있습니다.
Icon(
  Icons.home, // 아이콘 종류
  color: Colors.red, // 아이콘 색상
  size: 30, // 아이콘 크기
);

ListView

  • 정의: 스크롤 가능한 리스트를 표시합니다.
  • 특징: 많은 데이터가 있을 때 유용하며, 스크롤이 기본으로 지원됩니다.
ListView(
  children: [ // 리스트 아이템
    ListTile(title: Text("Item 1")), // 리스트 첫 번째 항목
    ListTile(title: Text("Item 2")), // 리스트 두 번째 항목
    ListTile(title: Text("Item 3")), // 리스트 세 번째 항목
  ],
);

레시피 앱

프로젝트 폴더 구조

  • assets/images/: 앱에서 사용할 이미지 저장.
  • assets/fonts/: 앱에서 사용할 폰트 저장.

pubspec.yaml 파일에서 이미지와 폰트를 프로젝트에 등록합니다.

pubspec.yaml 설정

flutter:
  assets:
    - assets/images/
  fonts:
    - family: PatuaOne
      fonts:
        - asset: assets/fonts/PatuaOne-Regular.ttf

 


앱 시작점

import 'package:flutter/material.dart';

// 앱의 시작점
void main() {
  runApp(MyApp()); // MyApp을 실행
}

// MyApp 클래스: 앱 전체의 루트 위젯
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false, // 디버그 배너 제거
      theme: ThemeData(fontFamily: 'PatuaOne'), // 기본 폰트 설정
      home: RecipePage(), // 앱의 첫 번째 화면 설정
    );
  }
}

RecipePage: 앱의 첫 화면

 
class RecipePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold( // 화면의 기본 구조를 잡아주는 위젯
      appBar: _buildRecipeAppBar(), // AppBar 추가
      body: ListView( // 스크롤 가능한 리스트
        padding: EdgeInsets.all(20), // 리스트의 여백
        children: [
          Text("Recipe App", style: TextStyle(fontSize: 24)), // 제목
          SizedBox(height: 20), // 위젯 간 간격
          RecipeMenu(), // 메뉴 위젯 호출
          RecipeListItem('coffee', 'Made Coffee'), // 리스트 아이템
          RecipeListItem('burger', 'Made Burger'), // 리스트 아이템
        ],
      ),
    );
  }

  // AppBar를 빌드하는 메서드
  AppBar _buildRecipeAppBar() {
    return AppBar(
      title: Text("Recipes"), // 앱바 제목
      backgroundColor: Colors.white, // 앱바 배경 색
      iconTheme: IconThemeData(color: Colors.black), // 아이콘 색상
      actions: [
        Icon(Icons.search, color: Colors.black), // 검색 아이콘
        SizedBox(width: 10), // 간격
        Icon(Icons.favorite, color: Colors.red), // 즐겨찾기 아이콘
      ],
    );
  }
}

RecipeMenu: 메뉴 구성

class RecipeMenu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row( // 메뉴를 가로로 정렬
      mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 균등 간격
      children: [
        _buildMenuIcon(Icons.food_bank, "All"), // 메뉴 아이콘 1
        _buildMenuIcon(Icons.coffee, "Coffee"), // 메뉴 아이콘 2
        _buildMenuIcon(Icons.fastfood, "Burger"), // 메뉴 아이콘 3
      ],
    );
  }

  // 개별 메뉴 아이콘을 빌드하는 메서드
  Widget _buildMenuIcon(IconData icon, String text) {
    return Column(
      children: [
        Icon(icon, color: Colors.red, size: 40), // 아이콘
        SizedBox(height: 5), // 간격
        Text(text, style: TextStyle(color: Colors.black)), // 텍스트
      ],
    );
  }
}

RecipeListItem: 레시피 아이템

class RecipeListItem extends StatelessWidget {
  final String imageName; // 이미지 파일 이름
  final String title; // 레시피 제목

  RecipeListItem(this.imageName, this.title); // 생성자

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 10), // 위아래 여백
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start, // 왼쪽 정렬
        children: [
          AspectRatio( // 이미지 비율 조정
            aspectRatio: 2 / 1,
            child: ClipRRect( // 모서리를 둥글게 처리
              borderRadius: BorderRadius.circular(15),
              child: Image.asset(
                'assets/images/$imageName.jpg', // 이미지 경로
                fit: BoxFit.cover, // 이미지를 채우기
              ),
            ),
          ),
          SizedBox(height: 5), // 간격
          Text(title, style: TextStyle(fontSize: 20)), // 레시피 제목
        ],
      ),
    );
  }
}

정리

  1. 기본 위젯
    • Container: 레이아웃과 스타일 설정.
    • AppBar: 상단 네비게이션 바.
    • Icon: 아이콘 표시.
    • ListView: 스크롤 가능한 리스트.
    • Padding: 여백 설정.
  2. 프로젝트 구조
    • main.dart: 앱의 시작점.
    • RecipePage: 레이아웃과 앱바 구성.
    • RecipeMenu: 메뉴 아이콘 표시.
    • RecipeListItem: 리스트 아이템 구성.
  3. 플러터 앱의 핵심
    • StatelessWidgetStatefulWidget을 이해하고, 위젯 트리를 통해 화면을 구성합니다.

전체 코드

main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'recipe_list_item.dart';
import 'recipe_menu.dart';
import 'recipe_title.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(fontFamily: 'PatuaOne'),
      home: RecipePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      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,
      iconTheme: IconThemeData(color: Colors.black),
      actions: [
        Icon(CupertinoIcons.search, color: Colors.black),
        SizedBox(width: 15),
        Icon(CupertinoIcons.heart, color: Colors.red),
        SizedBox(width: 15),
      ],
    );
  }
}

 

recipe_menu.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 20),
      child: Row(
        children: [
          _buildMenuIcon(Icons.food_bank, 'ALL'),
          SizedBox(width: 25),
          _buildMenuIcon(Icons.emoji_food_beverage, 'Coffee'),
          SizedBox(width: 25),
          _buildMenuIcon(Icons.fastfood, 'Burger'),
          SizedBox(width: 25),
          _buildMenuIcon(Icons.local_pizza, 'Pizza'),
        ],
      ),
    );
  }

  Widget _buildMenuIcon(IconData mIcon, String text) {
    return Container(
      width: 60,
      height: 80,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.black12),
        borderRadius: BorderRadius.circular(25),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(mIcon, color: Colors.red),
          SizedBox(height: 5),
          Text(
            text,
            style: TextStyle(color: Colors.black54),
          ),
        ],
      ),
    );
  }
}

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, {super.key});

  @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...'),
        ],
      ),
    );
  }
}

 

recipe_title.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 20),
      child: Text(
        'Recipes',
        style: TextStyle(fontSize: 30),
      ),
    );
  }
}