반응형

D-Day 앱 만들기

 

D-Day 앱 레이아웃

 - 화면의 상/하단으로 나눕니다.

 - 화면의 상단에는 D-Day 시작 날짜를 선택 할 수 있는 이미지 버튼과 D-Day 를 계산하여 표시합니다.

 - 화면의 하단에는 이미지를 넣어줍니다.

 

사전 준비

 - 배경으로 사용될 이미지를 준비합니다.

 - 폰트 파일을 준비합니다.

 - pubspec.yaml 에서 프로젝트에서 사용할 이미지와 폰트를 설정합니다.

....

flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  assets:
    - asset/img/

  fonts:
    - family: parisienne  # ➊ family 키에 폰트 이름을 지정할 수 있습니다.
      fonts:
        - asset: asset/font/Parisienne-Regular.ttf  # ➋ 등록할 폰트 파일의 위치

    - family: sunflower
      fonts:
        - asset: asset/font/Sunflower-Light.ttf
        - asset: asset/font/Sunflower-Medium.ttf
          weight: 500  # ➌ 폰트의 두께. FontWeight 클래스의 값과 같습니다.
        - asset: asset/font/Sunflower-Bold.ttf
          weight: 700

....

Weight 는 폰트의 두께를 의미합니다. 폰트의 두께별로 파일이 따로 존재하기 때문에 같은 폰트라도 다른 두께를 표현하는 파일은 Weight 값을 따로 표현해야 합니다.

두께 값은 100~900까지 100단위로 사용할 수 있으면 숫자가 높을수록 두껍다는 의미입니다. 코드에서 사용시에는 FontWeight.w500 처럼 사용하면 됩니다.

pubspec.yaml 파일을 수정했다면 [pub get] 을 실행하여 변경 사항을 반영해줍니다.

 

 

첫 화면

그럼 이제 본격적으로 D-Day 앱을 만들어 보겠습니다.

 - main.dart 파일에 위젯에서 사용할 텍스트와 IconButton 테마를 정의합니다.

import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(  // ➊ 테마를 지정할 수 있는 클래스
          fontFamily: 'sunflower',  // 기본 글씨체
          textTheme: TextTheme(     // ➋ 글짜 테마를 적용할 수 있는 클래스
            headline1: TextStyle(   //  headline1 스타일 정의
              color: Colors.white,  //  글 색상
              fontSize: 80.0,       //  글 크기
              fontWeight: FontWeight.w700, //  글 두께
              fontFamily: 'parisienne',    //  글씨체
            ),
            headline2: TextStyle(
              color: Colors.white,
              fontSize: 50.0,
              fontWeight: FontWeight.w700,
            ),
            bodyText1: TextStyle(
              color: Colors.white,
              fontSize: 30.0,
            ),
            bodyText2: TextStyle(
              color: Colors.white,
              fontSize: 20.0,
            ),
          )
      ),
      home: HomeScreen(),
    ),
  );
}

MaterialApp 에는 theme 라는 매개변수가 있습니다. 이 매개변수에는 ThemeData 를 입력할 수 있습니다.

ThemeData 에서는 플러터가 기본으로 제공하는 대부분의 위젯의 기본 스타일을 지정할 수 있습니다.

위에서 사용한 textTheme 는 글자의 테마를 지정할 수 있는 매개변수입니다.

 

앱 화면(HomeScreen 위젯)

D-Day 앱 화면에 표시할 위젯을 작성합니다.

 - 화면을 _DDay 위젯과 _CoupleImage 위젯, 두 위젯을 위 아래로 배치하여 구현합니다.

 - lib/home_screen.dart

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

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  DateTime firstDay = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.pink[100],
      body: SafeArea(
        // ➊ 시스템 UI 피해서 UI 그리기
        top: true,
        bottom: false,
        child: Column(
          // ➋ 위, 아래 끝에 위젯 배치
          mainAxisAlignment: MainAxisAlignment.spaceBetween,

          // 반대 축 최대 크기로 늘리기
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _DDay(

              // ➎ 하트 눌렀을때 실행할 함수 전달하기
              onHeartPressed: onHeartPressed,
              firstDay: firstDay,
            ),
            _CoupleImage(),
          ],
        ),
      ),
    );
  }

  void onHeartPressed(){  // ➍ 하트 눌렀을때 실행할 함수
    showCupertinoDialog(  // ➋ 쿠퍼티노 다이얼로그 실행
      context: context,
      builder: (BuildContext context) {
        return Align(  // ➊ 정렬을 지정하는 위젯
          alignment: Alignment.bottomCenter,  // ➋ 아래 중간으로 정렬
          child: Container(
            color: Colors.white,  // 배경색 흰색 지정
            height: 300,  // 높이 300 지정
            child: CupertinoDatePicker(
              mode: CupertinoDatePickerMode.date,
              onDateTimeChanged: (DateTime date) {
                setState(() {
                  firstDay = date;
                });
              },
            ),
          ),
        );
      },
      barrierDismissible: true,
    );
  }
}

.....

SafeArea : 아이폰의 노치 디스플레이등에 대비에서 위쪽에 SafeArea 를 적용해준다. 하단에는 미적용한다.

mainAxisAlignment: MainAxisAlignment.spaceBetween

 - 위 아래 각각 끝에 _DDay, _CoupleImage 위젯을 위치시킵니다.

상태 관리

 - 처음 만난 날은 변경 할 수 있는 값입니다.

 - 이 날짜를 변수 값으로 저장하고 변경하면서 사용합니다.

 - 하트 아이콘(ImageButtion)를 누르면 날짜를 고를 수 있는 UI 가 나오며, 날짜를 선택하면 firstDay 변수를 변경시킵니다.

 - _DDay 위젯에 하트 아이콘을 눌렀을 때 실행되는 콜백함수를 매개변수로 전달해서 _HomeScreen 에서 상태를 관리합니다.

Align 위젯

 - 자식 위젯의 위치를 지정합니다.

 - bottomCenter 는 자식 위젯을 중간 아래에 배치합니다.

onDateTimeChanged 의 콜백 함수

 - CupertinoDatePicker 위젯에서 날짜가 변경될 때마다 실행합니다.

 - 콜백함수가 실행될 때마다 firstDay 변수에 선택된 date 값이 저장됩니다.

 

 

_DDay 위젯

 - 여러 Text 위젯과 하트 아이콘(ImageButton)으로 이루어져 있습니다.

 - 만나기 시작할 날짜와 며칠이 지났는지 표시하는 글자는 날짜를 변경할 때마다 자동으로 바뀌게 됩니다.

 - main.dart 에서 정의한 스타일을 Text 위젯의 style 에 적용합니다.

 - lib/home_screen.dart

class _HomeScreenState extends State<HomeScreen> {
....
}


class _DDay extends StatelessWidget {
  final GestureTapCallback onHeartPressed;
  final DateTime firstDay;

  _DDay({
    required this.onHeartPressed,  // ➋ 상위에서 함수 입력받기
    required this.firstDay,
  });

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    final now = DateTime.now();

    return Column(
      children: [
        const SizedBox(height: 16.0),
        Text(
          // 최상단 U&I 글자
          'U&I',
          style: textTheme.headline1,
        ),
        const SizedBox(height: 16.0),
        Text(
          // 두번째 글자
          '우리 처음 만난 날',
          style: textTheme.bodyText1,
        ),
        Text(
          // 임시로 지정한 만난 날짜
          '${firstDay.year}.${firstDay.month}.${firstDay.day}',
          style: textTheme.bodyText2,
        ),
        const SizedBox(height: 16.0),
        IconButton(
          // 하트 아이콘 버튼
          iconSize: 60.0,
          onPressed: onHeartPressed,
          icon: Icon(
            Icons.favorite,
            color: Colors.red,
          ),
        ),
        const SizedBox(height: 16.0),
        Text(
          // 만난 후 DDay
          'D+${DateTime(now.year, now.month, now.day).difference(firstDay).inDays + 1}',
          style: textTheme.headline2,
        ),
      ],
    );
  }
}

Theme.of(context).textTheme

 - 위젯트리 위 가장 가까운 Theme 값을 가져옵니다.

 - Icon 의 경우 색상이 각각 다른 경우가 많기 때문에 theme 를 사용하지 않고 직접 지정합니다.

GestureTapCallback

 - Material 패키지에서 기본으로 제공하는 typedef 로, 버튼의 onPressed 및 onTap 콜백 함수들의 GestureTapCallback 타입으로 정의되어 있습니다.

 

 

_CoupleImage 위젯

 - lib/home_screen.dart

class _HomeScreenState extends State<HomeScreen> {
....
}

class _DDay extends StatelessWidget {
....
}

class _CoupleImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      // Expanded 추가
      child: Center(
        // ➊ 이미지 중앙정렬
        child: Image.asset(
          'asset/img/middle_image.png',

          // ➋ 화면의 반만큼 높이 구현
          height: MediaQuery.of(context).size.height / 2,
        ),
      ),
    );
  }
}

Expanded

 - 핸드폰은 화면의 비율과 해상도가 모두 다릅니다. 따라서 하나의 화면을 기준으로 UI 작업을 하다보면 다른 크기의 핸드포에서는 같은 UI 배치가 나오지 않을 수 있습니다. 이미지를 화면의 반만큼 차지하도록 설정하였는데 만약 화면의 크기가 작아서 상단의 글자들이 화면의 반 이상을 차지하면 아래쪽 이미지는 남은 공간보다 더 많은 높이를 차지합니다. 그래서 일반적으로 이미지가 남는 공간만큼 차지하도록 Expanded 위젯을 사용합니다.

height: MediaQuery.of(context).size.height / 2

 - 이미지를 적용하고 높이를 화면 높이의 반으로 설정합니다.

 - MediaQuery 를 사용하면 화면의 크기와 관련된 각종 기능을 사용할 수 있습니다.

 - size getter 를 사용하여 전체의 너비와 높이를 사용하였습니다. 화면의 전체 높이 / 2 를 사용하여 이미지가 화면 높이 반만큼만 차지할 수 있도록 설정하였습니다.

Center 를 이용해서 이미지를 중앙에 배치시킵니다.

 

 

자!! 이제 코드 작성은 완성하였습니다.

앱을 실행해보자.

D-Day 앱. 짜잔~~~

 

반응형

+ Recent posts