Dandy Now!
  • [Flutter] "모두가 할 수 있는 플러터 UI 입문" - 레시피 앱 만들기
    2022년 02월 01일 18시 03분 57초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    "최주호, 정호준, & 정동진. (2021). 모두가 할 수 있는 플러터 UI 입문. 앤써북"으로 플러터 공부를 하고 있다. 이번 챕터에서는 AppBar, AspectRatio, ClipRRect, Column, Container, Icon, ListView 등을 다뤘다. 특별히 [그림 1]과 같이 RecipeListItem을 출력할 때 스마트폰 화면을 넘어가는 overflow 문제 해결 방법을 잘 기억하자!

     

    [그림 1] overflow 문제

     

    overflow 문제는 recipe_page.dart의 Scaffold 위젯 내 Column 위젯을 ListView 위젯으로 변경하여 해결한다. 그러면 세로 스크롤 기능이 추가되며 overflow 문제가 해결된다.

     

    [그림 2] 완성된 레시피 앱

     


     

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:flutter_recipe/recipe_page.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
              fontFamily:
                  "PatuaOne"), // pubspec.yaml의 fonts에 PatuaOne-Regular.ttf 추가
          home: RecipePage(),
        );
      }
    }

     

    // recipe_page.dart
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_recipe/recipe_list_item.dart';
    import 'package:flutter_recipe/recipe_menu.dart';
    import 'package:flutter_recipe/recipe_title.dart';
    
    class RecipePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          appBar: _buildRecipeAppBar(), // AppBar 연결
          body: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 20),
            // 세로 스크롤 위해 Column을 ListView로 변경
            // child: Column(
            child: ListView(
              children: [
                RecipeTitle(),
                RecipeMenu(),
                RecipeListItem("coffee", "Made Coffee"), // 첫번째 인자는 그림 파일명과 같아야 한다.
                RecipeListItem("burger", "Made Burger"),
                RecipeListItem("pizza", "Made Pizza"),
              ],
            ),
          ),
        );
      }
    }
    
    AppBar _buildRecipeAppBar() {
      return AppBar(
        backgroundColor: Colors.white,
        elevation: 1.0, // AppBar의 그림자 효과 조정
        actions: [
          Icon(
            CupertinoIcons.search, // 쿠퍼티노 아이콘 사용
            color: Colors.black,
          ),
          SizedBox(width: 15),
          Icon(
            CupertinoIcons.heart,
            color: Colors.redAccent,
          ),
          SizedBox(width: 15),
        ],
      );
    }

     

    // recipe_title.dart
    import 'package:flutter/material.dart';
    
    class RecipeTitle extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.only(top: 20.0),
          child: Text(
            "Recipes",
            style: TextStyle(fontSize: 30),
          ),
        );
      }
    }

     

    // recipe_menu.dart
    import 'package:flutter/material.dart';
    
    class RecipeMenu extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.only(top: 20),
          child: 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) {
        // Container는 내부에 decoration 속성이 있어서 박스 테두리, 색상, 모양을 바꿀 수 있다.
        // Container의 특징은 자식이 없는 경우 박스를 최대한 크게 만든다. 자식이 있으면 자식 크기에 맞춘다.
        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),
              )
            ],
          ),
        );
      }
    }

     

    // 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); // const, final 이용하여 생성자 인수가 동일할 때는 객체를 재사용하고, 다를 때는 새로운 객체를 생성한다.
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.symmetric(vertical: 20),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // AspectRatio로 이미지 비율 설정
              AspectRatio(
                aspectRatio: 2 / 1,
                // ClipRRect로 이미지 모서리 곡선 처리
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(20),
                  child: Image.asset(
                    "assets/images/$imageName.jpg", // 문자열 안에서 $ 넣어 변수 사용
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              SizedBox(height: 10),
              Text(
                title,
                style: TextStyle(fontSize: 20),
              ),
              Text(
                "불라 불라 불라 불라 불라 불라 불라 $title? 불라 불라 불라 불라 불라 불라 불라 $title, 불라 불라 불라 불라 불라 불라 불라",
                style: TextStyle(color: Colors.grey, fontSize: 12),
              ),
            ],
          ),
        );
      }
    }
    728x90
    반응형
    댓글