inblog logo
|
parangdajavous
    Flutter

    Chapter 05. Flutter_Recipe

    김미숙's avatar
    김미숙
    Jun 10, 2025
    Chapter 05. Flutter_Recipe
    Contents
    출처Project 생성SettingPreview기본 구조 - DefaultRecipe App의 화면 구조Layout 잡기만든 위젯들 파일화 + MainPage 위젯 Class화Design

    출처

    만들면서 배우는 플러터 앱 프로그래밍 7가지 모바일 앱 UI 제작 & RiverPod 상태 관리
     

    Project 생성

    notion image

    Setting

    notion image
    formatter: trailing_commas: preserve

    5장에 있는 사진 가져오기

    사진만 복사
    사진만 복사
    assets 폴더 생성 → 사진 붙여넣기
    assets 폴더 생성 → 사진 붙여넣기
    pubspec.yaml 설정 / 주석풀면 띄어쓰기가 하나 더 되있으므로 주의하기
    pubspec.yaml 설정 / 주석풀면 띄어쓰기가 하나 더 되있으므로 주의하기

    라이브러리 가져오기

    notion image
    플러터 관련 모든 라이브러리를 다운 받는 곳
    notion image
    shift+insert+enter → ctrl + v 금지
    shift+insert+enter → ctrl + v 금지
    notion image
    notion image
    현재 이 상태가 문제 없는지 Test
    notion image
    test 파일 삭제
     

    Preview

    ❗
    • AppBar 위젯
    • Conctainer 위젯
    • Icon 위젯
    • ClipRRect 위젯
    • AspectRatio 위젯
    • ListView 위젯
    • Font 변경
     

    기본 구조 - Default

    import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Placeholder(), ); } }
     

    Recipe App의 화면 구조

    notion image
     

    Layout 잡기

    1. MainPage - AppBar

    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Placeholder(), ); } }
    notion image

    AppBar에 Icon 넣기

    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ Icon(Icons.search), Icon(CupertinoIcons.heart)], ), body: Placeholder(), ); } }
    notion image

    AppBar 간격 주기

    SizedBox(width: 16) → 기본값
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ Icon(Icons.search), SizedBox(width: 16), Icon(CupertinoIcons.heart), SizedBox(width: 16), ], ), body: Placeholder(), ); } }
    notion image

    AppBar 함수로 빼기

    notion image
    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: Placeholder(), ); } AppBar _appBar() { return AppBar( actions: [ Icon(Icons.search), SizedBox(width: 16), Icon(CupertinoIcons.heart), SizedBox(width: 16), ], ); } }
     

    MainPage - ListView

    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ Text("Recipes"), Container( child: Column( children: [ Icon(Icons.food_bank), Text("ALL"), ], ), ), ], ), ); }
    notion image
     

    ListView Class 만들기

    문서
    ListView class - widgets library - Dart API
    API docs for the ListView class from the widgets library, for the Dart programming language.
    https://api.flutter.dev/flutter/widgets/ListView-class.html
    notion image
    예제코드
    ListView( padding: const EdgeInsets.all(8), children: <Widget>[ Container( height: 50, color: Colors.amber[600], child: const Center(child: Text('Entry A')), ), Container( height: 50, color: Colors.amber[500], child: const Center(child: Text('Entry B')), ), Container( height: 50, color: Colors.amber[100], child: const Center(child: Text('Entry C')), ), ], )
    디자인 할 때는 순수하게 아이콘만 넣는게 좋다
    여백까지 같이 줘버리면 다른 곳에서 재사용이 불가능하다
    선택적 매개변수를 넣는 방법도 있으나 비추천 → 매개변수가 많아지고 복잡해짐
    notion image
    MenuItem
    class MenuItem extends StatelessWidget { IconData mIcon; var mText; MenuItem(this.mIcon, this.mText); @override Widget build(BuildContext context) { return Container( child: Column( children: [ Icon(mIcon), Text(mText), ], ), ); } }
    MainPage - ListView
    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ Text("Recipes"), Row( children: [ MenuItem(Icons.food_bank, "ALL"), MenuItem(Icons.emoji_food_beverage, "coffee"), MenuItem(Icons.fastfood, "burger"), MenuItem(Icons.local_pizza, "pizza"), ], ), ], ), ); }
    notion image
    MenuItem 사이 여백 추가 - SizedBox()
    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ Text("Recipes"), 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"), ], ), ], ), ); }
    notion image
     

    Menu 위젯 만들기

    notion image
    Menu
    class Menu extends StatelessWidget { @override Widget build(BuildContext context) { return 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"), ], ); } }
    MainPage - ListView
    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ Text("Recipes"), Menu(), ], ), ); }
     

    Image 위젯 만들기

    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ Text("Recipes"), Menu(), Column( children: [ Image.asset("assets/burger.jpeg"), Text("title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ), ], ), ); }
    notion image
    ListItem 위젯
    class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( children: [ Image.asset("assets/$title.jpeg"), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ); } }
    MainPage - ListView
    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ Text("Recipes"), Menu(), ListItem("coffee"), ListItem("burger"), ListItem("pizza"), ], ), ); }
    notion image
    notion image
     

    만든 위젯들 파일화 + MainPage 위젯 Class화

    notion image

    list_item

    import 'package:flutter/material.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( children: [ Image.asset("assets/$title.jpeg"), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ); } }

    m_title

    import 'package:flutter/material.dart'; class MTitle extends StatelessWidget { @override Widget build(BuildContext context) { return Text("Recipes"); } }

    menu

    import 'package:flutter/material.dart'; import 'package:flutter_recipe_app_2/component/menu_item.dart'; class Menu extends StatelessWidget { @override Widget build(BuildContext context) { return 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

    import 'package:flutter/material.dart'; class MenuItem extends StatelessWidget { IconData mIcon; var mText; MenuItem(this.mIcon, this.mText); @override Widget build(BuildContext context) { return Container( child: Column( children: [ Icon(mIcon), Text(mText), ], ), ); } }

    main

    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_recipe_app_2/component/list_item.dart'; import 'package:flutter_recipe_app_2/component/menu.dart'; import 'component/m_title.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: ListView( children: [ MTitle(), Menu(), ListItem("coffee"), ListItem("burger"), ListItem("pizza"), ], ), ); } AppBar _appBar() { return AppBar( actions: [ Icon(Icons.search), SizedBox(width: 16), Icon(CupertinoIcons.heart), SizedBox(width: 16), ], ); } }
     

    Design

    ListView 왼 쪽에 마진

    class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: ListView( children: [ MTitle(), Menu(), ListItem("coffee"), ListItem("burger"), ListItem("pizza"), ], ), ), ); }
    상단의 아이콘과 위치가 잘 맞아야함
    상단의 아이콘과 위치가 잘 맞아야함
     

    heart 아이콘에 색깔주기

    AppBar _appBar() { return AppBar( actions: [ Icon(Icons.search), SizedBox(width: 16), Icon(CupertinoIcons.heart, color: Colors.redAccent), SizedBox(width: 16), ], ); } }
    notion image
     

    MTitle(Recipes) Design

    font 샘플링 할때는 부분 복사가 아닌 전체 복사 / 동일한 환경에서 샘플링
    • google_font 라이브러리 사용해서 font 바꾸기
      • Dart packagesDart packagesgoogle_fonts | Flutter package
        google_fonts | Flutter package

        google_fonts | Flutter package

        A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.

        Dart packagesDart packages
        Google FontsGoogle FontsBrowse Fonts - Google Fonts
        Browse Fonts - Google Fonts

        Browse Fonts - Google Fonts

        Making the web more beautiful, fast, and open through great typography

        Google FontsGoogle Fonts
        font style 사용법
        notion image
    import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class MTitle extends StatelessWidget { @override Widget build(BuildContext context) { return Text( "Recipes", style: GoogleFonts.patuaOne(textStyle: TextStyle(fontSize: 30)), ); } }
    notion image
     

    Menu Item 디자인

    1. Container에 width, height 주기
    import 'package:flutter/material.dart'; class MenuItem extends StatelessWidget { IconData mIcon; var mText; MenuItem(this.mIcon, this.mText); @override Widget build(BuildContext context) { return Container( width: 60, height: 80, child: Column( children: [ Icon(mIcon), Text(mText), ], ), ); } }
    notion image
     
    1. Container 색깔, 모양주기
    import 'package:flutter/material.dart'; class MenuItem extends StatelessWidget { IconData mIcon; var 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( children: [ Icon(mIcon), Text(mText), ], ), ); } }
    notion image
     
    1. 아이콘 정렬, 색깔, 아이콘 사이 여백
    import 'package:flutter/material.dart'; class MenuItem extends StatelessWidget { IconData mIcon; var 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, color: Colors.redAccent, size: 30, ), SizedBox(height: 5), Text(mText), ], ), ); } }
    notion image
     

    Mtitle과 ListItem 사이 여백주기

    import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_recipe_app_2/component/list_item.dart'; import 'package:flutter_recipe_app_2/component/m_title.dart'; import 'package:flutter_recipe_app_2/component/menu.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(), body: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: ListView( children: [ MTitle(), SizedBox(height: 20), Menu(), SizedBox(height: 20), ListItem("coffee"), ListItem("burger"), ListItem("pizza"), ], ), ), ); } AppBar _appBar() { return AppBar( actions: [ Icon(Icons.search), SizedBox(width: 16), Icon(CupertinoIcons.heart, color: Colors.redAccent), SizedBox(width: 16), ], ); } }
    notion image
     

    이미지 비율 맞추기 + 자르기

    1. 이미지에 속성주기
    import 'package:flutter/material.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( children: [ Image.asset( "assets/$title.jpeg", fit: BoxFit.cover, ), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ); } }
    1. 이미지 자르기
    notion image
    import 'package:flutter/material.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( children: [ ClipRRect( child: Image.asset( "assets/$title.jpeg", fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(20), ), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ); } }
    notion image
    1. 이미지 비율 맞추기
    notion image
    import 'package:flutter/material.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( children: [ AspectRatio( aspectRatio: 2 / 1, child: ClipRRect( child: Image.asset( "assets/$title.jpeg", fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(20), ), ), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ); } }
    notion image

    ListItem 폰트 변경 + 글자 정렬 + ListItem 간 여백주기

    1. 글자 정렬
    import 'package:flutter/material.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 2 / 1, child: ClipRRect( child: Image.asset( "assets/$title.jpeg", fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(20), ), ), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), ], ); } }
    notion image
    1. ListItem 간 여백주기
    import 'package:flutter/material.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 2 / 1, child: ClipRRect( child: Image.asset( "assets/$title.jpeg", fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(20), ), ), Text("$title"), Text("Have you ever made your own $title? Once you've tried a homemade $title, you'll never go back."), SizedBox(height: 20), ], ); } }
    notion image
    1. 폰트, 폰트 색상 변경
    import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class ListItem extends StatelessWidget { var title; ListItem(this.title); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 2 / 1, child: ClipRRect( child: Image.asset( "assets/$title.jpeg", fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(20), ), ), Text( "$title", style: GoogleFonts.patuaOne(textStyle: 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), ), SizedBox(height: 20), ], ); } }
    notion image
    notion image
    Share article
    Contents
    출처Project 생성SettingPreview기본 구조 - DefaultRecipe App의 화면 구조Layout 잡기만든 위젯들 파일화 + MainPage 위젯 Class화Design

    parangdajavous

    RSS·Powered by Inblog