logoStephen's 기술블로그

포스트 검색

제목, 태그로 포스트를 검색해보세요

#004 #레시피 앱 UI 만들기

#004 #레시피 앱 UI 만들기
Flutter
성훈 김
2024년 4월 20일
목차

✔️ 소스코드 flutter_recipe 프로젝트

flutter-book
flutter-coderUpdated May 21, 2025
notion image
 
 
 

👈목표 결과물

 
 

✔️ 경로 설정 후 Pub Get 클릭

notion image
 
 

✔️ Main.dart 기본 코딩

notion image
내부를 다 지우고 stateless 위젯을 생성한다.
내부를 다 지우고 stateless 위젯을 생성한다.
 
 

✔️ Scaffold 구조 만들고 AppBar, Column추가

notion image
notion image
비어있는 AppBar랑 도화지가 생겼다.
비어있는 AppBar랑 도화지가 생겼다.
 
 

✔️ 컬럼의 자식요소로 Container 생성

notion image
body영역에 Container를 다섯개 생성
body영역에 Container를 다섯개 생성
플러터에서 Container는 HTML의 DIV태그와 유사하다. 
빈 박스를 만들고 그 내부를 디자인하거나 다른 위젯을 담을 때 사용한다.
플러터에서 Container는 HTML의 DIV태그와 유사하다. 빈 박스를 만들고 그 내부를 디자인하거나 다른 위젯을 담을 때 사용한다.
 
 

Container요소에 Extract Widget으로 추출

notion image
ctrl + alt + w 로 위젯으로 추출한뒤에 recipe_title 파일을 만들어서 components 폴더에 넣는다. 

이러한 행위를 component화 한다고 말할 수 있고, 이렇게 만든것은 재사용 할 수 있다.
ctrl + alt + w 로 위젯으로 추출한뒤에 recipe_title 파일을 만들어서 components 폴더에 넣는다. 이러한 행위를 component화 한다고 말할 수 있고, 이렇게 만든것은 재사용 할 수 있다.
notion image
만들어진 위젯을 recipe_title파일로 빼놓았다.
만들어진 위젯을 recipe_title파일로 빼놓았다.
 

recipe_menu도 마찬가지

notion image
 
 

recipe_list_item

Java
import 'package:flutter/cupertino.dart';

class RecipeListItem extends StatelessWidget {
  final String imageName;
  final String title;

  RecipeListItem(this.imageName, this.title);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
마지막 Container요소 역시 위젯으로 따로 빼서 RecipeListItem으로 명명한다.
마지막 Container요소 역시 위젯으로 따로 빼서 RecipeListItem으로 명명한다.
그리고 내부에 final요소로 imageName 과 title을 선언해서 생성자도 같이 만들어 준다. 

굳이 final로 선언한 이유는 RecipeListItem은 생성이 될때 반드시 imageName과 title이 필요한 위젯이라는 것을 명시하기 위함이고, 이는 향후 플러터 프레임워크에서 위젯들을 재생성 할 때 final로 불변요소의 상태를 추적하지 않기 때문에, 성능이 더 향상 될 수 있다.
그리고 내부에 final요소로 imageName title을 선언해서 생성자도 같이 만들어 준다. 굳이 final로 선언한 이유는 RecipeListItem은 생성이 될때 반드시 imageName과 title이 필요한 위젯이라는 것을 명시하기 위함이고, 이는 향후 플러터 프레임워크에서 위젯들을 재생성 할 때 final로 불변요소의 상태를 추적하지 않기 때문에, 성능이 더 향상 될 수 있다.
 
 

recipe_page 전체코드

Java
import 'package:flutter/material.dart';

import 'components/recipe_menu.dart';
import 'components/recipe_title.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: Colors.white,
        appBar: _buildRecipeAppBar(),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              RecipeTitle(),
              RecipeMenu(),
              RecipeListItem("coffee", "Made Coffee"),
              RecipeListItem("burger", "Made Burger"),
              RecipeListItem("pizza", "Made Pizza"),
            ],
          ),
        ),
      ),
    );
  }

  AppBar _buildRecipeAppBar() {
    return AppBar();
  }
}

class RecipeListItem extends StatelessWidget {
  final String imageName;
  final String title;

  RecipeListItem(this.imageName, this.title);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
 
 

✔️ AppBar

notion image
AppBar를 사용할 때 leading요소의 위치와 action, 그리고 title의 위치를 숙지 한다.
AppBar를 사용할 때 leading요소의 위치와 action, 그리고 title의 위치를 숙지 한다.
 
 

AppBar 구현

Java
  AppBar _buildRecipeAppBar() {
    return AppBar(
      backgroundColor: Colors.white, // appbar 배경색
      elevation: 1.0,  // appbar의 그림자 효과 조정
      actions: [
        Icon(
          CupertinoIcons.search, // 쿠퍼티노 아이콘 검색 모양
          color: Colors.black,
        ),
        SizedBox(width: 15),
        Icon(
          CupertinoIcons.heart, // 쿠퍼티노 아이콘 하트 모양
          color: Colors.redAccent,
        ),
        SizedBox(width: 15),
      ],
    );
  }
notion image
appbar에 적용된 모습
appbar에 적용된 모습
 
 

✔️ RecipeTitle 구현

notion image
Build메소드 안에 “Recipes” 텍스트를 넣고, styling을 해준다.
Build메소드 안에 “Recipes” 텍스트를 넣고, styling을 해준다.
notion image
Padding은 항상 구현후 감싸주면 좀 편리하다. 
해당 위젯에 alt + enter를 치면 해당 메뉴가 나오는데 Wrap with Padding을 선택한다.
Padding은 항상 구현후 감싸주면 좀 편리하다. 해당 위젯에 alt + enter를 치면 해당 메뉴가 나오는데 Wrap with Padding을 선택한다.
 

RecipeTitle 전체 코드

Java
class RecipeTitle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 20),
      child: Text(
        "Recipes",
        style: TextStyle(fontSize: 30),
      ),
    );
  }
}
 
 
notion image
완성화면
완성화면
 
 

✔️ Theme 적용하기

notion image
MaterialApp 구조에 theme 선택적매개변수에 ThemeData(fontFamily: “PatuaOne”) 을 지정해준다.
MaterialApp 구조에 theme 선택적매개변수에 ThemeData(fontFamily: “PatuaOne”) 을 지정해준다.
notion image
결과
결과
 

✔️ Container와 SizedBox의 차이점?!

container 와 sizedBox는 빈 박스 위젯으로 비슷하지만, container는 내부에 decoration을 할 수 있어서 여러가지 꾸밀 수 있다. 하지만 sizedBox는 마진을 줄 때 주로 사용한다.

 

➖ Container 위젯의 특징

notion image
내부 요소가 없으면 화면 크기만큼 확장한다.
내부 요소가 없으면 화면 크기만큼 확장한다.
notion image
내부요소가 있으면 그 크기만큼 줄어든다.
내부요소가 있으면 그 크기만큼 줄어든다.
 
 

recipe_menu.dart 전체 코드

SQL
import 'package:flutter/material.dart';

class RecipeMenu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return 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),
          ),
        ],
      ),
    );
  }
}
notion image
recipe_menu 코드 작성 결과
recipe_menu 코드 작성 결과
 
 

✔️ 재사용이 가능한 레시피 리스트 아이템 만들기

같은 구조를 가진 여러아이템이 있다면 이들을 생성자로 매개변수를 받게 하여, 같은 구조의 다른 데이터를 가진 것을 표현할 수 있다. 이렇게 컴포넌트화 하면 다음에 재사용이 가능하다 .
 
 

✔️ RecipeListItem 생성자로 생성

notion image
RecipeListItem에 imageName이랑 title을 받아서 다른 값의 같은 구조를 가진 아이템을 만들 수 있다. 

칼럼에 children에  Image.asset 메소드에 사진을 변수로 넣으니 사진 세개가 출력된다.
RecipeListItem에 imageName이랑 title을 받아서 다른 값의 같은 구조를 가진 아이템을 만들 수 있다. 칼럼에 children에 Image.asset 메소드에 사진을 변수로 넣으니 사진 세개가 출력된다.
notion image
 
 
 
 
하지만 기존 칼럼은 화면만큼의 크기를 가진 요소에  사진 세개가 담기면서 , 아래에 오버플로우 에러가 나타난다.
하지만 기존 칼럼은 화면만큼의 크기를 가진 요소에 사진 세개가 담기면서 , 아래에 오버플로우 에러가 나타난다.
이럴 때는 모든 자식요소를 세로 크기만큼 포함해 줄 수 있는 ListView를 사용한다.
이럴 때는 모든 자식요소를 세로 크기만큼 포함해 줄 수 있는 ListView를 사용한다.
 
 

✔️ 세로 스크롤 달기 → ListView

ListView는 일반적으로 사용되는 스크롤 위젯이다

notion image
Scaffold 구조의 body 영역에 ListView를 만들고 그 안에 컴포넌트들을 넣는다.
Scaffold 구조의 body 영역에 ListView를 만들고 그 안에 컴포넌트들을 넣는다.
notion image
 
 
 
 
이렇게 표현이 된다.
이렇게 표현이 된다.
 

✔️ AspectRatio로 이미지 비율 정하기

AsepectRatio는 비율로 자식의 크기를 조정하는 위젯이다. 이미지를 화면에 표시할 때는 비율로 표시하는 것이 좋다.
 

AspectRatio 적용

notion image
이미지를 가로 2, 세로 1의 비율로 표시하게 한다. 
Image 위젯에 alt + enter 로 Widget으로 감싸고 AspectRatio로 변경해주면 된다.
이미지를 가로 2, 세로 1의 비율로 표시하게 한다. Image 위젯에 alt + enter 로 Widget으로 감싸고 AspectRatio로 변경해주면 된다.
notion image
2대 1의 비율이 된 사진
2대 1의 비율이 된 사진
 
 

✔️ 둥근모서리 표현하기

ClipRRect클래스를 사용해서 사진의 모서리에 둥근효과를 줄 수 있다.
 

clipRRect사용

notion image
Image 위젯에 alt + enter로 위젯으로 감싸준뒤 ClipRRect를 적용하면 된다.
Image 위젯에 alt + enter로 위젯으로 감싸준뒤 ClipRRect를 적용하면 된다.
notion image
둥근 모서리 완
둥근 모서리 완
 
 
 
notion image
 
 
 
 

완성!!