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
我有一些本地 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