728x90
728x90
프로젝트 사이에 시간이 비어서 뭘 해볼까 하는 생각에 내기할때 내가 무조껀 이긴다면??
하는 막연한 생각에 만들어볼까? 해서 시작한 자그마한 어플개발입니다.
(※ 전체 코드는 마지막에 있습니다)
🏷️ 먼저 개발에 사용되었던 버전 정보들입니다.
FVM version: 3.2.1
Flutter version: 3.13.9
Dart version: 3.1.5
Git version: 2.47.0.windows.2
Java version: OpenJDK 11 2018-09-25
1. 기본 구조
🧑💻main() 함수에서 MyApp 위젯을 실행
- MyApp은 앱의 테마와 홈 화면을 설정하는 기본 구조를 제공
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
// MaterialApp을 반환하는 기본 앱 구조
}
2. 홈 화면 구현
2.1 애니매이션 컨트롤러
🧑💻터치 시 나타나는 리플 효과를 위한 애니메이션 설정(300ms 동안 실행되는 애니메이션)
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
}
2.2 터치 이벤트 처리
🧑💻터치 위치를 저장하고 애니메이션을 시작
void _handleTapDown(TapDownDetails details) async {
setState(() {
_showRipple = true;
_tapPosition = details.localPosition;
});
_animationController.forward();
}
3. UI 구성
3.1 메인화면 구성
🧑💻민트색 배경의 메인 화면
- 중앙에 "Betting Start!" 텍스트 표시
- 터치 시 리플 효과 표시
Scaffold(
backgroundColor: const Color(0xFF98E4D8), // 민트색 배경
body: GestureDetector(
// 터치 이벤트 처리
child: Stack(
children: [
// 중앙 텍스트
// 리플 효과
],
),
),
)
3.2 설정버튼( FloatingActionButton)
🧑💻 작은 크기의 흰색 플로팅 버튼
- 터치 시 설정 모달을 표시
floatingActionButton: SizedBox(
width: 25,
height: 25,
child: FloatingActionButton(
onPressed: () async {
// 설정 모달 표시
},
),
)
3.3 설정모달
🧑💻바텀 시트 형태의 설정 모달
- 다크 테마 적용 (배경색: #1C1C1E)
- 앱 버전 정보 표시 (package_info_plus 사용)
showModalBottomSheet(
// 설정 화면 UI
child: Column(
children: [
// 헤더 ('설정' 텍스트와 '완료' 버튼)
// 앱 버전 정보
],
),
)
2. 실행화면
더보기
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bet',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF98E4D8),
),
useMaterial3: true,
),
home: const MyHomePage(title: 'Bet'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
// final int _betAmount = 0;
bool _showRipple = false;
Offset _tapPosition = Offset.zero;
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _handleTapDown(TapDownDetails details) async {
setState(() {
_showRipple = true;
_tapPosition = details.localPosition;
});
// await Vibration.vibrate(duration: 50);
_animationController.forward();
}
void _handleTapUp(TapUpDetails details) {
_animationController.reverse().then((_) {
setState(() {
_showRipple = false;
});
});
}
void _handleTapCancel() {
_animationController.reverse().then((_) {
setState(() {
_showRipple = false;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF98E4D8),
body: GestureDetector(
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onTapCancel: _handleTapCancel,
child: Stack(
children: [
const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Betting Start!',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
if (_showRipple)
Positioned(
left: _tapPosition.dx - 25,
top: _tapPosition.dy - 25,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: 50 * _animation.value,
height: 50 * _animation.value,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.3),
border: Border.all(
color: Colors.white,
width: 2,
),
),
);
},
),
),
],
),
),
floatingActionButton: SizedBox(
width: 25,
height: 25,
child: FloatingActionButton(
onPressed: () async {
final packageInfo = await PackageInfo.fromPlatform();
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return FractionallySizedBox(
heightFactor: 0.9,
widthFactor: 1.0,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Color(0xFF1C1C1E),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.grey[600],
borderRadius: BorderRadius.circular(2),
),
),
),
Stack(
alignment: Alignment.center,
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: 12,
),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color(0xFF3C3C3E),
width: 1,
),
),
),
child: const Center(
child: Text(
'설정',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
Positioned(
right: 0,
child: TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text(
'완료',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF0A84FF),
),
),
),
),
],
),
const SizedBox(height: 30),
const Text(
'앱 정보',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 15),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
color: const Color(0xFF2C2C2E),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
const Icon(
Icons.info_outline,
color: Colors.white,
size: 20,
),
const SizedBox(width: 12),
Text(
'버전 ${packageInfo.version}',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
),
],
),
),
],
),
),
);
},
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
);
},
backgroundColor: Colors.white,
elevation: 2,
shape: const CircleBorder(),
child: Container(),
),
),
);
}
}
재미로 만들어 보는 앱 개발 일지 첫번쨰 장입니다.
완료되는 그날까지!!
728x90
300x250