반응형

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

 

주사위 앱 레이아웃

 - 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 화면으로 이동한 뒤 핸드폰을 더 강하게 흔들어야 난수가 발생하는지 확인합니다.

 

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

 

반응형

+ Recent posts