Flutter:如何阻止状态更改UI



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

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

要查看我在应用程序中遇到的问题,这里有一个视频链接: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方法中使用代码,除非您希望在小部件树中发生任何小更改时都刷新它。基本上,问题是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

最新更新