inblog logo
|
{CODE-RYU};
    Flutter

    [Flutter] Flutter 예제 - Recipe App 만들기

    류재성's avatar
    류재성
    Apr 14, 2024
    [Flutter] Flutter 예제 - Recipe App 만들기
    Contents
    1. 기본 세팅2. main.dart3. AppBar 만들기4. ListView 사용하기5. Container 사용하기5.1. Container 란?6. AspectRatio 사용하기6.1. AspectRatio 란?7. 남은 컴포넌트 분리하기
     

    1. 기본 세팅

    notion image
     
    assets에 폰트와 사진을 넣는다.
     
    pubspec.yaml 설정
    notion image
    notion image
     
    pubspec.yaml 설정 후 pub get 을 누른다.

    2. main.dart

     
    import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold(), ); } }
     
    💡
    ThemeData는 Flutter 앱의 전반적인 테마를 설정하는 데 사용된다. 여기에는 글꼴, 색상, 버튼 스타일 등 앱의 시각적 요소를 정의하는 다양한 속성이 포함된다.
     

    3. AppBar 만들기

     

    3.1. AppBar 란?

     
    💡
    AppBar는 앱의 상단에 위치하는 툴바로, 주로 앱의 제목을 표시하거나 메뉴 버튼, 액션 버튼 등을 포함하는 데 사용한다.
     
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ), ), ); } }
     
    notion image
     

    3.2. 컴포넌트 분리하기

     
    💡
    가독성을 위해 컴포넌트를 분리한다.
     
    main.dart
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
    💡
    _ 는 클래스 내부에서만 사용할 수 있다.
     

    4. ListView 사용하기

     

    4.1. ListView 란?

     
    💡
    ListView는 스크롤 가능한 항목들의 리스트를 구현할 때 사용되는 기본적이고 중요한 위젯이다. 사용자 정의 데이터를 수직으로 배열하여 화면에 표시하는 데 사용되며, 쉬운 구현과 유연한 기능성을 제공한다.
     
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( children: [ Padding( padding: const EdgeInsets.only(top: 20), child: Text( "Recipes", style: TextStyle(fontSize: 30), ), ), ], ), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
     
    notion image
     

    4.2. 컴포넌트 분리하기

     
    main.dart
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.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, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( children: [ RecipeTitle(), ], ), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
    components/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), ), ); } }
     

    5. Container 사용하기

     

    5.1. Container 란?

     
    💡
    Container는 가장 기본적이면서도 유용한 레이아웃 위젯 중 하나이다. 단 하나의 자식 위젯을 포함할 수 있으며, 크기 지정, 색상, 패딩, 마진 등 다양한 스타일링 옵션을 가진다. Container는 위젯의 배치, 크기 조정, 데코레이션(배경색, 테두리 등)을 쉽게 할 수 있게 해주는 역할을 한다.
     
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.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, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( // 스크롤을 만들 때 사용 children: [ RecipeTitle(), // 컨퍼넌트 분리 Row( // 가로 정렬할 때 사용 children: [ Container( width: 60, height: 80, decoration: BoxDecoration( // 컨테이너의 배경을 바꿀 때 사용 borderRadius: BorderRadius.circular(30), border: Border.all(color: Colors.black12), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.food_bank, color: Colors.redAccent, size: 30), SizedBox(height: 5), Text( "ALL", style: TextStyle(color: Colors.black87), ) ], ), ), ], ) ], ), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
    notion image
     

    5.2. 컴포넌트 분리 후 값 변수로 만들기

     
    main.dart
    @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( children: [ RecipeTitle(), recipe_menu() // 컴포넌트 분리 ], ), ), ); }
     
    components/recipe_menu.dart
    import 'package:flutter/material.dart'; class recipe_menu extends StatelessWidget { const recipe_menu({ super.key, }); @override Widget build(BuildContext context) { return Row( children: [ Container( width: 60, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), border: Border.all(color: Colors.black12), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.food_bank, color: Colors.redAccent, size: 30), SizedBox(height: 5), Text( "ALL", style: TextStyle(color: Colors.black87), ) ], ), ), ], ); } }
     
    컴포넌트를 분리한다.
     
    현재 변경될 데이터는 Icons.food_bank, 와 "ALL", 이다. 이 두 데이터를 변수로 만들자.
     
    recipe_menu_item.dart
    import 'package:flutter/material.dart'; class RecipeMenuItem extends StatelessWidget { IconData mIcon ; //아이콘과 text 를 변수로 분리한다. String text ; recipe_menu_item({required this.mIcon,required this.text}); @override Widget build(BuildContext context) { return Row( children: [ 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), ) ], ), ), ], ); } }
     
    💡
    생성자에 클래스명({}) 의 형태를 선택적 생성자라고 한다. 선택적 생성자를 만들면 데이터를 받을 때 키 : 값의 형태로 받을 수 있다. 반드시 받아야할 데이터는 required 를 사용한다.
     
    main.dart
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'components/recipe_menu_item.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, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( children: [ RecipeTitle(), Padding( padding: const EdgeInsets.only(top: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, // Row 는 가로가 메인축, 세로가 크로스축 children: [ recipe_menu_item(mIcon: Icons.food_bank, text: "ALL"), recipe_menu_item( mIcon: Icons.emoji_food_beverage, text: "Coffee"), recipe_menu_item(mIcon: Icons.fastfood, text: "Burger"), recipe_menu_item(mIcon: Icons.local_pizza, text: "Pizza"), ], ), ), ], ), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
    notion image
     
    💡
    컴포넌트를 분리하고 , 변수를 만들어 사용하면 함수만 호출하면 재사용할 수 있다.
     
    notion image
     
     

    6. AspectRatio 사용하기

     

    6.1. AspectRatio 란?

     
    💡
    AspectRatio 위젯은 자식 위젯이 특정 종횡비(aspect ratio)를 유지하도록 설정하는 데 사용된다. 종횡비는 너비 대 높이의 비율로 표현되며, 이를 통해 위젯의 크기를 조절할 수 있다. aspectRatio : 2/1 : 가로 :2 = 세로 :1 를 의미한다.
     
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'components/recipe_menu_item.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, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( children: [ RecipeTitle(), Padding( padding: const EdgeInsets.only(top: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, // Row 는 가로가 메인축, 세로가 크로스축 children: [ recipe_menu_item(mIcon: Icons.food_bank, text: "ALL"), recipe_menu_item( mIcon: Icons.emoji_food_beverage, text: "Coffee"), recipe_menu_item(mIcon: Icons.fastfood, text: "Burger"), recipe_menu_item(mIcon: Icons.local_pizza, text: "Pizza"), ], ), ), Column( children: [ AspectRatio( aspectRatio: 2 / 1, // 사진의 비율을 가로 2 세로 1로 한다. child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.asset( "assets/images/coffee.jpeg", fit: BoxFit.cover, ), ), ), SizedBox(height: 10), Text( "Coffee", style: TextStyle(fontSize: 20), ), Text( "Have you ever made your own Coffee Once you've tried a homemade Coffee, you'll never go back.", style: TextStyle(color: Colors.grey, fontSize: 12), ) ], ) ], ), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
     
    notion image
     
    💡
    Column 의 세로의 디폴트 값은 위쪽이지만, 가로는 센터가 디폴트 값이다. 그래서 크로스축을 start 로 정렬해야 한다.
     
    notion image

    6.2. 컴포넌트 분리하고 변수 만들기

     
    components/recipe_list_item.dart
    import 'package:flutter/material.dart'; class RecipeListItem extends StatelessWidget { const RecipeListItem({ super.key, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 25), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 2 / 1, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.asset( "assets/images/coffee.jpeg", fit: BoxFit.cover, ), ), ), SizedBox(height: 10), Text( "Coffee", style: TextStyle(fontSize: 20), ), Text( "Have you ever made your own Coffee Once you've tried a homemade Coffee, you'll never go back.", style: TextStyle(color: Colors.grey, fontSize: 12), ) ], ), ); }
     
    notion image
     
    클래스에 변수와 생성자를 만든다.
     
    notion image
     
    변수를 변경되어야 하는 곳에 대체한다.
     
    main.dart
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'components/recipe_List_item.dart'; import 'components/recipe_menu_item.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, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: Scaffold( appBar: _buildAppBar(), body: ListView( children: [ RecipeTitle(), Padding( padding: const EdgeInsets.only(top: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, // Row 는 가로가 메인축, 세로가 크로스축 children: [ recipe_menu_item(mIcon: Icons.food_bank, text: "ALL"), recipe_menu_item( mIcon: Icons.emoji_food_beverage, text: "Coffee"), recipe_menu_item(mIcon: Icons.fastfood, text: "Burger"), recipe_menu_item(mIcon: Icons.local_pizza, text: "Pizza"), ], ), ), RecipeListItem(imageName:"coffee" ,title: "Made coffee"), RecipeListItem(imageName:"burger" ,title: "Made burger"), RecipeListItem(imageName:"pizza" ,title: "Made pizza"), ], ), ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
    RecipeListItem 를 호출할 때 변수를 넣으면 재사용할 수 있다.
     
    notion image
     

    7. 남은 컴포넌트 분리하기

     
    main.dart
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:recipe_app/pages/recipe_page.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: "PatuaOne"), // 페이지의 폰트를 설정한다. home: RecipePage(), ); } }
     
    components/recipe_page.dart
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../components/recipe_list.dart'; import '../components/recipe_menu.dart'; import '../components/recipe_title.dart'; class RecipePage extends StatelessWidget { const RecipePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: _buildAppBar(), body: ListView( children: [ RecipeTitle(), RecipeMenu(), RecipeList(), ], ), ); } AppBar _buildAppBar() { return AppBar( elevation: 1.0, actions: [ Icon( CupertinoIcons.search, // 돋보기 아이콘 color: Colors.black, ), SizedBox(width: 15), Icon( CupertinoIcons.heart, // 하트 아이콘 color: Colors.redAccent, ), SizedBox(width: 15), ], ); } }
     
    notion image
    💡
    모든 기능을 분리함으로써 각 페이지는 원하는 기능만 호출하면 된다. 가독성과 유지보수가 매우 편리해졌다.
    Share article

    {CODE-RYU};

    RSS·Powered by Inblog