Menu
CoddyTech

퀴즈: 결과 메시지

Coddy Flutter 여정의 프로젝트 섹션에 포함된 레슨. 36개 중 18번째.

"5문제 중 5문제를 맞혔어요"라는 결과에는 단순한 한 줄 이상의 표현이 어울립니다. 큰 아이콘과 결과에 맞는 메시지로 퀴즈를 마무리하세요. 만점을 받았다면 트로피를, 대부분 맞혔다면 엄지손가락을 치켜든 아이콘을, 그 외에는 격려의 메시지를 보여 주세요.

세 가지 결과를 가장 좋은 것부터 확인하는 방식은 if / else if / else입니다. 순서가 중요합니다. 만점도 절반보다 많이 맞힌 경우에 해당하므로 만점인지 먼저 확인하세요.

if (score == total) {
  message = 'Perfect!';
  icon = Icons.emoji_events;
} else if (score * 2 >= total) {
  message = 'Well done!';
  icon = Icons.thumb_up;
} else {
  message = 'Keep practicing!';
  icon = Icons.school;
}

score * 2 >= total은 반올림 없이 "절반 이상"을 뜻합니다. 5문제 중 3문제는 통과하고, 2문제는 통과하지 못합니다.

if 앞에서 message, icon, color를 값 없이 선언하세요. Dart는 사용하기 전에 모든 분기에서 각 변수에 값이 할당되는지 확인합니다.

이것이 퀴즈의 마지막 수업입니다. 테스트에서는 색상, 점수, 결과, 다시 플레이하기까지 전체 라운드를 진행합니다.

challenge icon

챌린지

중급

결과 화면에서 점수 위에는 다음이 표시됩니다.

  • 만점이면 Perfect!와 호박색 Icons.emoji_events가 표시됩니다.
  • 절반 이상이면 Well done!과 초록색 Icons.thumb_up이 표시됩니다.
  • 절반 미만이면 Keep practicing!과 테마의 기본 색상으로 표시되는 Icons.school이 표시됩니다.

아이콘은 96픽셀이고, 그다음 16픽셀의 간격, headlineMedium 스타일의 메시지, 8픽셀의 간격, 그리고 이제 titleMedium 스타일로 표시되는 점수 줄이 이어집니다.

직접 해보기

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const QuizScreen(),
    );
  }
}

class Question {
  const Question(this.text, this.answers, this.correct);

  final String text;
  final List<String> answers;
  final int correct;
}

const List<Question> questions = [
  Question('Which widget stacks its children from top to bottom?', ['Row', 'Column', 'Stack', 'Wrap'], 1),
  Question('Which language do you write Flutter apps in?', ['Kotlin', 'Swift', 'Dart', 'JavaScript'], 2),
  Question('What does setState do?', ['Rebuilds the widget', 'Saves the app', 'Closes the screen', 'Plays a sound'], 0),
  Question('Which widget shows a list you can scroll?', ['Container', 'SizedBox', 'Padding', 'ListView'], 3),
  Question('Which widget adds space around its child?', ['Padding', 'Center', 'Icon', 'Text'], 0),
];

class QuizScreen extends StatefulWidget {
  const QuizScreen({super.key});

  @override
  State<QuizScreen> createState() => _QuizScreenState();
}

class _QuizScreenState extends State<QuizScreen> {
  int _index = 0;
  int? _picked;
  int _score = 0;

  Question get _question => questions[_index];
  bool get _answered => _picked != null;
  bool get _isLast => _index == questions.length - 1;

  void _pick(int answer) {
    if (_answered) return;
    setState(() {
      _picked = answer;
      if (answer == _question.correct) {
        _score++;
      }
    });
  }

  Future<void> _next() async {
    if (!_isLast) {
      setState(() {
        _index++;
        _picked = null;
      });
      return;
    }
    await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => ResultsScreen(score: _score, total: questions.length),
      ),
    );
    setState(() {
      _index = 0;
      _picked = null;
      _score = 0;
    });
  }

  Color? _colorFor(int answer) {
    if (!_answered) return null;
    if (answer == _question.correct) return Colors.green;
    if (answer == _picked) return Colors.red;
    return null;
  }

  @override
  Widget build(BuildContext context) {
    final question = _question;
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Quiz'),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Center(
              child: Text('Score: $_score', style: textTheme.titleMedium),
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              'Question ${_index + 1} of ${questions.length}',
              style: textTheme.labelLarge,
            ),
            const SizedBox(height: 8),
            LinearProgressIndicator(
              value: (_index + 1) / questions.length,
              minHeight: 8,
              borderRadius: BorderRadius.circular(4),
            ),
            const SizedBox(height: 24),
            Text(question.text, style: textTheme.headlineSmall),
            const SizedBox(height: 24),
            for (int i = 0; i < question.answers.length; i++)
              Padding(
                padding: const EdgeInsets.only(bottom: 12),
                child: FilledButton.tonal(
                  style: FilledButton.styleFrom(
                    backgroundColor: _colorFor(i),
                    foregroundColor: _colorFor(i) == null ? null : Colors.white,
                    padding: const EdgeInsets.all(16),
                  ),
                  onPressed: () => _pick(i),
                  child: Text(question.answers[i]),
                ),
              ),
            if (_answered) ...[
              const SizedBox(height: 12),
              Text(
                _picked == question.correct
                    ? 'Correct!'
                    : 'The answer is ${question.answers[question.correct]}',
                textAlign: TextAlign.center,
                style: textTheme.titleMedium,
              ),
              const SizedBox(height: 16),
              FilledButton(
                onPressed: _next,
                child: Text(_isLast ? 'See results' : 'Next'),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

class ResultsScreen extends StatelessWidget {
  const ResultsScreen({super.key, required this.score, required this.total});

  final int score;
  final int total;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Results')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                'You scored $score out of $total',
                style: Theme.of(context).textTheme.headlineSmall,
              ),
              const SizedBox(height: 32),
              FilledButton.icon(
                onPressed: () => Navigator.pop(context),
                icon: const Icon(Icons.replay),
                label: const Text('Play again'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

프로젝트의 모든 레슨

직접 연습해 보세요: 온라인 Flutter 컴파일러