[Flutter] 스나이퍼팩토리 Flutter 주간평가 (3주차)

GONG·2023년 4월 8일
0

3주차 주간과제 (퀴즈 앱)

Output

Data

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": ["녹색", "흰색", "노란색", "파란색"]
  }
];

Requirements

  1. 주어진 데이터를 활용하여 퀴즈앱을 만듭니다.

  2. 앱 배경색은 다음의 색상들을 Gradient로 표시합니다.

    • Colors.pinkAccent
    • Colors.blue
  3. 퀴즈 위젯은 퀴즈 데이터 수 만큼 생성되며, 데이터에 퀴즈 데이터를 추가할 때 추가된 퀴즈도 위젯으로 되어 보여집니다.

  4. 각 퀴즈의 보기들 중 하나를 클릭하였을 경우, 상단에 스코어가 위젯형태로 표시되며, 다음 페이지로 넘어갑니다.

    • 정답시 Icon(Icons.circle_outlined) 가 추가됩니다.
    • 오답시 Icon(Icons.close) 가 추가됩니다.
  5. 모든 문제를 풀었을 경우, 하단에 FAB가 표시되며 이 때 표시되는 위젯은 Icon(Icons.refresh)가 됩니다.

    • 해당 버튼을 클릭하면, 모든 스코어가 초기화되며 첫 퀴즈로 이동됩니다.
  6. 제공되는 일부 코드가 있습니다.

    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) {
    			...
    • 위 코드를 분석하고 어떠한 코드인지, 어떠한 부분이 생략되었는지 고민하여 과제 해결에 사용할 수 있습니다.
  7. 그 외 UI 디자인은 자유입니다.

    • 폰트, 이미지의 사용, 글씨 크기 및 자간 등, 퀴즈 색상 자유

제공되는 코드

  • 제공되는 코드는 퀴즈 pageView 안에 사용할 커스텀위젯의 코드 일부입니다.
    • Map<String, dynamic> 타입의 quiz와 문제의 정답을 판별할 onCorrect, onIncorrect 함수가 파라미터로 정의되어 있습니다.

코드

  • 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,
      );
    }
    }

    Screenshot_1680928399.png

❗PageController.page cannot be accessed before a PageView is built with it.

  • pageControllerPageView위젯과 함께 생성되기 전에 접근되었기 때문에 발생한 것

  • 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,
        );
      }
    }

profile
우와재밋다

0개의 댓글