【问题标题】:Timer.periodic() callback interferes with GestureDetectorTimer.periodic() 回调干扰 GestureDetector
【发布时间】:2020-10-21 08:41:38
【问题描述】:

编辑

本质上,我只是想知道是否有一种方法可以让两个小部件在 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'),
          ),
        ),
      ),
    );
  }
}

【问题讨论】:

    标签: flutter dart timer gesturedetector


    【解决方案1】:

    问题在于您的计时器每秒都设置状态,并且您在构建方法中生成列表,其属性可点击和未覆盖分别为 false 和 true,当您 setState 时,此列表将每秒重建,即使您在一秒钟后在 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 等)更改整个逻辑,您尝试越多,此代码就会变得一团糟如果您不将逻辑拆分到其他地方,请保留它。另外:

    忽略:must_be_immutable

    这应该是一个危险信号,因为阅读 StatefulWidget 文档你会看到这一点

    StatefulWidget 实例本身是不可变的,并存储它们的 可变状态在由 创建状态方法

    不遵守这些准则可能会导致以后出现奇怪的行为。

    我知道一开始你只是想让它发挥作用,然后你会完善它,只是把它作为一个提示来鼓励你尝试不同的方法,看看什么是最适合你的

    【讨论】:

      猜你喜欢
      • 2013-03-31
      • 2012-06-05
      • 1970-01-01
      • 2020-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多