이번 글에서는 주사위 앱 만들기에 대해서 알아봅니다.
주사위 앱 레이아웃
- 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 화면으로 이동한 뒤 핸드폰을 더 강하게 흔들어야 난수가 발생하는지 확인합니다.
이상 플러터에서 주사위 앱을 만들고 실행까지 해보았습니다.