[플러터] 주사위 앱 만들기
이전 내용
[플러터] D-Day 앱 만들기 (feat. 폰트 반영하기, Cupertino 위젯)
이전 내용 [플러터] 이미지 롤링 기능 구현하기이전 내용 [플러터] 웹 앱 만들어보기에러 관련 [플러터] 에러 발생 및 해결1. Gradle build failed to produce an .apk file...import 'package:flutter/material.dart';void ma
puppy-foot-it.tistory.com
◆ 에러 관련
[플러터] 에러 발생 및 해결
1. Gradle build failed to produce an .apk file...import 'package:flutter/material.dart';void main() { runApp( MaterialApp( // 머터리얼 디자인 위젯 home: Scaffold( // Scaffold 위젯 body: Center( // 가운데 정렬 child: Text( // Text 위젯 'He
puppy-foot-it.tistory.com
◆ 깃허브 업로드 관련
[플러터] 대용량 파일 깃허브 업로드하기 (안드로이드 스튜디오)
대용량 파일 깃허브 업로드(안드로이드 스튜디오) 플러터 공부를 하다가 만들어둔 작업물들을 깃허브에 올리려고 하는데, 파일 용량이 크고, 파일 갯수도 많아서 GUI로 업로드가 안 된다. 이전에
puppy-foot-it.tistory.com
주사위 앱 만들기
핸드폰을 흔들면 새로운 주사위를 뽑는 앱 만들기
- 두 개의 탭 만들기
- 첫 번째 탭: 주사위 숫자 보여주기 (난수 생성)
- 두 번째 탭: 흔들기의 민감도 정하기 (슬라이더)
파일 구조
- lib
/asset/img
/const
/screen
main.dart
- pubspec.yaml
코드 및 설명
◆ const/colors.dart
import 'package:flutter/material.dart';
const backgroundColor = Color(0xFF0E0E0E); // 배경색
const primaryColor = Colors.white; // 메인 색상
final secondaryColor = Colors.grey[600]; // 보조 색상
◆ screen/ home_screen.dart
import 'package:flutter/material.dart';
import 'package:random_dice/const/colors.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,
),
),
],
);
}
}
◆ screen/ root_screen.dart
import 'package:flutter/material.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();
}
// TickerProviderStateMixin 사용
class _RootScreenState extends State<RootScreen> with TickerProviderStateMixin {
TabController? controller; // 사용할 TabController 선언
double threshold = 2.5; // 민감도 기본값 설정
int number = 1; // 주사위 숫자
ShakeDetector? shakeDetector;
@override
void initState() {
super.initState();
controller = TabController(length: 2, vsync: this); // 컨트롤러 초기화
// 컨트롤러 속성 변경 시마다 실행될 함수 등록
controller!.addListener(tabListener);
shakeDetector = ShakeDectector.autoStart( // 흔들기 감지 즉시 시작
shakeSlopTimeMS: 100, // 감지 주기
shakeThresholdGravity: threshold, // 감지 민감도
onPhoneShake: onPhoneShake, // 감지 후 실행 함수
);
}
void onPhoneShake() { // 감지 후 실행 함수
final rand = new Random();
setState(() {
number = rand.nextInt(5) + 1;
});
}
tabListener() { // 리스너로 사용할 함수
setState(() {});
}
@override
dispose() {
controller!.removeListener(tabListener); // 리스너에 등록한 함수 등록 취소
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(
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: '설정',
),
],
);
}
}
◆ screen/ settings_screen.dart
import 'package:random_dice/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), // 표시되는 값
),
],
);
}
}
◆ main.dart
import 'package:flutter/material.dart';
import 'package:random_dice/screen/root_screen.dart';
import 'package:random_dice/const/colors.dart';
void main() {
runApp(
MaterialApp(
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(),
),
);
}
◆ pubspec.yaml
name: random_dice
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.8.0
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
shake: ^2.2.0 # 흔들림 감지 플러그인
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
# 의존성 덮어쓰기
dependency_overrides:
sensors_plus: 6.1.0 # sensors_plus 6.1.0 버전 덮어쓰기
flutter:
uses-material-design: true
assets:
- asset/img/
[참고]
코드팩토리의 플러터 프로그래밍
다음 내용