Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- reference
- changenotifierprovider
- borderRadius
- enum
- setstate
- user
- switch
- multiprovider
- swift 문법
- provider
- 문법
- runTransaction
- Firebase
- changenotifier
- Stream
- datetime
- platformexception
- Swift
- signout
- Snapshot
- Navigator
- transform
- snackbar
- Camera
- controller
- divider
- permission
- ListView.builder
- globalkey
- consumer
Archives
- Today
- Total
코딩하는 제리
[Flutter/MiniProject] 퀴즈앱 UI 본문
소스코드 및 pubspec.yaml
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/screens/welcome/welcome_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Quiz App',
theme: ThemeData.dark(),
home: WelcomeScreen(),
);
}
}
// lib/constants.dart
import 'package:flutter/material.dart';
const kSecondaryColor = Color(0xFF8B94BC);
const kGreenColor = Color(0xFF6AC259);
const kRedColor = Color(0xFFE92E30);
const kGrayColor = Color(0xFFC1C1C1);
const kBlackColor = Color(0xFF101010);
const kPrimaryGradient = LinearGradient(
colors: [Color(0xFF46A0AE), Color(0xFF00FFCB)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
);
const double kDefaultPadding = 20.0;
// lib/models/Questions.dart
class Question {
final int id, answer;
final String question;
final List<String> options;
Question({this.id, this.question, this.answer, this.options});
}
const List sample_data = [
{
"id": 1,
"question":
"Flutter is an open-source UI software development kit created by ______",
"options": ['Apple', 'Google', 'Facebook', 'Microsoft'],
"answer_index": 1,
},
{
"id": 2,
"question": "When google release Flutter.",
"options": ['Jun 2017', 'Jun 2017', 'May 2017', 'May 2018'],
"answer_index": 2,
},
{
"id": 3,
"question": "A memory location that holds a single letter or number.",
"options": ['Double', 'Int', 'Char', 'Word'],
"answer_index": 2,
},
{
"id": 4,
"question": "What command do you use to output data to the screen?",
"options": ['Cin', 'Count>>', 'Cout', 'Output>>'],
"answer_index": 2,
},
];
// lib/controllers/question_controller.dart
import 'package:flutter/animation.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/models/Questions.dart';
import 'package:quiz_app_jerry/screens/welcome/score/score_screen.dart';
class QuestionController extends GetxController
with SingleGetTickerProviderMixin {
AnimationController _animationController;
Animation _animation;
Animation get animation => this._animation;
PageController _pageController;
PageController get pageController => this._pageController;
List<Question> _questions = sample_data
.map((question) => Question(
id: question['id'],
question: question['question'],
options: question['options'],
answer: question['answer_index'],
))
.toList();
List<Question> get questions => this._questions;
bool _isAnswered = false;
bool get isAnswered => this._isAnswered;
int _correctAns;
int get correctAns => this._correctAns;
int _selectedAns;
int get selectedAns => this._selectedAns;
// for more about obs please check documentation
RxInt _questionNumber = 1.obs;
RxInt get questionNumber => this._questionNumber;
int _numOfCorrectAns = 0;
int get numOfCorrectAns => this._numOfCorrectAns;
@override
void onInit() {
_animationController =
AnimationController(duration: Duration(seconds: 60), vsync: this);
_animation = Tween<double>(begin: 0, end: 1).animate(_animationController)
..addListener(() {
// update like setState
update();
});
// start our animation
// Once 60s is completed go to the next question
_animationController.forward().whenComplete(nextQuestion);
_pageController = PageController();
super.onInit();
}
// called just before the Controller is deleted from memory
@override
void onClose() {
super.onClose();
_animationController.dispose();
_pageController.dispose();
}
void checkAns(Question question, int selectedIndex) {
// because once user press any option then it will run
_isAnswered = true;
_correctAns = question.answer;
_selectedAns = selectedIndex;
if (_correctAns == _selectedAns) _numOfCorrectAns++;
// It will stop the counter
_animationController.stop();
update();
Future.delayed(Duration(seconds: 2), () {
nextQuestion();
});
}
void nextQuestion() {
if (_questionNumber.value != _questions.length) {
_isAnswered = false;
_pageController.nextPage(
duration: Duration(milliseconds: 250), curve: Curves.ease);
// Reset the counter
_animationController.reset();
// Then start it again
// Once timer is finish go to the next question
_animationController.forward().whenComplete(nextQuestion);
} else {
Get.to(() => ScoreScreen());
}
}
void updateTheQnNum(int index) {
_questionNumber.value = index + 1;
}
}
// lib/screens/welcome/welcome_screen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/constants.dart';
import 'package:quiz_app_jerry/screens/welcome/quiz/quiz_screen.dart';
import 'package:websafe_svg/websafe_svg.dart';
class WelcomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
WebsafeSvg.asset('assets/icons/bg.svg'),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(flex: 2), // 2/6
Text(
"Let's Play Quiz,",
style: Theme.of(context).textTheme.headline4.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text('Enter your informations below'),
Spacer(), // 1/6
TextField(
decoration: InputDecoration(
hintText: 'Full Name',
filled: true,
fillColor: Color(0xFF1C2341),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
),
Spacer(), // 1/6
InkWell(
onTap: () => Get.to(QuizScreen()),
child: Container(
width: double.infinity,
alignment: Alignment.center,
padding: EdgeInsets.all(kDefaultPadding * 0.75),
// 15
decoration: BoxDecoration(
gradient: kPrimaryGradient,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: Text(
'Lets Start Quiz',
style: Theme.of(context)
.textTheme
.button
.copyWith(color: Colors.black),
),
),
),
Spacer(flex: 2), // it will take 2/6
],
),
),
),
],
),
);
}
}
// lib/screens/welcome/quiz/quiz_screen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/controllers/question_controller.dart';
import 'components/body.dart';
class QuizScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
QuestionController _controller = Get.put(QuestionController());
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
TextButton(
onPressed: _controller.nextQuestion,
child: Text(
'Skip',
style: TextStyle(color: Colors.white),
),
),
],
),
body: Body(),
);
}
}
// lib/screens/welcome/quiz/components/body.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/constants.dart';
import 'package:quiz_app_jerry/controllers/question_controller.dart';
import 'package:websafe_svg/websafe_svg.dart';
import 'progress_bar.dart';
import 'question_card.dart';
class Body extends StatelessWidget {
const Body({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
QuestionController _questionController = Get.put(QuestionController());
return Stack(
children: [
WebsafeSvg.asset('assets/icons/bg.svg', fit: BoxFit.fill),
SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: kDefaultPadding),
child: ProgressBar(),
),
SizedBox(height: kDefaultPadding),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: kDefaultPadding),
child: Obx(
() => Text.rich(
TextSpan(
text:
'Question ${_questionController.questionNumber.value}',
style: Theme.of(context)
.textTheme
.headline4
.copyWith(color: kSecondaryColor),
children: [
TextSpan(
text: '/${_questionController.questions.length}',
style: Theme.of(context)
.textTheme
.headline5
.copyWith(color: kSecondaryColor),
),
],
),
),
),
),
Divider(thickness: 1.5),
SizedBox(height: kDefaultPadding),
Expanded(
child: PageView.builder(
physics: NeverScrollableScrollPhysics(),
controller: _questionController.pageController,
onPageChanged: _questionController.updateTheQnNum,
itemCount: _questionController.questions.length,
itemBuilder: (context, index) => QuestionCard(
question: _questionController.questions[index],
),
),
),
],
),
),
],
);
}
}
// lib/screens/welcome/quiz/components/option.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/controllers/question_controller.dart';
import '../../../../constants.dart';
class Option extends StatelessWidget {
final String text;
final int index;
final VoidCallback press;
const Option({
Key key,
this.text,
this.index,
this.press,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<QuestionController>(
init: QuestionController(),
builder: (qnController) {
Color getTheRightColor() {
if (qnController.isAnswered) {
if (index == qnController.correctAns) {
return kGreenColor;
} else if (index == qnController.selectedAns &&
qnController.selectedAns != qnController.correctAns) {
return kRedColor;
}
}
return kGrayColor;
}
IconData getTheRightIcon() {
return getTheRightColor() == kRedColor ? Icons.close : Icons.done;
}
return InkWell(
onTap: press,
child: Container(
margin: EdgeInsets.only(top: kDefaultPadding),
padding: EdgeInsets.all(kDefaultPadding),
decoration: BoxDecoration(
border: Border.all(color: getTheRightColor()),
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${index + 1} $text',
style: TextStyle(
color: getTheRightColor(),
fontSize: 16,
),
),
Container(
height: 26,
width: 26,
decoration: BoxDecoration(
color: getTheRightColor() == kGrayColor
? Colors.transparent
: getTheRightColor(),
borderRadius: BorderRadius.circular(50),
border: Border.all(color: getTheRightColor()),
),
child: getTheRightColor() == kGrayColor
? null
: Icon(getTheRightIcon(), size: 16),
),
],
),
),
);
});
}
}
// lib/screens/welcome/quiz/components/progress_bar.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/controllers/question_controller.dart';
import 'package:websafe_svg/websafe_svg.dart';
import '../../../../constants.dart';
class ProgressBar extends StatelessWidget {
const ProgressBar({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 35,
decoration: BoxDecoration(
border: Border.all(color: Color(0xFF3F4768), width: 3),
borderRadius: BorderRadius.circular(50),
),
child: GetBuilder<QuestionController>(
init: QuestionController(),
builder: (controller) {
return Stack(
children: [
LayoutBuilder(
builder: (context, constraints) => Container(
// from 0 to 1 it takes 60s
width: constraints.maxWidth * controller.animation.value,
decoration: BoxDecoration(
gradient: kPrimaryGradient,
borderRadius: BorderRadius.circular(50),
),
),
),
Positioned.fill(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: kDefaultPadding / 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${(controller.animation.value * 60).round()} sec'),
WebsafeSvg.asset('assets/icons/clock.svg'),
],
),
),
),
],
);
},
),
);
}
}
// lib/screens/welcome/quiz/components/question_card.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/controllers/question_controller.dart';
import 'package:quiz_app_jerry/models/Questions.dart';
import '../../../../constants.dart';
import 'option.dart';
class QuestionCard extends StatelessWidget {
final Question question;
const QuestionCard({
Key key,
@required this.question,
}) : super(key: key);
@override
Widget build(BuildContext context) {
QuestionController _controller = Get.put(QuestionController());
return Container(
margin: EdgeInsets.symmetric(horizontal: kDefaultPadding),
padding: EdgeInsets.all(kDefaultPadding),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
),
child: Column(
children: [
Text(
question.question,
style: Theme.of(context)
.textTheme
.headline6
.copyWith(color: kBlackColor),
),
SizedBox(height: kDefaultPadding / 2),
...List.generate(
question.options.length,
(index) => Option(
index: index,
text: question.options[index],
press: () {
_controller.checkAns(question, index);
},
),
),
],
),
);
}
}
// lib/screens/welcome/score/score_screen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app_jerry/constants.dart';
import 'package:quiz_app_jerry/controllers/question_controller.dart';
import 'package:websafe_svg/websafe_svg.dart';
class ScoreScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
QuestionController _qnController = Get.put(QuestionController());
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
WebsafeSvg.asset('assets/icons/bg.svg', fit: BoxFit.fill),
Column(
children: [
Spacer(flex: 3),
Text(
'Score',
style: Theme.of(context)
.textTheme
.headline3
.copyWith(color: kSecondaryColor),
),
Spacer(),
Text(
'${_qnController.numOfCorrectAns * 10}/${_qnController.questions.length * 10}',
style: Theme.of(context)
.textTheme
.headline4
.copyWith(color: kSecondaryColor),
),
Spacer(flex: 3),
],
),
],
),
);
}
}
'Flutter > MiniProject' 카테고리의 다른 글
[Flutter/MiniProject] 심플한 날씨앱 (0) | 2021.06.25 |
---|---|
[Flutter/MiniProject] 간단한 가계부 앱 (0) | 2021.03.26 |
[Flutter/MiniProject] 채팅앱 UI (0) | 2021.03.19 |
[Flutter/MiniProject] 간단한 채팅앱 UI (0) | 2021.03.17 |
[Flutter/MiniProject] (구) Netflix Clone (0) | 2021.03.17 |
Comments