Menu
CoddyTech

Trắc nghiệm: Màn hình kết quả

Thuộc phần Dự án trong hành trình Flutter của Coddy. Bài học 16 trên 36.

Khi bạn trả lời câu hỏi cuối cùng, bài trắc nghiệm sẽ kết thúc trên một màn hình riêng hiển thị kết quả. Đó là widget thứ hai, ResultsScreen, và Navigator.push sẽ đưa bạn đến đó.

Màn hình kết quả cần hai con số: điểm số và tổng số câu hỏi. Màn hình nhận các giá trị này qua hàm khởi tạo, dưới dạng tham số có tên required:

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

  final int score;
  final int total;
  ...
}

Ở câu hỏi cuối cùng, nút sẽ hiển thị "Xem kết quả" thay vì "Tiếp theo". Cùng một phương thức _next xử lý cả hai trường hợp: ở câu hỏi cuối cùng, phương thức mở màn hình kết quả rồi kết thúc, nhờ đó chỉ số không vượt quá cuối danh sách.

if (_isLast) {
  Navigator.push(context, MaterialPageRoute(
    builder: (context) => ResultsScreen(score: _score, total: questions.length),
  ));
  return;
}
challenge icon

Thử thách

Trung bình

Kết thúc bài trắc nghiệm bằng màn hình kết quả:

  • viết một ResultsScreen có Scaffold, AppBar với tiêu đề Results và, ở giữa với phần đệm 24 pixel, You scored 3 out of 5 (với các con số thực tế) dùng headlineSmall của giao diện
  • hiển thị nút sau mỗi câu trả lời, có nhãn See results ở câu hỏi cuối cùng và Next ở các câu hỏi còn lại
  • ở câu hỏi cuối cùng, _next mở màn hình kết quả

Tự làm thử

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++;
      }
    });
  }

  void _next() {
    setState(() {
      _index++;
      _picked = null;
    });
  }

  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),
              if (!_isLast)
                FilledButton(
                  onPressed: _next,
                  child: const Text('Next'),
                ),
            ],
          ],
        ),
      ),
    );
  }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Dự án

Tự luyện tập: Trình biên dịch Flutter online