Flutter | Exercise Example | Cupertino Widget, CupertinoDatePicker, Dialog, StatefulWidget

Flutter Design

플러터(Flutter)에는 두 가지 디자인 시스템이 있다.

Material Widget

구글 스타일의 머티리얼(Material) 디자인 위젯이다.

Cupertino Widget

애플 스타일의 쿠퍼티노(Cupertino) 디자인 위젯이다.

Stateful Widget의 setState({callback function}) 함수

setState() 함수는 인자로 콜백(callback) 함수를 받는다.

콜백(callback) 함수에 클래스가 가진 속성들을 변경하면 해당 코드가 반영되어 build() 함수가 실행된다.

setState() 함수 형태에 대한 예시

setState(()
  x++;
})

이와 같이 하면 ‘x++’ 수행 후 build() 함수가 실행된다.

showCupertinoDialog() 함수

showCupertino() 함수는 애플(Apple) iOS 스타일의 다이얼로그를 실행하는 함수이다. 실행 시 모든 애니메이션과 작동이 iOS 스타일로 적용 됩니다.

import 'package:flutter/cupertino.dart';

showCupertinoDialog(
  context: context,
  barrierDismissible: true,  // 외부 탭 시 다이얼로그 닫히는 옵션
  builder: (BuildContext context) {  // 다이얼로그 내부 위젯들 작성
    return Text('Dlg');
  },
);

플러터(Flutter)에서 모든 showDialog() 형태의 함수들은 BuildContext를 필수로 입력해야 한다.

플러터(Flutter)에서 다이얼로그 위젯 외에 흐림 처리가 된 부분을 배리어(barrier)라고 부른다. barrierDismissible 옵션이 바로 그 배리어(barrier)영역을 탭 했을 때 다이얼로그를 닫을지 여부를 결정하는 옵션이다.

예제 : D-Day APP

특정 일을 설정하면 지금까지 며칠이나 흘렀는지 계산해주는 앱을 만들며 Cupertino Widget을 사용해 보자.

asset에 이미지와 폰트 추가 및 pubspec.yaml 설정

이미지와 폰트 파일 추가

flutter-screenshot-d-day-app
asset 파일 구조

위와 같이 asset 폴더를 만들고 그 하위 폴더로 font, img 폴더를 생성 후, 폰트 파일과 이미지 파일을 추가해 주었다.

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - asset/img/

  fonts:
    - family: parisienne
      fonts:
        - asset: asset/font/Parisienne-Regular.ttf

    - family: sunflower
      fonts:
        - asset: asset/font/Sunflower-Light.ttf
        - asset: asset/font/Sunflower-Medium.ttf
          weight: 500
        - asset: asset/font/Sunflower-Bold.ttf
          weight: 700

예제 코드와 파일 구조

component와 screen 그리고 main.dart로 폴더 구조를 잡아보았다.

flutter-file-structure-app-d-day
소스 파일 구조

couple_image.dart

import 'package:flutter/material.dart';

class CoupleImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Center(
        child: Image.asset(
          'asset/img/middle_image.png',
          // 이 위젯 트리에서 가장 가까운 MediaQuery 높이 값의 반을 높이로 지정
          // --> Expanded로 대체
          // height: MediaQuery.of(context).size.height / 2,
        ),
      ),
    );
  }
}

dday.dart

import 'package:flutter/material.dart';

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

  const 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', style: textTheme.displayLarge),
        const SizedBox(height: 16.0),
        Text('우리 처음 만난 날', style: textTheme.bodyLarge),
        Text(
          '${firstDay.year}.${firstDay.month}.${firstDay.day}',
          style: textTheme.bodyMedium,
        ),
        const SizedBox(height: 16.0),
        IconButton(
          onPressed: onHeartPressed,
          icon: Icon(Icons.favorite),
          color: Colors.red,
          iconSize: 60.0,
        ),
        const SizedBox(height: 16.0),
        Text(
          'D+${DateTime(now.year, now.month, now.day).difference(firstDay).inDays + 1}',
          style: textTheme.displayMedium,
        ),
      ],
    );
  }
}

home_screen.dart

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

import '../component/couple_image.dart';
import '../component/dday.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.pink[100], // 배경색 분홍색으로
      body: SafeArea(
        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,
            child: CupertinoDatePicker(
              mode: CupertinoDatePickerMode.date,
              onDateTimeChanged: (DateTime date) {
                setState(() {
                  firstDay = date;
                });
              },
            ),
          ),
        );
      },
      barrierDismissible: true,
    );
  }
}

main.dart

import 'package:flutter/material.dart';
import 'package:u_and_i/screen/home_screen.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        fontFamily: 'sunflower',  // 기본 글씨체
        textTheme: TextTheme(
          displayLarge: TextStyle(  // H1 스타일 정의
            color: Colors.white,    // 글자 색상
            fontSize: 80.0,         // 글자 크기
            fontWeight: FontWeight.w700,  // 글자 두께
            fontFamily: 'parisienne', // 글씨체
          ),
          displayMedium: TextStyle(
            color: Colors.white,
            fontSize: 50.0,
            fontWeight: FontWeight.w700,
          ),
          bodyLarge: TextStyle(color: Colors.white, fontSize: 30.0),
          bodyMedium: TextStyle(color: Colors.white, fontSize: 20.0),
        ),
      ),
      home: HomeScreen(),
    ),
  );
}


참고

Flutter | Windows 개발 환경 구축

[Must Have] 코드팩토리의 플러터 프로그래밍(3판)

댓글 남기기