Timer.periodic() 回调干扰 GestureDetector
Timer.periodic() callback interferes with GestureDetector
编辑
本质上,我只是想知道是否有一种方法可以让两个小部件在 Flutter 中并行 运行。在我的例子中,我想要一个 Timer 小部件和 Game 小部件 运行 结合在一起而不干扰彼此的执行。
我尝试过使用计算和隔离,但显然,隔离不能用于更改小部件状态。
我正在开发一款记忆游戏,我想在卡片组的顶部添加一个计时器。下面是我的代码。
问题是,当计时器启动时,
@override
void initState() {
previousClick = _CardTileState();
super.initState();
st.start();
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() {
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
}
我无法再点击我的卡片来匹配它们。 GestureDetector 的 onTap() 回调没有被执行。
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (widget.isClickable) {
print('clicked ' + widget.index.toString());
widget.parent.handleClick(this);
}
},
我该如何解决这个问题?这是完整的代码:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:save_the_planet/data.dart';
class GameScreen extends StatefulWidget {
GameScreen({Key key, this.title}) : super(key: key);
final String title;
@override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
List<CardTile> cards = getCards();
_CardTileState previousClick;
String click = "";
int score = 0;
String mins = "00";
String secs = "00";
Stopwatch st = Stopwatch();
Timer timer;
@override
void initState() {
previousClick = _CardTileState();
super.initState();
st.start();
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() {
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
}
String strFormat(int val) {
String res = "";
if (val < 10)
res = "0" + val.toString();
else
res = val.toString();
return res;
}
void handleClick(_CardTileState source) {
try {
if (previousClick.widget.getIsUncovered()) {
if (click == 'first click') {
print('second click');
click = 'second click';
source.setState(() {
source.widget.setIsUncovered(true);
source.widget.setIsClickable(false);
});
Timer(Duration(milliseconds: 1000), () {
if (source.widget.getImagePath() ==
previousClick.widget.getImagePath()) {
score += 100;
print(score);
if (score == 1000) {
print('game over');
}
previousClick = _CardTileState();
} else {
source.setState(() {
source.widget.setIsUncovered(false);
source.widget.setIsClickable(true);
});
previousClick.setState(() {
previousClick.widget.setIsUncovered(false);
previousClick.widget.setIsClickable(true);
});
previousClick = _CardTileState();
}
});
}
}
} catch (e) {
print('first click');
click = 'first click';
source.setState(() {
source.widget.setIsUncovered(true);
source.widget.setIsClickable(false);
});
previousClick = source;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
color: Colors.black,
child: Column(
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 25)),
SizedBox(
width: MediaQuery.of(context).size.width / 3,
child: Card(
margin: EdgeInsets.all(5),
color: Colors.white54,
child: Row(
children: <Widget>[
Icon(Icons.access_time),
Text(mins + ":" + secs),
],
),
),
),
GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100.0, mainAxisSpacing: 0.0),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: List.generate(
cards.length,
(index) {
cards[index].setIsUncovered(true);
cards[index].setIsClickable(false);
cards[index].setIndex(index);
cards[index].setParent(this);
return cards[index];
},
),
),
],
),
),
);
}
}
// ignore: must_be_immutable
class CardTile extends StatefulWidget {
String imagePath;
bool isClickable;
bool isUncovered;
int index;
_GameScreenState parent;
CardTile(
{Key key,
this.imagePath,
this.isClickable,
this.isUncovered,
this.index,
this.parent})
: super(key: key);
void setImagePath(String path) {
this.imagePath = path;
}
void setIsClickable(bool val) {
this.isClickable = val;
}
void setIsUncovered(bool val) {
this.isUncovered = val;
}
void setIndex(int val) {
this.index = val;
}
void setParent(_GameScreenState val) {
this.parent = val;
}
String getImagePath() {
return this.imagePath;
}
bool getIsClickable() {
return this.isClickable;
}
bool getIsUncovered() {
return this.isUncovered;
}
int getIndex() {
return this.index;
}
_GameScreenState getParent() {
return this.parent;
}
@override
_CardTileState createState() => _CardTileState();
}
class _CardTileState extends State<CardTile> {
@override
void initState() {
super.initState();
Timer(
Duration(seconds: 5),
() {
setState(() {
widget.setIsUncovered(false);
widget.setIsClickable(true);
});
},
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (widget.isClickable) {
print('clicked ' + widget.index.toString());
widget.parent.handleClick(this);
}
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Container(
margin: EdgeInsets.all(5),
child: Container(
child: Image.asset(
widget.isUncovered ? widget.imagePath : 'assets/default1.png'),
),
),
),
);
}
}
问题出在您的计时器中,您每秒设置一次状态,并且您在构建方法中生成列表,其属性可点击和未覆盖分别为 false 和 true,当您设置状态时,此列表将每秒重建,甚至如果您在一秒钟后在 initState 中将它们的值更改为 true,它们将变回 false,并且您将在没有点击的情况下被未覆盖的卡片卡住
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() { //this setState
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
只需在initState中生成List并传递给GridView的children参数
List<Widget> listOfCards; //Create a variable to save your Widget List
@override
void initState() {
previousClick = _CardTileState();
listOfCards = List<Widget>.generate(cards.length, //Generate it here in initState
(index) {
//Each time setState ran this code generated the List with this attributes to true and false
// Now in initState you create this list once, and even with the setState this list will not be generated again
cards[index].setIsUncovered(true);
cards[index].setIsClickable(false);
cards[index].setIndex(index);
cards[index].setParent(this);
return cards[index];
},
);
super.initState();
st.start();
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() {
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
}
然后在 GridView 中传递变量 listOfCards
GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100.0, mainAxisSpacing: 0.0),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: listOfCards
),
测试代码是否如您所愿后,我只能告诉您尝试使用一些状态管理解决方案(Bloc、get_it、Provider 等)更改整个逻辑,此代码将是如果您不在其他地方拆分逻辑,那么您尝试维护它的次数越多。还有:
ignore: must_be_immutable
这应该是一个危险信号,表明存在问题,因为阅读 StatefulWidget 文档您会看到
StatefulWidget instances themselves are immutable and store their
mutable state either in separate State objects that are created by the
createState method
不遵守这些准则可能会导致以后出现奇怪的行为。
我知道一开始你只是想让它工作,然后你会打磨它,只是说它作为一个提示,鼓励你尝试不同的方法,看看什么是最适合你的
编辑
本质上,我只是想知道是否有一种方法可以让两个小部件在 Flutter 中并行 运行。在我的例子中,我想要一个 Timer 小部件和 Game 小部件 运行 结合在一起而不干扰彼此的执行。
我尝试过使用计算和隔离,但显然,隔离不能用于更改小部件状态。
我正在开发一款记忆游戏,我想在卡片组的顶部添加一个计时器。下面是我的代码。
问题是,当计时器启动时,
@override
void initState() {
previousClick = _CardTileState();
super.initState();
st.start();
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() {
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
}
我无法再点击我的卡片来匹配它们。 GestureDetector 的 onTap() 回调没有被执行。
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (widget.isClickable) {
print('clicked ' + widget.index.toString());
widget.parent.handleClick(this);
}
},
我该如何解决这个问题?这是完整的代码:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:save_the_planet/data.dart';
class GameScreen extends StatefulWidget {
GameScreen({Key key, this.title}) : super(key: key);
final String title;
@override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
List<CardTile> cards = getCards();
_CardTileState previousClick;
String click = "";
int score = 0;
String mins = "00";
String secs = "00";
Stopwatch st = Stopwatch();
Timer timer;
@override
void initState() {
previousClick = _CardTileState();
super.initState();
st.start();
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() {
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
}
String strFormat(int val) {
String res = "";
if (val < 10)
res = "0" + val.toString();
else
res = val.toString();
return res;
}
void handleClick(_CardTileState source) {
try {
if (previousClick.widget.getIsUncovered()) {
if (click == 'first click') {
print('second click');
click = 'second click';
source.setState(() {
source.widget.setIsUncovered(true);
source.widget.setIsClickable(false);
});
Timer(Duration(milliseconds: 1000), () {
if (source.widget.getImagePath() ==
previousClick.widget.getImagePath()) {
score += 100;
print(score);
if (score == 1000) {
print('game over');
}
previousClick = _CardTileState();
} else {
source.setState(() {
source.widget.setIsUncovered(false);
source.widget.setIsClickable(true);
});
previousClick.setState(() {
previousClick.widget.setIsUncovered(false);
previousClick.widget.setIsClickable(true);
});
previousClick = _CardTileState();
}
});
}
}
} catch (e) {
print('first click');
click = 'first click';
source.setState(() {
source.widget.setIsUncovered(true);
source.widget.setIsClickable(false);
});
previousClick = source;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
color: Colors.black,
child: Column(
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 25)),
SizedBox(
width: MediaQuery.of(context).size.width / 3,
child: Card(
margin: EdgeInsets.all(5),
color: Colors.white54,
child: Row(
children: <Widget>[
Icon(Icons.access_time),
Text(mins + ":" + secs),
],
),
),
),
GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100.0, mainAxisSpacing: 0.0),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: List.generate(
cards.length,
(index) {
cards[index].setIsUncovered(true);
cards[index].setIsClickable(false);
cards[index].setIndex(index);
cards[index].setParent(this);
return cards[index];
},
),
),
],
),
),
);
}
}
// ignore: must_be_immutable
class CardTile extends StatefulWidget {
String imagePath;
bool isClickable;
bool isUncovered;
int index;
_GameScreenState parent;
CardTile(
{Key key,
this.imagePath,
this.isClickable,
this.isUncovered,
this.index,
this.parent})
: super(key: key);
void setImagePath(String path) {
this.imagePath = path;
}
void setIsClickable(bool val) {
this.isClickable = val;
}
void setIsUncovered(bool val) {
this.isUncovered = val;
}
void setIndex(int val) {
this.index = val;
}
void setParent(_GameScreenState val) {
this.parent = val;
}
String getImagePath() {
return this.imagePath;
}
bool getIsClickable() {
return this.isClickable;
}
bool getIsUncovered() {
return this.isUncovered;
}
int getIndex() {
return this.index;
}
_GameScreenState getParent() {
return this.parent;
}
@override
_CardTileState createState() => _CardTileState();
}
class _CardTileState extends State<CardTile> {
@override
void initState() {
super.initState();
Timer(
Duration(seconds: 5),
() {
setState(() {
widget.setIsUncovered(false);
widget.setIsClickable(true);
});
},
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (widget.isClickable) {
print('clicked ' + widget.index.toString());
widget.parent.handleClick(this);
}
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Container(
margin: EdgeInsets.all(5),
child: Container(
child: Image.asset(
widget.isUncovered ? widget.imagePath : 'assets/default1.png'),
),
),
),
);
}
}
问题出在您的计时器中,您每秒设置一次状态,并且您在构建方法中生成列表,其属性可点击和未覆盖分别为 false 和 true,当您设置状态时,此列表将每秒重建,甚至如果您在一秒钟后在 initState 中将它们的值更改为 true,它们将变回 false,并且您将在没有点击的情况下被未覆盖的卡片卡住
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() { //this setState
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
只需在initState中生成List并传递给GridView的children参数
List<Widget> listOfCards; //Create a variable to save your Widget List
@override
void initState() {
previousClick = _CardTileState();
listOfCards = List<Widget>.generate(cards.length, //Generate it here in initState
(index) {
//Each time setState ran this code generated the List with this attributes to true and false
// Now in initState you create this list once, and even with the setState this list will not be generated again
cards[index].setIsUncovered(true);
cards[index].setIsClickable(false);
cards[index].setIndex(index);
cards[index].setParent(this);
return cards[index];
},
);
super.initState();
st.start();
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
int ms = st.elapsedMilliseconds;
int sec = (ms ~/ 1000) % 60;
int min = (ms ~/ 1000) ~/ 60;
if (min == 2) {
st.stop();
st.reset();
timer.cancel();
} else {
setState(() {
mins = strFormat(min);
secs = strFormat(sec);
});
}
});
}
然后在 GridView 中传递变量 listOfCards
GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100.0, mainAxisSpacing: 0.0),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: listOfCards
),
测试代码是否如您所愿后,我只能告诉您尝试使用一些状态管理解决方案(Bloc、get_it、Provider 等)更改整个逻辑,此代码将是如果您不在其他地方拆分逻辑,那么您尝试维护它的次数越多。还有:
ignore: must_be_immutable
这应该是一个危险信号,表明存在问题,因为阅读 StatefulWidget 文档您会看到
StatefulWidget instances themselves are immutable and store their mutable state either in separate State objects that are created by the createState method
不遵守这些准则可能会导致以后出现奇怪的行为。
我知道一开始你只是想让它工作,然后你会打磨它,只是说它作为一个提示,鼓励你尝试不同的方法,看看什么是最适合你的