List<Map<String, dynamic>> quizs = [
{
"question": "의학적으로 얼굴과 머리를 구분하는 기준은 어디일까요?",
"answer": 2,
"options": ["코", "눈썹", "귀", "머리카락"]
},
{
"question": "다음 중 바다가 아닌 곳은?",
"answer": 3,
"options": ["카리브해", "오호츠크해", "사해", "지중해"]
},
{
"question": "심청이의 아버지 심봉사의 이름은?",
"answer": 2,
"options": ["심전도", "심학규", "심한길", "심은하"]
},
{
"question": "심청전에서 심청이가 빠진 곳은 어디일까요?",
"answer": 4,
"options": ["정단수", "육각수", "해모수", "인당수"]
},
{
"question": "택시 번호판의 바탕색은?",
"answer": 3,
"options": ["녹색", "흰색", "노란색", "파란색"]
}
];
주어진 데이터를 활용하여 퀴즈앱을 만듭니다.
앱 배경색은 다음의 색상들을 Gradient로 표시합니다.
퀴즈 위젯은 퀴즈 데이터 수 만큼 생성되며, 데이터에 퀴즈 데이터를 추가할 때 추가된 퀴즈도 위젯으로 되어 보여집니다.
각 퀴즈의 보기들 중 하나를 클릭하였을 경우, 상단에 스코어가 위젯형태로 표시되며, 다음 페이지로 넘어갑니다.
모든 문제를 풀었을 경우, 하단에 FAB가 표시되며 이 때 표시되는 위젯은 Icon(Icons.refresh)가 됩니다.
제공되는 일부 코드가 있습니다.
class QuizCard extends StatelessWidget {
const QuizCard({super.key, required this.quiz, required this.onCorrect, required this.onIncorrect});
final Map<String, dynamic> quiz;
final Function onCorrect;
final Function onIncorrect;
Widget build(BuildContext context) {
...
그 외 UI 디자인은 자유입니다.
quiz.dart
List<Map<String, dynamic>> quizs = [
{
"question": "의학적으로 얼굴과 머리를 구분하는 기준은 어디일까요?",
"answer": 2,
"options": ["코", "눈썹", "귀", "머리카락"]
},
{
"question": "다음 중 바다가 아닌 곳은?",
"answer": 3,
"options": ["카리브해", "오호츠크해", "사해", "지중해"]
},
{
"question": "심청이의 아버지 심봉사의 이름은?",
"answer": 2,
"options": ["심전도", "심학규", "심한길", "심은하"]
},
{
"question": "심청전에서 심청이가 빠진 곳은 어디일까요?",
"answer": 4,
"options": ["정단수", "육각수", "해모수", "인당수"]
},
{
"question": "택시 번호판의 바탕색은?",
"answer": 3,
"options": ["녹색", "흰색", "노란색", "파란색"]
}
];
Main.dart
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, file_names
import 'package:flutter/material.dart';
import 'QuizPage.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: QuizPage(),
);
}
}
QuizCard.dart
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, file_names
import 'package:flutter/material.dart';
class QuizCard extends StatelessWidget {
const QuizCard({Key? key, required this.quiz, required this.onCorrect, required this.onIncorrect}) : super(key: key);
final Map<String, dynamic> quiz;
final Function onCorrect;
final Function onIncorrect;
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15)
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(quiz['question'], style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),),
),
Column(
children: List.generate(
quiz['options'].length, (index) => ElevatedButton(
// ElevatedButton 크기 고정
style: ElevatedButton.styleFrom(
minimumSize: Size(250, 36),
),
onPressed: (){
// 누른 버튼의 option 값이 정답과 같은지 판별
quiz['options'][index] == quiz['options'][quiz['answer']-1] ? onCorrect() : onIncorrect();
},
child: Text(quiz['options'][index])
)
),
)
],
),
),
);
}
}
QuizPage.dart
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, file_names
import 'package:first_app/homework/week3/quiz_app/QuizCard.dart';
import 'package:flutter/material.dart';
import 'quiz.dart';
class QuizPage extends StatefulWidget {
const QuizPage({Key? key}) : super(key: key);
State<QuizPage> createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
var pageController = PageController(viewportFraction: 0.8);
List<Widget> score = [];
// List<Widget> score = List<Widget>.filled(quizs.length, Icon(null));
// 버튼 눌렀을 때 정답인지 아닌지 판별하고, 판별 결과에 따라 appBar에 아이콘을 추가할 함수들
void onCorrect() {
setState(() {
pageController.nextPage(duration: Duration(seconds: 2), curve: Curves.linear);
// int pageIndex = pageController.page!.toInt();
// score[pageIndex] = Icon(Icons.circle_outlined);
score.add(Icon(Icons.circle_outlined));
});
}
void onIncorrect() {
setState(() {
pageController.nextPage(duration: Duration(seconds: 2), curve: Curves.linear);
// int pageIndex = pageController.page!.toInt();
// score[pageIndex] = Icon(Icons.close);
score.add(Icon(Icons.close));
});
}
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: score,
),
leading: IconButton(
iconSize: 30,
icon: Icon(Icons.navigate_before),
onPressed: () {
pageController.previousPage(duration: Duration(seconds: 2), curve: Curves.linear);
},
),
actions: [
IconButton(
iconSize: 30,
icon: Icon(Icons.navigate_next),
onPressed: () {
pageController.nextPage(duration: Duration(seconds: 2), curve: Curves.linear);
},
),
],
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.pinkAccent,
Colors.blue
],
)
),
child: SafeArea(
child: PageView.builder(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
itemCount: quizs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 60, horizontal: 15),
child: QuizCard(quiz: quizs[index], onCorrect: onCorrect, onIncorrect: onIncorrect,),
);
},
),
),
),
floatingActionButton: score.length >= quizs.length ? FloatingActionButton(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
child: Icon(Icons.restart_alt),
onPressed: () {
setState(() {
pageController.jumpTo(0);
score.clear();
});
},
) : null,
);
}
}
❗PageController.page cannot be accessed before a PageView is built with it.
pageController
가 PageView
위젯과 함께 생성되기 전에 접근되었기 때문에 발생한 것
pageController에 대한 접근은 PageView 안에서 하는 것이 이상적임
현재 코드는 상단 AppBar에 있는 이전 버튼을 통해 이전 페이지로 이동해서 같은 문제를 다시 풀면 기존의 O,X아이콘이 수정되는 것이 아닌, 새로 아이콘이 등록됩니다.
기존의 아이콘을 수정하는 방식으로 구현해보려고 시도해보던 중에 갑작스러운 노트북의 건강악화(블루스크린... 안드로이드 에뮬레이터 이상... 코드를 칠 수 없을 정도로 버벅임... 등등)로 인해 어쩔 수 없이 시도를 멈추게 되었습니다...
주말에 노트북 초기화를 한번 해보고 노트북이 건강을 되찾게 된다면 다시 해결해보겠습니다......
score 리스트를 빈 리스트가 아닌 quizs.length만큼 Icon(null)을 채워서 초기화해두고, onCorrect, onIncorrect 메소드 내부에서 score에 접근할 때 페이지 컨트롤러의 페이지 인덱스로 접근 후 아이콘을 바꾸는 형식으로 수정
QuizPage.dart
import 'package:first_app/homework/week3/quiz_app/QuizCard.dart';
import 'package:flutter/material.dart';
import 'quiz.dart';
class QuizPage extends StatefulWidget {
const QuizPage({Key? key}) : super(key: key);
State<QuizPage> createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
var pageController = PageController(viewportFraction: 0.8);
List<Widget> score = List<Widget>.filled(quizs.length, Icon(null));
// 버튼 눌렀을 때 정답인지 아닌지 판별하고, 판별 결과에 따라 appBar에 아이콘을 추가할 함수들
void onCorrect() {
setState(() {
pageController.nextPage(duration: Duration(seconds: 2), curve: Curves.linear);
int pageIndex = pageController.page!.toInt();
score[pageIndex] = Icon(Icons.circle_outlined);
});
}
void onIncorrect() {
setState(() {
pageController.nextPage(duration: Duration(seconds: 2), curve: Curves.linear);
int pageIndex = pageController.page!.toInt();
score[pageIndex] = Icon(Icons.close);
});
}
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: score,
),
leading: IconButton(
iconSize: 30,
icon: Icon(Icons.navigate_before),
onPressed: () {
pageController.previousPage(duration: Duration(seconds: 2), curve: Curves.linear);
},
),
actions: [
IconButton(
iconSize: 30,
icon: Icon(Icons.navigate_next),
onPressed: () {
pageController.nextPage(duration: Duration(seconds: 2), curve: Curves.linear);
},
),
],
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.pinkAccent,
Colors.blue
],
)
),
child: SafeArea(
child: PageView.builder(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
itemCount: quizs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 60, horizontal: 15),
child: QuizCard(quiz: quizs[index], onCorrect: onCorrect, onIncorrect: onIncorrect,),
);
},
),
),
),
floatingActionButton: score.last != Icon(null) ? FloatingActionButton(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
child: Icon(Icons.restart_alt),
onPressed: () {
setState(() {
pageController.jumpTo(0);
score = List<Widget>.filled(quizs.length, Icon(null));
});
},
) : null,
);
}
}