반응형

이번 글에서는 주사위 앱 만들기에 대해서 알아봅니다.

 

주사위 앱 레이아웃

 - BottomNavigationBar 위젯을 이용해서 화면 전환을 한다.

 - RootScreen 위젯 : 상단과 하단으로 나누어져 있는 위젯입니다. 하단에는 화면 전환을 할 수 있는 BottomNavigationBar 위젯을 위치 시키고, 상단에는 TabBarView 를 통해 선택된 화면을 보여줍니다. 화면 전환은 BottomNavigationBar 의 탭을 클릭하거나 TabBarView 에서 좌우 스크롤을 통해 이루어집니다. TabBarView 에 나타내는 화면은 HomeScreen 위젯과 SettingsScreen 위젯 두 화면입니다.

 - HomeScreen 위젯 : 주사위 이미지를 위치시킬 Image 위젯, 글자를 작성할 Text 위젯, 주사위 숫자를 나타낼 Text 위젯으로 이루어져 있습니다.

 - SettingsScreen : 민감도를 정의하는 Slider 위젯을 위치시키고 이 Slider 를 좌우로 이동시켜 민감도를 정합니다. Text 위젯을 이용하여 레이블을 작성합니다.

 

주사위 앱 shake 플러그인 및 이미지 추가

 - pubspec.yaml

....

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  shake: 2.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
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/
  
  ....

 

주사위 앱에서 사용할 상수 정의

 - 글자 크기, 자주 사용하는 색상등을 반복적으로 사용하는 값들을 미리 정의해놓고 사용하기 위해서 정의한다.

 - 일괄적으로 변경 시 유용하게 사용됩니다.

 - [프로젝트 디렉토리]/lib/const/colors.dart

import 'package:flutter/material.dart';

const backgroundColor = Color(0xFF0E0E0E);

const primaryColor = Colors.white;

final secondaryColor = Colors.grey[600];

※ secondaryColor : grey[600] 은 런타임시에 색상이 계산되기 때문에 const 사용이 불가하다. 그래서 final 로 정의합니다.

 

 

주사위 앱 위젯 구현

1. HomeScreen

 - [프로젝트 디렉토리]/lib/screen/home_screen.dart

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

class HomeScreen extends StatelessWidget {
  final int number;

  const HomeScreen({
    required this.number,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [

        // ➊ 주사위 이미지
        Center(
          child: Image.asset('asset/img/$number.png'),
        ),
        SizedBox(height: 32.0),
        Text(
          '주사위 숫자',
          style: TextStyle(
            color: secondaryColor,
            fontSize: 20.0,
            fontWeight: FontWeight.w700,
          ),
        ),
        SizedBox(height: 12.0),
        Text(
          number.toString(),  // ➋ 주사위 값에 해당되는 숫자
          style: TextStyle(
            color: primaryColor,
            fontSize: 60.0,
            fontWeight: FontWeight.w200,
          ),
        ),
      ],
    );
  }
}

 - Column 위젯을 사용하여 Image 위젯과 Text 위젯을 배치합니다. 화면에 보여질 숫자를 RootScreen 위젯에서 정의하도록 생성자를 통해서 number 매개변수로 입력받는다.

 - 생성자로 입력받은 number 매개변수 값을 이용하여 Image 위젯에는 해당하는 숫자 이미지를, Text 위젯에는 숫자 값을 표시합니다.

 

2. SettingsScreen

 - [프로젝트 디렉토리]/lib/screen/settings_screen.dart

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

class SettingsScreen extends StatelessWidget {
  final double threshold;  // Slider의 현잿값

  // Slider가 변경될 때마다 실행되는 함수
  final ValueChanged<double> onThresholdChange;

  const SettingsScreen({
    Key? key,

    // threshold와 onThresholdChange는 SettingsScreen에서 입력
    required this.threshold,
    required this.onThresholdChange,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Padding(
          padding: const EdgeInsets.only(left: 20.0),
          child: Row(
            children: [
              Text(
                '민감도',
                style: TextStyle(
                  color: secondaryColor,
                  fontSize: 20.0,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ],
          ),
        ),
        Slider(
          min: 0.1,  // ➊ 최소값
          max: 10.0,  // ➋ 최대값
          divisions: 101,  // ➌ 최소값과 최대값 사이 구간 개수
          value: threshold,  // ➍ 슬라이더 선택값
          onChanged: onThresholdChange,  // ➎ 값 변경 시 실행되는 함수
          label: threshold.toStringAsFixed(1),  // ➏ 표시값
        ),
      ],
    );
  }
}

 - SettinsScreen 위젯은 Text 위젯과 Slider 위젯으로 이루어져 있습니다. Column 위젯을 이용하여 Text 위젯과 Slider 위젯을 세로로 배치합니다.

 - Slider 위젯 : 좌우로 움직일 때 onChanged 매개변수에 정의된 콜백함수가 호출됩니다. onChanged 매개변수로 입력받은 현재값을 State에 저장하고 다시 value 매개변수에 같은 값을 입력하게 됩니다.

 - Slider 위젯은 현재값과 onChanged 매개변수는 RootScreen 에서 입력받습니다.

 

3. RootScreen

 - [프로젝트 디렉토리]/lib/screen/root_screen.dart

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

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

  @override
  State<RootScreen> createState() => _RootScreenState();
}

class _RootScreenState extends State<RootScreen> with TickerProviderStateMixin{    // ➊
  TabController? controller;  // 사용할 TabController 선언
  double threshold = 2.7;
  int number = 1;
  ShakeDetector? shakeDetector;

  @override
  void initState() {
    super.initState();

    controller = TabController(length: 2, vsync: this);  // ➋

    controller!.addListener(tabListener);
    shakeDetector = ShakeDetector.autoStart(   // ➊ 흔들기 감지 즉시 시작
      shakeSlopTimeMS: 100,  // ➋ 감지 주기
      shakeThresholdGravity: threshold,  // ➌ 감지 민감도
      onPhoneShake: onPhoneShake,  // ➍ 감지 후 실행할 함수
    );
  }

  void onPhoneShake() {  // ➎ 감지 후 실행할 함수
    final rand = new Random();

    setState(() {
      number = rand.nextInt(5) + 1;
    });
  }

  tabListener() {  // ➋ listener로 사용할 함수
    setState(() {});
  }

  @override
  dispose(){
    controller!.removeListener(tabListener); // ➌ listener에 등록한 함수 등록 취소
    shakeDetector!.stopListening();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(  // ➊ 탭 화면을 보여줄 위젯
        controller: controller,
        children: renderChildren(),
      ),

      // ➋ 아래 탭 네비게이션을 구현하는 매개변수
      bottomNavigationBar: renderBottomNavigation(),
    );
  }

  List<Widget> renderChildren(){
    return [
      HomeScreen(number: number),
      SettingsScreen(  // 기존에 있던 Container 코드를 통째로 교체
        threshold: threshold,
        onThresholdChange: onThresholdChange,
      ),
    ];
  }

  void onThresholdChange(double val){  // ➊ 슬라이더값 변경 시 실행 함수
    setState(() {
      threshold = val;
    });
  }

  BottomNavigationBar renderBottomNavigation() {
    return BottomNavigationBar(
      currentIndex: controller!.index,
      onTap: (int index) {  // ➎ 탭이 선택될 때마다 실행되는 함수
        setState(() {
          controller!.animateTo(index);
        });
      },
      items: [
        BottomNavigationBarItem(  // ➊ 하단 탭바의 각 버튼을 구현
          icon: Icon(
            Icons.edgesensor_high_outlined,
          ),
          label: '주사위',
        ),
        BottomNavigationBarItem(
          icon: Icon(
            Icons.settings,
          ),
          label: '설정',
        ),
      ],
    );
  }
}

 - TabBarView 는 TabController 가 필수입니다. TabController 는 위젯이 생성될 때 딱 한번만 초기화되어야 하니 initState() 에서 초기화 작업을 합니다. TabController vsync 기능을 사용하려면 필수로 TickerProviderStateMixin을 사용해야 합니다. TickerProviderStateMixin 은 애니메이션의 효율을 올려주는 역할을 합니다. TabController 의 length 매개변수에는 탭의 개수를 나타내고, TickerProviderStateMixin 를 사용하는 State 클래스를 this 로 넣어줍니다. 그러면 controller 를 이용해서 TabBarView 를 조작할 수 있습니다. controller 의 속성이 변할 때마다 실행시킬 콜백함수를 addListener() 함수를 통해 등록할 수 있습니다. setState() 를 실행하여 controller 의 속성이 변경될 때 build() 를 재실행하도록 합니다.

 - ShakeDetector : Shake 플러그인은 흔들기를 인지할 때마다 실행할 함수를 등록합니다. 얼마나 자주 흔들기 감지 주기, 감지 민감도, 감지 시 실행할 콜백함수 등을 매개변수로 전달합니다. 흔들 때마다 1 ~ 6 사이의 난수를 생성합니다.

 - renderChildren() : TabBarView 위젯을 반환하는 함수입니다. TabBarView 위젯을 이용하면 각종 Tab 위젯과 쉽게 연동 할 수 있는 UI 를 구현할 수 있습니다. 기본 애니메이션이 제공되며 children 매개변수에 각 탭의 화면에서 활용하고 싶은 위젯을 List 로 넣어줍니다. bottomNavigation 매개변수에 BottomNavigationBar 를 넣어주면 쉽게 Tab을 조정할 수 있는 UI 를 하단에 배치할 수 있습니다. 주사위 앱에서는 TabBarView 의 첫번째 화면에 HomeScreen, 두번째 화면에 SettinsScreen 을 표시합니다.

 - renderBottomNavigation() : BottomNavigationBar 위젯을 반환하는 함수입니다. BottomNavigationBar 에 제공될 각 탭은 BottomNavigationBar 위젯의 items 매개변수에 제공해주면 됩니다. BottomNavigationBarItem 의 icon 매개변수와 label 매개변수를 이용해서 구현합니다. 탭을 클릭했을 때 TabBarView 와 화면을 동기화하기 위해 animateTo() 를 실행하여 자연스러운 애니메이션으로 TabBarView 를 전환시킵니다.

 

4. 주사위 앱 실행 첫 화면 : main

 - [프로젝트 디렉토리]/lib/main.dart

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

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        scaffoldBackgroundColor: backgroundColor,
        sliderTheme: SliderThemeData(  // Slider 위젯 관련
          thumbColor: primaryColor,    // 동그라미 색
          activeTrackColor: primaryColor,  // 이동한 트랙 색

          // 아직 이동하지 않은 트랙 색
          inactiveTrackColor: primaryColor.withOpacity(0.3),         ),
        // BottomNavigationBar 위젯 관련
        bottomNavigationBarTheme: BottomNavigationBarThemeData(
          selectedItemColor: primaryColor,     // 선택 상태 색
          unselectedItemColor: secondaryColor, // 비선택 상태 색
          backgroundColor: backgroundColor,    // 배경 색
        ),
      ),
      home: RootScreen(),
    ),
  );
}

 - Theme 설정 : 상수를 사용해서 테마를 적용합니다.

 - 홈 화면 : RootScreen 위젯으로 설정합니다.

 

 

주사위 앱을 실제 기기에서 실행해봅니다.

 - 주사위가 보이면 핸드폰을 흔들어서 난수가 생성되고 새로운 숫자와 주사위가 보이는 것을 확인합니다.

 - TabBarView 에서 좌우 스크롤하거나 BottomNavigationBar 를 클릭하여 화면 전환이 되는지 확인합니다.

 - [설정] 탭을 클릭하여 설정화면으로 이동한 뒤 Slider 위젯을 이용하여 민감도를 올립니다.

 - [주사위] 탭을 클릭하여 HomeScreen 화면으로 이동한 뒤 핸드폰을 더 강하게 흔들어야 난수가 발생하는지 확인합니다.

 

이상 플러터에서 주사위 앱을 만들고 실행까지 해보았습니다.

 

반응형
반응형

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 앱. 짜잔~~~

 

반응형
반응형

이번에는 짧은 글이 되겠다.

 

StatefulWidget 을 이용한 상태 관리

위젯 생명주기에서의 setState() 함수 역할

이전에 작성한 생명주기 관련된 글을 참조하자

[플러터] 17. 위젯의 생명주기(Life Cycle)
 - State 자체적으로 build() 를 재실행할 때 생명주기

 

[플러터] 17. 위젯의 생명주기(Life Cycle)

위젯의 생명주기 - 위젯이 화면에 그려지는 순간부터 삭제되는 순간까지의 주기를 의미 - UI 를 표현할때 사용되는 대표적인 StatelessWidget 과 StatefulWidget 의 생명주기 StatelessWidget - 이름에서 알 수

childeye.tistory.com

 

StatefulWidget 이 렌더링이 끝나고 clean 상태가 된다. 플러터에서는 그 어떤 상태 변경 툴을 사용하든 클린 상태에서 상태를 변경해줘야 한다.

setState() 함수를 실행해서 속성을 변경한다.

속성이 변경되고 위젯은 상태가 dirty 로 설정된다.

build() 함수가 재 실행된다.

다시 State 가 clean 상태로 되돌아온다.

 

setState() 함수는 매개변수 하나를 입력으로 받는다.

이 매개변수는 콜백함수이고 이 콜백함수에 변경하고 싶은 속성들을 입력한다.

※ 콜백함수가 비동기로 작성되면 안된다.

setState((){
	num++;
});

num을 1증가 시키고 build() 함수를 실행한다.

 

이번 글에서 setState() 를 통한 상태 관리에 대해서 알아봤다.

다음에 만들 앱에서 유용하게 사용해보자!!

오늘은 이만 짧게.. ㅂㅇㅂㅇ~~

반응형
반응형

플러터로 개발할 때 빌드 시 아래와 같은 에러를 만난다고 하면.....

 - java 11 버전 이상 사용하라는 말이다!!

FAILURE: Build failed with an exception.

* Where:
Build file '[프로젝트 디렉토리]\android\app\build.gradle' line: 24

* What went wrong:
A problem occurred evaluating project ':app'.
> Failed to apply plugin 'com.android.internal.application'.
   > Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
     You can try some of the following options:
       - changing the IDE settings.
       - changing the JAVA_HOME environment variable.
       - changing `org.gradle.java.home` in `gradle.properties`.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 6s

┌─ Flutter Fix ─────────────────────────────────────────────────────────────────┐
│ [!] You need Java 11 or higher to build your app with this version of Gradle. │
│                                                                               │
│ To get Java 11, update to the latest version of Android Studio on             │
│ https://developer.android.com/studio/install.                                 │
│                                                                               │
│ To check the Java version used by Flutter, run `flutter doctor -v`.           │
└───────────────────────────────────────────────────────────────────────────────┘
Exception: Gradle task assembleDebug failed with exit code 1

 

어떻게 하라고 친절하게 알려준다.

그런데 저 gradle.properties 이 어디 있냐고????!!!

그거슨 바로 여기~~~

 - [프로젝트 디렉토리]/android/gradle.properties

org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
org.gradle.java.home=[JAVA 설치 디렉토리. ex) C:\\Program Files\\Java\\jdk-11]

 

 

그런데 말입니다~~~

난 이미 자바 11 버전을 쓰고 있는데 왜 이런 에러가 발생하는지... 참... --;;;

C:\Users\home>java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)

 

반응형
반응형

이번에는 전자 액자 앱을 만들어 보자!!

여러 이미지를 슬라이딩하여 볼 수 있는 전자 액자이다.

Layout

 - 위젯을 좌우로 스와이프 할 수 있는 PageView 하나로 구성

 

1. 앱에서 사용할 이미지를 가져온다.

 - [프로젝트 디렉토리]/asset/img 폴더 아래에 이미지를 복사한다.

    √ [프로젝트 디렉토리]/asset/img/image_1.jpeg

    √ [프로젝트 디렉토리]/asset/img/image_2.jpeg

    √ [프로젝트 디렉토리]/asset/img/image_3.jpeg

    √ [프로젝트 디렉토리]/asset/img/image_4.jpeg

    √ [프로젝트 디렉토리]/asset/img/image_5.jpeg

 

2. 추가된 이미지(에셋)을 pubspec.yaml 에 등록한다.

 - [프로젝트 디렉토리]/pubspec.yaml

....

# The following section is specific to Flutter packages.
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/
 
 ....

 

3. 앱의 기본 홈 화면으로 사용할 HomeScreen 위젯을 생성한다.

 - [프로젝트 디렉토리]/lib/screen/home_screen.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text('Home Screen'),
    );
  }
}

 

4. 앱 실행 시 HomeScreen 을 사용하도록 한다.

 - [프로젝트 디렉토리]/lib/main.dart

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

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    ),
  );
}

 

5. PageView 구현

 - PageView 는 여러개의 위젯을 독립적인 페이지로 생성하고 가로 또는 세로 스와이프로 페이지를 넘길 수 있게 하는 위젯이다.

 - PageView 는 material 패키지에서 기본으로 제공

 - children 변수에 페이지로 생성하고 싶은 위젯을 넣어준다.

 - 핸드폰 크기와 이미지의 크기가 달라서 위아래로 흰색 여백이 생길수 있다. 핸드폰 화면 비율에 따라 위아래 대신 좌우가 남거나 운좋게 딱 맞을 수 도 있다. 여러 비율의 화면에 대응할 수 있게 이미지 fit 을 조절해줘서 항상 전체 화면을 차지하도록 설정한다.(fit: BoxFit.cover)

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: PageView(
        controller: pageController,
        // ➊ PageView 추가
        children: [1, 2, 3, 4, 5] // ➋ 샘플 리스트 생성
            .map(
              // ➌ 위젯으로 매핑
              (number) => Image.asset(
                'asset/img/image_$number.jpeg',
                fit: BoxFit.cover,
              ),
            )
            .toList(),
      ),
    );
  }
}

 - BoxFit 관련해서는 Flutter 설명 페이지를 참고.

 

BoxFit enum - painting library - Dart API

How a box should be inscribed into another box. See also: applyBoxFit, which applies the sizing semantics of these values (though not the alignment semantics). Inheritance Constructors BoxFit() const Values fill → const BoxFit Fill the target box by dist

api.flutter.dev

 

6. 상태 바 변경

 - 상태바 : 앱 실행중에 배터리, 시간, 와이파이 연결 상태 등을 보여주는 영역

 - 상태바의 글자 및 아이콘 색상이 검정색이라 잘 안보여 흰색으로 변경.

 - SystemChrome 클래스 : 시스템 UI 의 그래픽 설정을 변경하는 기능 제공.

 - 상태바 변경 : SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);

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

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

  @override
  Widget build(BuildContext context) {
  
 	// 상태바가 이미 흰색이면 light 대신 dark 로 설정하여 검정색으로 변경.
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);

    return Scaffold(
      body: PageView(
        controller: pageController,
        children: [1, 2, 3, 4, 5]
            .map(
              (number) => Image.asset(
                'asset/img/image_$number.jpeg',
                fit: BoxFit.cover,
              ),
            )
            .toList(),
      ),
    );
  }
}

 

 

7. 타이머 추가

 - 일정 시간이 지나면 자동으로 페이지가 변경되도록 Timer 클래스를 사용하여 자동 롤링 기능 추가.

※ Timer 를 추가하려면 HomeScreen 위젯을 StatelessWidget 이 아닌 StatefulWidget 으로 변경해야 한다. StatelessWidget 위젯을 사용하면 Timer 를 build() 에서 등록해야 하는데 이럴경우 위젯이 새로 생성될 때마다 매번 새로운 Timer 가 생성된다. 이렇게 구현하면 Memory leak 이 발생한다.

 - 따라서, StatefulWidget 위젯을 사용하여 initState() 에서 한번만 Timer 를 등록하여 구현하면 된다.

 - Timer 를 사용하기 위해 async 패키지를 import 한다.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async'; // ❶ async 패키지

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

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

// ❷ State 정의
class _HomeScreenState extends State<HomeScreen> {

  @override
  void initState() {
    super.initState(); // ➌ 부모 initState() 실행

    Timer.periodic(
      // ➍ Timer.periodic() 등록
      Duration(seconds: 3),
      (timer) {
        print('실행!');
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);

    return Scaffold(
      body: PageView(
        controller: pageController,
        children: [1, 2, 3, 4, 5]
            .map(
              (number) => Image.asset(
                'asset/img/image_$number.jpeg',
                fit: BoxFit.cover,
              ),
            )
            .toList(),
      ),
    );
  }
}

① Timer 를 사용하기 위해 async 패키지를 import 한다.

② initState() 함수를 override 하면 StatefulWidget 생명주기에서의 initState() 함수를 사용할 수 있다.

③ 모든 initState() 함수는 부모의 initState() 함수를 호출해야 한다.

④ 3초마다 실행되는 Timer 등록.

 

8. PageController 사용

 - PageView 를 조작하기 위해 PageController 사용.

 - PageController 를 State 에 선언하고 PageView 에 매개변수로 입력

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

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

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

class _HomeScreenState extends State<HomeScreen> {
  // ❶ PageController 생성
  final PageController pageController = PageController();

  @override
  void initState() {
    super.initState();

    Timer.periodic(
      Duration(seconds: 3),
      (timer) {
      
        // ➌ 현재 페이지 가져오기.
        int? nextPage = pageController.page?.toInt();

        // 페이지 값이 없을 때 예외 처리
        if (nextPage == null) {
          return;
        }
        
        // 첫페이지와 마지막 페이지 분기 처리
        if (nextPage == 4) {
          nextPage = 0;
        } else {
          nextPage++;
        }
        pageController.animateToPage(
          // ➍ 페이지 변경
          nextPage,
          duration: Duration(milliseconds: 500),
          curve: Curves.ease,
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);

    return Scaffold(
      body: PageView(
      // ❷ PageController 등록
        controller: pageController,
        children: [1, 2, 3, 4, 5]
            .map(
              (number) => Image.asset(
                'asset/img/image_$number.jpeg',
                fit: BoxFit.cover,
              ),
            )
            .toList(),
      ),
    );
  }
}

PageController 사용을 위한 생성.

PageView 에 매개변수로 PageController  등록

③ pageController.page Getter 를 사용하여 PageView 의 현재 페이지를 가져올 수 있다. 페이지가 변경 중인 경우 소수점으로 표현돼서 double 로 값이 반환된다. animateToPage() 함수 실행시 정수값을 넣어줘야해서 toInt() 로 변환한다.

④ animateToPage() 함수를 이용해서 PageView 의 현재 페이지를 변경할 수 있다. curve 매개변수는 페이지가 변경되는 애니메이션의 작동 방식을 정의한다.  공식 홈페이지에서 애니메이션을 확인할 수 있다.

 

Curves class - animation library - Dart API

A collection of common animation curves. See also: Curve, the interface implemented by the constants available from the Curves class. Properties hashCode → int The hash code for this object. read-onlyinherited runtimeType → Type A representation of the

api.flutter.dev

 

이제 모든 코드 작성이 끝났다.

열심히 만든 앱을 실행 시켜보자.

짜잔~~~ 나만의 액자 앱이 잘~~ 만들어 진 것을 확인할 수 있다!!

반응형
반응형

위젯의 생명주기

 - 위젯이 화면에 그려지는 순간부터 삭제되는 순간까지의 주기를 의미

 - UI 를 표현할때 사용되는 대표적인 StatelessWidget 과 StatefulWidget 의 생명주기

 

StatelessWidget

 - 이름에서 알 수 있듯이 "상태가 없는 위젯"

 - StatelessWidget 이 빌드되면 생성자가 실행된다. 이어서 필수로 override 해야하는 build() 함수가 실행된다. build() 함수에 반환된 위젯이 화면에 렌더링된다.

 - 플러터에서 모든 위젯은 Widget 클래스를 상속 받는다. Widget 클래스는 불면(immutable) 특성을 갖고 있다. 클래스를 한번 생성하면 속성을 변경할 수 없다는 뜻이다.

 - 위젯의 속성을 변경해야 할 때가 있다. 예를 들어 생성자에 새로운 매개변수가 입력되는 경우. build() 함수에서 매개변수 값을 사용하고 있다면 변경된 매개변수 기반으로 build() 함수를 재실행해야 한다

 - StatelessWidget 은 불변이기 때문에 한 번 생성된 인스턴스의 build() 함수는 재실행되지 않는다. 화면에 변경된 내용을 반영하고 싶다면 새로운 인스턴스를 생성한 후 기존 인스턴스를 대체해야 한다.

 

StatefulWidget

 - StatefulWidget 은 StatelessWidget 과 다른 목적을 가지고 있다. 위젯 내부에서 build() 함수를 재실행해야 하는 상황에서 사용하는 위젯이다.

 - StatefulWidget은 Widget 클래스와 State 클래스 2개로 구성되어 있다. 

 - 상태 변경이 없는 생명주기

  √ 상태 변경이 없는 생명주기는 위젯이 화면에 나타나며 생성되고 화면에서 사라지며 삭제되는 과정을 의미한다.

   중간에 위젯의 상태가 변경되지 않는다.

      1. StatefulWidget 생성자 실행

      2. createState() 함수 실행. createState() 함수는 필수로 override 해야 하는 함수로 StatefulWidget 과 연동되는 State 를 생성한다.

      3. State 가 생성이되면 initState() 가 실행된다. initState() 는 State가 생성되는 순간 단 한번 실행되고 다시 실행되지 않는다.

      4. didChangeDependencies() 함수 실행. initState() 와 다르게 BuildContext 가 제공되고 State 가 의존하는 값이 변경되면 재실행된다.

      5. State 의 상태가 dirty 상태가 된다. dirty 상태는 build() 가 재실행되어야 하는 상태이다.

      6. build() 함수가 실행되고 UI 가 반영된다.

      7. build() 함수 실행이 완료되면 상태가 clean 상태로 변경된다. 화면에 변화가 없으면 이 상태가 유지된다.

      8. 위젯이 위젯 트리에서 사라지면 deactivate() 가 실행된다. deactivate() 는 State 가 일시적/영구적으로 삭제될때 실행된다.

      9. dispose() 가 실행된다. 위젯이 영구적으로 삭제될때 실행한다.

 

 

 - StatefulWidget 생성자의 매개변수가 변경됐을 때 생명주기

  StatefulWidgetStatelessWidget 처럼 하나의 클래스이다. 따라서 매개변수를 입력 받을 수 있다.

   위젯이 삭제되기 전에 매개변수가 변경된다면....

      1. StatefulWidget 생성자 실행

      2. State 의 didUpdateWidget() 함수 실행

      3. State 의 상태가 dirty 로 변경

      4. build() 함수 실행

      5. State 의 상태가 clean 으로 변경

 

 

 - State 자체적으로 build() 를 재실행할 때 생명주기

   StatelessWidget 은 생성될 때 build() 함수가 한번 실행되고 절대로 다시 실행되지 않는다.

  √ StatefulWidgetStatefulWidget 과 State 클래스로 구성되어 있는데, State 클래스는 setState() 함수를 실행해서 build() 함수를 재실행 할 수 있다.

      1. State 클래스의 setState() 함수 실행

      2. State 의 상태가 dirty 로 변경

      3. build() 함수 실행

      4. State 의 상태가 clean 으로 변경

 

 

이상 StatelessWidget 과 StatefulWidget 의 생명주기에 대해서 알아봤다.

앞으로 두 위젯을 써서 앱을 만들어보면서 더 익혀보기로 하자!!

반응형
반응형

웹 앱을 만들기 위한 사전 작업이 끝났다.

이제 진짜 앱을 만들어 보자!!

 

1. 앱이 처음 실행됐을 때 보여줄 화면.

 - lib/main.dart

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

void main() {
  runApp(
    MaterialApp(
      home: HomeScreen(),
    ),
  );
}

 - import : 다른 파일의 위젯이나 변수등을 불러와서 사용하는 경우에 사용.

  √ 다른 파일에서 불러올 경우 : package:[프로젝트의 이름]/[lib 폴더로 부터의 위치]/파일명(.dart)

  √ 플러그인 기능 사용 시 : package:[플러그인 이름]/[플러그인 이름].dart

 

 - MaterialApp 위젯은 플러터 앱의 최상위 위젯이며 앱이 처음 실행됐을 때 보여줄 화면을 home 매개변수에 입력할 수 있다. 여기서는 HomeScreen() 을 불러와서 첫화면으로 설정한다.

 

2. 홈 화면에서 사용할 HomeScreen 정의

 - lib/screen/home_screen.dart

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

class HomeScreen extends StatelessWidget {
  WebViewController? controller; // 컨트롤러 변수 생성

  HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( // 앱바 위젯 추가
        // 배경색 지정
        backgroundColor: Colors.orange,
        // 앱 타이틀 설정
        title: Text('Childeye Blog'),
        // 가운데 정렬
        centerTitle: true,
        actions: [
          IconButton(

            // 눌렀을 때 콜백 함수 설정
            onPressed: () {
              if (controller != null) {

                // 웹뷰에서 보여줄 사이트 실행하기
                controller!.loadUrl('https://childeye.tistory.com');
              }
            },

            // 홈 버튼 아이콘 설정
            icon: Icon(
              Icons.home,
            ),
          ),
        ],
      ),
      body: WebView(  // WebView 추가하기
        initialUrl: 'https://childeye.tistory.com',
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (WebViewController controller) {
          this.controller = controller; // 위젯에 컨트롤러 저장
        },
      ),
    );
  }
}

 - HomeScreen 위젯 : 웹 앱이 실행되면 가장 먼저 보이는 위젯.

 

 - 웹뷰 컨트롤러 : 홈 아이콘을 눌렀을 때 웹뷰 화면을 변경하려면 위젯 제어 기능이 필요하다. 이 기능을 하는 것이 웹뷰 컨트롤러가 담당한다.

  √ WebViewController 를 저장할 변수를 선언한다.(controller 변수)

  √ 웹뷰 위젯이 생성되면 onWebViewCreated 함수가 실행되는데 이때 WebViewController 를 매개변수로 받는다. 이 값을 controller 변수에 저장하면 웹뷰를 제어할 수 있다.

 

 - 화면 구성 : 앱바와 웹뷰로 이루어져 있음.

  √ 앱바 : 제목과 홈 버튼을 렌더링.

  √ 웹뷰 : 지정한 URL(childeye.tistory.com)의 내용을 표시

 

 - 앱바(AppBar)

  √ HomeScreen 위젯에 앱바를 추가, 제목을 넣어주고 배경색을 설정한다.

  √ 앱바 위젯은 일반적으로 Scaffold 위젯의 appBar 매개변수로 넣어준다.

  √ AppBar 의 배경색을 지정한다.

  √ AppBar 의 중간에 Text 위젯을 넣어주고 가운데 정렬한다.

  √ AppBar 의 actions 매개변수에 홈아이콘을 생성하고 제어한다. actions 매개변수에 위젯을 넣으면 앱바의 오른쪽 끝에 순서대로 위젯이 배치된다.

  √ 홈아이콘을 눌렀을 때 실행할 콜백 함수를 onPressed 매개변수에 정의한다. WebViewController 의 load() 함수를 이용하여 URL 로 이동시킨다. controller 변수에 ! 기호를 추가하는 이유는 controller 변수가 null 이 가능한 타입으로 선언됐기 때문이다. 위 코드에서는 controller 변수가 null 이 아닌 경우에만 load() 함수를 호출하니 ! 를 사용하여 controller 변수는 절대로 null 이 될수 없다고 표현한 것이다.

 

 - 웹뷰(WebView)

  √ 웹뷰 플러그인을 import 하여 사용한다.

  √ Scaffold 의 body 매개변수에 WebView 위젯을 넣는다.

  √ 기본 URL 을 설정하고 자바스크립트도 실행할 수 있도록 한다.

 

에뮬레이터를 실행시키면 다음과 같은 결과물이 된다.

 

웹 앱 완성!!

반응형
반응형

이전 글에서 콜백 함수, 웹뷰 위젯에 대해서 알아보았다.

이를 이용하여 이제 웹 앱 만들기에 도전해보자.

이미 만들어진 웹 페이지를 앱에서 불러와서 보여주는 앱을 웹 앱이라 부른다.

웹 앱을 만들기 전에 사전 설정이 필요하다.

 

1. 플러그인 추가

 - pubspec.yaml

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2

  # 웹뷰 플러그인 추가.
  webview_flutter: 3.0.4

위와 같이 웹뷰 플러그인을 추가하고 "pub get" 을 클릭하여 플러그인을 내려받는다.

"pub get" 버튼을 클릭하지 않고 플러그인을 내려받는 방법은...

아래처럼 안드로이드 스튜디오 하단의 "터미널" 탭에서 명령어를 실행하는 것이다.

- flutter pub get

 

2. 권한 및 네이티브 설정

 - 웹 앱을 만들기 위해 인터넷 사용 권한을 추가하고 http / https 프로토콜을 이용할 수 있게 설정해야 한다.

 - android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_sample">
....
   <uses-permission android:name="android.permission.INTERNET" />
....
</manifest>

 

 - 안드로이드 빌드 툴인 gradle 설정 파일인 build.gradle 은 모듈 파일로써 의존성이나 버전 정보를 관리한다.

 - android/app/build.gradle(android/build.gradle 파일은 프로젝트 파일이며 주로 클래스패스나 repository 정보등을 설정한다)

....

android {
    // 변경.
    // compileSdkVersion flutter.compileSdkVersion
    compileSdkVersion 32

....

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.flutter_sample"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
        // 변경
        // minSdkVersion flutter.minSdkVersion
        minSdkVersion 20
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

....
}

....

 - minSdkVersion : 안드로이드 운영체제의 최소 SDK 버전을 설정

 - compileSdkVersion : 앱을 빌드할 때 사용할 SDK 버전. 앱은 compileSdkVersion 이하 버전의 기능을 모두 지원한다.

이런 네이티브 설정 관련 정보는 각 플러그인의 pub.dev 페이지에서 확인 할 수 있다.참고로 webview_flutter 플러그인 정보는 webview_flutter | Flutter Package (pub.dev) 에서 확인 할 수 있다.

아직 많은 사이트들이 https 가 아닌 http 만 지원하는 곳도 있을 수 있다. 따라서 http 프로토콜을 허용하도록 설정한다.

 - android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_sample">

   <uses-permission android:name="android.permission.INTERNET" />

   <application
        android:label="flutter_sample"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:usesCleartextTraffic="true"> <!-- 여기 부분 추가 -->
        
       ....
       
</manifest>

 

다음 글에서 본격적으로 웹 앱을 만들어 보도록 하자!!

반응형
반응형

1. 콜백 함수

 - 일정 작업이 완료되면 실행되는 함수

 - 함수를 정의하고 바로 실행되는 것이 아니라 특정 조건이 됐을 때 실행되는 함수

 - ex) 사용자가 화면을 터치 했을 때 실행할 함수, 웹뷰의 로딩이 완료됐을 때 실행할 콜백 함수를 정의 할 수 있다.

WebView(
	initialUrl: 'https://childeye.tistroy.com',
    javascriptMode: JavascriptMode.unrestricted,
    
    onPageFinished: (String url) {
    	print(url);
    },
}

onPageFinished() 함수는 웹뷰에서 페이지 로딩이 완료된 뒤에 실행되는 콜백 함수이다.

첫 번째 매개변수로 로딩된 페이지의 url 전달한다.

페이지가 로딩 후 실행하고 싶은 작업이 있다면 함수 내부에 코드를 정의하면 된다.

WebView 위젯의 콜백함수는 onPageFinished 뿐 아니라 onWebViewCreated(), onPageStarted(), onProgress 등의 특정 조건이 성립됐을 때 실행되는 콜백 함수도 있다.

 

2. 웹뷰 위젯

웹뷰는 프레임워크에 내장된 브라우저를 앱의 네이티브 컴포넌트에 임베딩하는 기능이다.

엡에서 웹브라우저의 기능을 구현해 주는 것이다.

웹뷰는 네이티브 컴포넌트에 비해 속도가 느리고 애니메이션이 부자연스럽지만 기존에 만든 웹사이트를 손쉽게 활용할 수 있어서 사용한다.

이미 만들어진 웹사이트가 있다면 코드 몇 줄로 해당 사이트를 앱에서 웹뷰로 탑재해서 앱을 만들수 있다.

결제 모듈을 PG사에서 웹으로 이미 기능을 구현해두었기 때문에 웹뷰를 사용하면 결제 기능 개발을 별도로 구현할 필요가 없다.

 

웹뷰의 속성

 - initialUrl : 웹뷰에서 처음 실행할 웹사이트의 주소. 웹뷰가 포함된 위젯이 화면에 생성되면 웹뷰를 생성하고 initialUrl 사이트를 처음으로 실행한다.

 - javascriptMode : 웹뷰에서 자바스크립트 실행을 허용할지 여부를 결정.

   √ unrestricted : 자바스크립트를 제한 없이 실행

    disable : 자바스크립트를 실행 할 수 없음.

 - onWebViewCreated : 웹뷰 위젯이 생성되면 실행할 콜백 함수. 매개 변수로 WebViewController 를 전달하며 뒤로가기, 앞으로 가기, 새로운 URL 실행하기 등 기능을 조작할 수 있다.

 - onPageStarted : 웹뷰가 처음 생성되거나 페이지를 이동했을 때 웹페이지가 로딩되기 시작하면 실행할 콜백 함수. 매개변수로 로딩이 시작된 페이지의 URL 이 전달된다.

 - onPageFinished : 웹페이지 로딩이 끝나면 실행. 로딩이 완료된 웹페이지의 URL 이 매개변수로 전달된다.

 - onProgress : 웹페이지가 로딩 중일 때 지속적으로 실행되며 페이지의 로딩이 끝날 때까지 실행된다. 매개변수로 페이지 로딩 상태가 0 ~ 100 사이의 int 값으로 전달된다.

반응형
반응형

이전 글에 이어서 앱에 배경을 넣어보자!!

이제까지 홈 화면에 글자를 출력하는 앱을 만들었다.

여기에 배경색을 입혀보자!!

 

1. 배경색을 화면 전체에 오렌지색으로 적용.

import 'package:flutter/material.dart';

void main() {
  runApp(
    SplashScreen(),
  );
}

class SplashScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          decoration: BoxDecoration(
            color: Colors.orange,
          ),
          child: Center(
            child: Text('Splash Screen'),
          ),
        ),
      ),
    );
  }
}

BoxDecoration 은 배경색, 테두리 색상, 테두리 두깨 등 컨테이너의 여러가지 UI 요소를 지정할 수 있다.

결과

 

2. 글자 대신 이미지를 출력해보자

Text 위젯 대신 Image 위젯을 사용

 - 앱에 저장된 이미지를 사용

 - [프로젝트 디렉토리]/assets 디렉토리를 생성한다.

 - 로고로 사용할 이미지를 [프로젝트 디렉토리]/assets/logo.png 파일에 저장.

 - 이미지 파일을 사용할 수 있도록 [프로젝트 디렉토리]/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

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
    - assets/

pubspec.yaml 파일을 변경 후에는 꼭 "Pub get" 을 하여 설정을 업데이트 한다.

 

글자 대신 이미지를 추가해보자.

import 'package:flutter/material.dart';

void main() {
  runApp(
    SplashScreen(),
  );
}

class SplashScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          decoration: BoxDecoration(
            color: Color(0xFFF99231),
          ),
          child: Center(
            child: Image.asset(
              'assets/logo.png'
            ),
          ),
        ),
      ),
    );
  }
}

글자 대신 로고 이미지가 나오는 걸 확인할 수 있다.

배경색도 로고 이미지와 동일한 색상인 0xFFF99231 로 변경하였다.

 

3. 위젯 정렬하기

 - 로딩 애니메이션 위젯을 로고 이미지 밑에 보이도록 해보자.

 - 세로로 정렬하기 위해 Column 위젯을 사용한다.

import 'package:flutter/material.dart';

void main() {
  runApp(
    SplashScreen(),
  );
}

class SplashScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          decoration: BoxDecoration(
            color: Color(0xFFF99231),
          ),
          child: Column(
            children: [
              Image.asset('assets/logo.png'),
              CircularProgressIndicator(),
            ],
          ),
        ),
      ),
    );
  }
}

Column 위젯의 children 매개변수에 로고 이미지와 로딩 애미메이션을 추가한다.

CircularProgressIndicator 은 플러터에서 제공하는 둥근 로딩 애미메이션 위젯이다.

 

Column 위젯의 mainAxisAlignment 매개 변수를 이용하여 children 위젯들의 배치할 수 있다.

 - 가운데 위치 시키기 : mainAxisAlignment: MainAxisAlignment.center

 

Column 위젯은 세로로 최대한 크기를 차지한다. 가로로는 최소한의 크기만 차지한다.

이미지 로고의 가로 크기를 조정하면 전체 화면이 달라진다.

 

이런 경우에는 Row 위젯을 추가해야한다.

Row 위젯은 가로로는 최대의 크기를 차지하고 세로로는 최소의 크기를 차지한다.

 

이제 마지막으로, 로고 이미지를 가로 세로 가운데로 정렬시키고 로딩 애니메이션의 색깔도 하얀색으로 변경해보자.

import 'package:flutter/material.dart';

void main() {
  runApp(
    SplashScreen(),
  );
}

class SplashScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          decoration: BoxDecoration(
            color: Color(0xFFF99231),
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Image.asset(
                    'assets/logo.png',
                    width: 200,
                  ),
                  CircularProgressIndicator(
                    valueColor: AlwaysStoppedAnimation(
                      Colors.white,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

최종으로 만든 앱의 결과물이다!!

애니메이션으로 돌아가는 색상을 흰색으로 변경하기 위해 valueColor 매개변수를 사용한다.

valueColor 의 경우 색상이 애니메이션이 되어야 하기 때문에 Colors.white 를 직접 넣을 수 없다.

대신에 AlwaysStoppedAnimation 이라는 클래스에 감싸서 색상을 변경했다.

반응형

+ Recent posts