Flutter:如何阻止状态改变 UI

Flutter: How to stop state from changing UI

我有一些本地 JSON 数据已解析到我的应用程序中。数据包含一些数组和数学问题(构建测验应用程序),因此基于数学,我在 ListView 构建器中显示了一些章节,当单击其中一个时,它会打开一个新屏幕并传递特定数据根据章节索引获取该章节。

在我的测验屏幕中,数组中的数学问题来自 JSON 数据,具体取决于它是哪一章,我将问题打乱,以便它们以随机顺序出现,问题是当用户尝试在提供的 TextField 中回答问题,键盘按预期打开,但 UI 的状态发生变化,这也改变了当前显示的问题。我该如何阻止它?

要查看我在应用程序中遇到的问题,请观看视频 link:https://youtu.be/n_VeT26I8NE

JSON 数据已从不同的屏幕解析并设置在这些变量中:

List quiz = widget.newData["quiz"];
//Shuffle questions
var randomQuiz = (quiz..shuffle()).first;

如果你更喜欢阅读来自 GitHub 的 Dart 代码:https://github.com/BotsheloRamela/classio/blob/main/quiz_screen.dart

部分JSON数据:

[
  {
    "chapter": "Exponents & Surds",
    "introduction": {
    "description": "A number's exponent determines how many times to multiply it. 
    Exponents can also be called powers or indices. In mathematics, a surd is a value 
    that cannot be further simplified into a whole number or integer. They are 
    irrational numbers.",
        "videoID": ["568dGLFTom8", "XZRQhkii0h0"]
    },
    "quiz": [
        {
         "question": "Simplify the follwoing.",
         "sum": "2a^2.3a^2.b^0",
         "answer": "6a^5",
         "explaination": ""
        },
        {
         "question": "Rewrite the following with prime bases, your answer should be a fraction.",
         "sum": "16.8^{-4}",
         "answer": "1/256",
         "explaination": ""
        },
        {
         "question": "Solve the following, your answer should be a fraction.",
         "sum": "\frac{(-3a^3 b)^2}{a^5 b^3}",
         "answer": "9a/b",
         "explaination": ""
        },
        {
         "question": "Rewrite the following with prime bases and simplify, your answer should be a fraction.",
         "sum": "(\frac{2x^3}{8y^{-4}})^{-3}",
         "answer": "\frac{64}{x^9 y^12}",
         "explaination": ""
        }
    ]
    
 },

 {
  "chapter": "Equations & Inequalities",
  "introduction": {
  "description": "Equations and inequalities are both mathematical sentences made up of two expressions related to one another. The symbol = indicates that two expressions are regarded as equal within an equation. While in an inequality, two terms are not necessarily equal, which is indicated by the following symbols: >, <, ≤ or ≥.",
  "videoID": ["hJ-_OoCHTks"]
    },
    "quiz": [
        {
         "question": "Show that the following trinomial is a perfect square.",
         "sum": "x^2+2x+1",
         "answer": "(x+1)^2",
         "explaination": ""
        },
        {
         "question": "Show that the following trinomial is a perfect square.",
         "sum": "x^2-6x+9",
         "answer": "(x-3)^2",
         "explaination": ""
        },
        {
         "question": "Show that the following trinomial is a perfect square.",
         "sum": "x^2+8x+16",
         "answer": "(x+4)^2",
         "explaination": ""
        }
    ]
 },
]

飞镖代码:

import 'package:classio_students/bottom_nav.dart';
import 'package:classio_students/colors.dart';
import 'package:classio_students/screens/learn/maths_formula_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:page_transition/page_transition.dart';

// ignore: must_be_immutable
class Gr11MathQuiz extends StatefulWidget {
var newData;
Gr11MathQuiz({Key? key, required this.newData}) : super(key: key);

@override
_Gr11MathQuizState createState() => _Gr11MathQuizState();
}

class _Gr11MathQuizState extends State<Gr11MathQuiz> {
final TextEditingController _answerController = TextEditingController();
@override
void initState() {
super.initState();
//SystemChannels.textInput.invokeMethod('TextInput.show');
}

@override
Widget build(BuildContext context) {
//Creates a list from the JSON data & shuffles the data quiz in a random order everytime the state changes
List quiz = widget.newData["quiz"];
var randomQuiz = (quiz..shuffle()).first;
//Check answer function
checkAnswer() async {
  if (_answerController.text.toString() !=
      randomQuiz['answer'].toString()) {
    Fluttertoast.showToast(
        msg: 'Are you sure? Take a look at your answer.',
        gravity: ToastGravity.TOP,
        backgroundColor: Colors.orangeAccent,
        textColor: Colors.white);
  } else {
    Fluttertoast.showToast(
        msg: 'Nice!',
        gravity: ToastGravity.TOP,
        backgroundColor: Colors.green,
        textColor: Colors.white);
    Future.delayed(const Duration(seconds: 1), () {
      setState(() {});
      _answerController.clear();
    });
  }
}

final height = MediaQuery.of(context).size.height;
const shape = StadiumBorder();
return Scaffold(
    resizeToAvoidBottomInset: false,
    appBar: AppBar(
      title: const Text('Gr11 Maths Quiz',
          style: TextStyle(
              color: titleColor,
              letterSpacing: 1,
              fontSize: 20,
              fontWeight: FontWeight.bold)),
      leading: IconButton(
          onPressed: () => Navigator.pop(context),
          icon: const Icon(
            Icons.arrow_back,
            color: Colors.black,
            size: 30,
          )),
      actions: [
        IconButton(
            onPressed: () => Navigator.pushReplacement(
                context,
                MaterialPageRoute(
                    builder: (context) => const CustomNavBar())),
            icon: const Icon(
              Icons.home,
              color: Colors.black,
              size: 30,
            ))
      ],
      backgroundColor: Colors.white,
      centerTitle: true,
      elevation: 0,
    ),
    body: SizedBox(
      height: height,
      child: Flexible(
        child: Padding(
          padding: const EdgeInsets.all(13),
          child: Column(
            children: [
              Text(
                randomQuiz['question'].toString(),
                style: const TextStyle(color: Colors.black, fontSize: 17),
              ),
              const SizedBox(
                height: 30,
              ),
              //Sum
              Center(
                child: Math.tex(
                  randomQuiz['sum'].toString(),
                  mathStyle: MathStyle.display,
                  textStyle: const TextStyle(fontSize: 25),
                ),
              ),
              const SizedBox(
                height: 60,
              ),
              //Answer text
              const Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  'Answer:',
                  style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Colors.black,
                      fontSize: 20),
                ),
              ),
              const SizedBox(
                height: 10,
              ),
              //Answer textfield & check btn
              Row(
                children: [
                  //Textfield
                  Expanded(
                    child: TextField(
                      controller: _answerController,
                      style: const TextStyle(color: Colors.black),
                      decoration: InputDecoration(
                          hintText: 'Answer',
                          hintStyle: const TextStyle(color: Colors.grey),
                          labelStyle: const TextStyle(color: Colors.grey),
                          enabledBorder: OutlineInputBorder(
                              borderSide:
                                  const BorderSide(color: Colors.grey),
                              borderRadius: BorderRadius.circular(13)),
                          border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(13),
                          ),
                          focusedBorder: OutlineInputBorder(
                              borderSide:
                                  const BorderSide(color: submitBtn),
                              borderRadius: BorderRadius.circular(13))),
                    ),
                  ),
                  const SizedBox(
                    width: 15,
                  ),
                  //Button
                  Container(
                    decoration: BoxDecoration(
                        color: titleColor,
                        borderRadius: BorderRadius.circular(10)),
                    child: ElevatedButton(
                      onPressed: () {
                        checkAnswer();
                      },
                      child: const Text(
                        "Check",
                        style: TextStyle(fontSize: 16),
                      ),
                      style: ElevatedButton.styleFrom(
                          primary: Colors.transparent,
                          shadowColor: Colors.transparent,
                          padding: const EdgeInsets.symmetric(
                              vertical: 19, horizontal: 20),
                          //minimumSize: const Size(100.0, 5.0),
                          textStyle: const TextStyle(
                              fontWeight: FontWeight.bold, fontSize: 23)),
                    ),
                  ),
                ],
              ),
              const SizedBox(
                height: 20,
              ),
              //View Math symbols dialog
              Align(
                alignment: Alignment.centerLeft,
                child: Container(
                  decoration: BoxDecoration(
                      color: Colors.grey.shade400,
                      borderRadius: BorderRadius.circular(10)),
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                          context: context,
                          builder: (context) {
                            return const MathFormulaDialog();
                          });
                    },
                    child: const Text(
                      "How should I type my answer?",
                      style: TextStyle(fontSize: 16, color: Colors.black),
                    ),
                    style: ElevatedButton.styleFrom(
                        primary: Colors.transparent,
                        shadowColor: Colors.transparent,
                        padding: const EdgeInsets.symmetric(
                            vertical: 19, horizontal: 20),
                        //minimumSize: const Size(100.0, 5.0),
                        textStyle: const TextStyle(
                            fontWeight: FontWeight.bold, fontSize: 23)),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    ));
 }
}

尽量不要在 build 方法中使用代码,除非您希望它在小部件树中发生任何微小变化时随时刷新。 基本上问题是 here 在 build 方法的开头几行:

...
@override
Widget build(BuildContext context) {
List quiz = widget.newData["quiz"];
var randomQuiz = (quiz..shuffle()).first;
...

您必须在 build 之外使用它,并且仅在触发操作时使用它,例如,如果用户完成当前问题的回答,然后刷新 ui 并显示另一个随机问题。

感谢@Namini40,我将构建函数中的变量移至initState()checkAnswer() 函数保留在构建方法中,我改变了一些东西。如果用户得到正确答案而不是使用 setState(),我会使用 Navigator.pushReplacement() 导航到同一页面,但会显示不同的问题。

新代码:https://github.com/BotsheloRamela/classio/blob/main/quiz_screen.dart