Menu
CoddyTech

Trắc nghiệm: Thanh tiến độ

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

"Câu hỏi 3 trên 5" cho biết bạn đang ở đâu. Thanh tiến trình đầy dần khi bạn làm bài sẽ giúp bạn nhìn thoáng qua là biết ngay. Đó là LinearProgressIndicator có value từ 0 (trống) đến 1 (đầy).

Không có value, thanh sẽ trượt qua lại mãi, như cách ứng dụng cho biết ứng dụng đang tải. Khi có value, thanh hiển thị tiến độ của bạn:

LinearProgressIndicator(
  value: (_index + 1) / questions.length,
  minHeight: 8,
  borderRadius: BorderRadius.circular(4),
)

Ở câu hỏi đầu tiên, giá trị là 1 / 5, tức một phần năm thanh tiến trình; ở câu cuối cùng, giá trị là 5 / 5, tức toàn bộ thanh. Trong Dart, / luôn cho kết quả kiểu double, nên 1 / 5 là 0.2, không phải 0.

minHeight làm thanh dày hơn độ dày mặc định là 4 pixel, còn borderRadius bo tròn hai đầu thanh để khớp với các nút.

challenge icon

Thử thách

Dễ

Bên dưới Question 1 of 5, sau khoảng trống 8 pixel, hãy thêm một LinearProgressIndicator:

  • thanh tiến trình đầy một phần năm ở câu hỏi 1 và đầy hoàn toàn ở câu hỏi 5
  • dày 8 pixel, hai đầu bo tròn 4 pixel

Giữ khoảng trống 24 pixel phía trước câu hỏi.

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: 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