【问题标题】:Where do you put logic in Flutter?你把逻辑放在 Flutter 的什么地方?
【发布时间】:2021-11-25 18:09:35
【问题描述】:

我正在尝试通过编写数独游戏来自学 Flutter/Dart。

我的计划是用一个名为“Square”的对象来表示数独网格上 81 个方格中的每一个。

每个 Square 都会管理自己的状态,以显示用户输入和记住状态。到目前为止,我已经使用 StatefulWidget 对此进行了编程。

我卡住的地方是,还需要有一个顶级的游戏逻辑,它可以概览所有方块正在发生的事情并处理交互。这意味着我需要能够查询顶层的方块以了解它们的状态。

有没有办法用 Flutter 做到这一点?还是我需要使用其他结构?

下面是我当前的 Square 实现的副本。

import 'package:flutter/material.dart';
import 'package:sudoku_total/logical_board.dart';

class Square extends StatefulWidget {
  final int squareIndex;
  final int boxIndex;
  final int rowIndex;
  final int colIndex;
  const Square(
    this.squareIndex,
    this.boxIndex,
    this.rowIndex,
    this.colIndex, {
    Key? key,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => _SquareState();
}

class _SquareState extends State<Square> {
  int _mainNumber = 0;
  bool showEdit1 = false;
  bool showEdit2 = false;
  bool showEdit3 = false;
  bool showEdit4 = false;
  bool showEdit5 = false;
  bool showEdit6 = false;
  bool showEdit7 = false;
  bool showEdit8 = false;
  bool showEdit9 = false;
  bool _selected = false;
  bool _selectedCollection = false;

  @override
  Widget build(BuildContext context) {
    LogicalBoard.numberButtonNotifier.addListener(() {
      if(LogicalBoard.selectedSquare?.squareIndex == widget.squareIndex){
        setState(() {
          _mainNumber = LogicalBoard.numberLastClicked;
        });
      }
    });
    LogicalBoard.selectionNotifier.addListener(() {
      setState(() {
        _selected =
            LogicalBoard.selectedSquare?.squareIndex == widget.squareIndex;
        _selectedCollection =
            LogicalBoard.selectedSquare?.squareIndex == widget.squareIndex ||
                LogicalBoard.selectedSquare?.rowIndex == widget.rowIndex ||
                LogicalBoard.selectedSquare?.colIndex == widget.colIndex ||
                LogicalBoard.selectedSquare?.boxIndex == widget.boxIndex;
      });
    });
    return Material(
        child: InkWell(
            onTap: () => LogicalBoard.selectionNotifier
                .setSelectedSquare(widget.squareIndex),
            child: Container(
              padding: const EdgeInsets.all(2.0),
              color: _selected
                  ? Theme.of(context).errorColor
                  : Theme.of(context).primaryColorDark,
              width: 52.0,
              height: 52.0,
              child: Container(
                  padding: _mainNumber == 0
                      ? const EdgeInsets.fromLTRB(2.0, 8.0, 2.0, 2.0)
                      : const EdgeInsets.all(0.0),
                  color: _selectedCollection
                      ? Theme.of(context).backgroundColor
                      : Theme.of(context).primaryColor,
                  width: 48.0,
                  height: 48.0,
                  child: _mainNumber != 0
                      ? Center(
                          child: Text(
                          _mainNumber.toString(),
                          style: Theme.of(context).textTheme.headline3,
                        ))
                      : Column(mainAxisSize: MainAxisSize.min, children: [
                          Flexible(
                              child: Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                Flexible(
                                    child: Text(
                                  showEdit1 ? "1" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                )),
                                Flexible(
                                    child: Text(
                                  showEdit2 ? "2" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                )),
                                Flexible(
                                    child: Text(
                                  showEdit3 ? "3" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                ))
                              ])),
                          Flexible(
                              child: Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                Flexible(
                                    child: Text(
                                  showEdit4 ? "4" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                )),
                                Flexible(
                                    child: Text(
                                  showEdit5 ? "5" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                )),
                                Flexible(
                                    child: Text(
                                  showEdit6 ? "6" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                ))
                              ])),
                          Flexible(
                              child: Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                Flexible(
                                    child: Text(
                                  showEdit7 ? "7" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                )),
                                Flexible(
                                    child: Text(
                                  showEdit8 ? "8" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                )),
                                Flexible(
                                    child: Text(
                                  showEdit9 ? "9" : "",
                                  style: Theme.of(context).textTheme.bodyText2,
                                ))
                              ]))
                        ])),
            )));
  }
}

感谢您的帮助。

【问题讨论】:

    标签: flutter dart oop logic


    【解决方案1】:

    我会做一个二维整数类型的顶级列表 `List squareValues = [[]];

    然后创建一个 void 函数,用 -1(表示其中没有数字)或 null(但请记住 nullsafety)填充这些函数

    void initSquares() {
       for loop over x {
           for loop over y {
               squareValues[x][y] = -1 // or null
           }
       }
    }
    

    然后您可以根据方形小部件内的回调更改这些值,该回调告诉顶层该值已更改

    onSquareValueChanged: (value) {
        squareValues[squareX][squareY] = value;
        //update the screen with new values if necessary with setState
    
    }
    

    【讨论】:

    • 感谢您的回答,感谢您试一试。我的 Square 需要的状态不仅仅是 1 个整数,我找到了一种方便的方法来做到这一点,并且能够通过使用 ChangeNotifier 迭代列表来填充我的显示,这比你的答案更适合我的需求。跨度>
    【解决方案2】:

    我找到了一个有效的问题答案,但我确信有不止一种方法可以做到这一点。另外,我什至不确定我的做法是否良好,因为我是 Flutter 的新手(来自 OO Java 和 React 背景),但就是这样。

    Square(如我的问题中所见)在状态中不仅仅是一个整数值。它有几个布尔值,几个整数,当我发现更多属于 Square 的东西时,它可能会获得更多。我最终调整了这里描述的模型https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#changenotifier

    我创建了一个 SquareModel,其中包含管理 Square 的所有状态信息,以及需要它们的 getter 和 setter。 SquareModel 扩展ChangeNotifier Setter 都包含 notifyListeners() 函数,该函数由 ChangeNotifier 提供,并将通知所有侦听器(在本例中为 Squares)状态更改。 SquareModel 还包含一个函数,用于初始化并返回一个 ChangeNotifierProvider,其中 Square 作为构建器函数的输出:

    import 'package:flutter/cupertino.dart';
    import 'package:provider/provider.dart';
    import 'package:sudoku_total/square.dart';
    import 'package:sudoku_total/square_collection.dart';
    
    class SquareModel extends ChangeNotifier {
      final int squareIndex;
      final int rowIndex;
      final int colIndex;
      final int boxIndex;
      LogicalBox? box;
      LogicalRow? row;
      LogicalCol? col;
    
      SquareModel(this.squareIndex, this.rowIndex, this.colIndex, this.boxIndex);
    
      int? _mainNumber;
      int? _answer;
      bool _showEdit1 = false;
      bool _showEdit2 = false;
      bool _showEdit3 = false;
      bool _showEdit4 = false;
      bool _showEdit5 = false;
      bool _showEdit6 = false;
      bool _showEdit7 = false;
      bool _showEdit8 = false;
      bool _showEdit9 = false;
      bool _selected = false;
      bool _selectedCollection = false;
    
      set editNumber(value) {
        switch (value) {
          case 1:
            _showEdit1 = !_showEdit1;
            break;
          case 2:
            _showEdit2 = !_showEdit2;
            break;
          case 3:
            _showEdit3 = !_showEdit3;
            break;
          case 4:
            _showEdit4 = !_showEdit4;
            break;
          case 5:
            _showEdit5 = !_showEdit5;
            break;
          case 6:
            _showEdit6 = !_showEdit6;
            break;
          case 7:
            _showEdit7 = !_showEdit7;
            break;
          case 8:
            _showEdit8 = !_showEdit8;
            break;
          case 9:
            _showEdit9 = !_showEdit9;
        }
        notifyListeners();
      }
    
      List<int> getEditNumbers() {
        List<int> editNumbers = [];
        if (_showEdit1) {
          editNumbers.add(1);
        }
        if (_showEdit2) {
          editNumbers.add(2);
        }
        if (_showEdit3) {
          editNumbers.add(3);
        }
        if (_showEdit4) {
          editNumbers.add(4);
        }
        if (_showEdit5) {
          editNumbers.add(5);
        }
        if (_showEdit6) {
          editNumbers.add(6);
        }
        if (_showEdit7) {
          editNumbers.add(7);
        }
        if (_showEdit8) {
          editNumbers.add(8);
        }
        if (_showEdit9) {
          editNumbers.add(9);
        }
        return editNumbers;
      }
    
      int? get mainNumber => _mainNumber;
    
      set mainNumber(int? value) {
        _mainNumber = value;
        notifyListeners();
      }
    
      int? get answer => _answer;
    
      set answer(int? value) {
        _answer = value;
        notifyListeners();
      }
    
      bool get selected => _selected;
    
      set selected(bool value) {
        _selected = value;
        notifyListeners();
      }
    
      bool get selectedCollection => _selectedCollection;
    
      set selectedCollection(bool value) {
        _selectedCollection = value;
        notifyListeners();
      }
    
    //This last function is called to instantiate the ChangeNotifierProvider for each Square and make sure each Square is provided with the relevant SquareModel state whenever state changes.
    
    getSquare() => ChangeNotifierProvider(
          create: (context) => this,
          child: Consumer<SquareModel>(
            builder: (context, square, child) => Square(
              squareIndex: squareIndex,
              mainNumber: _mainNumber,
              answer: _answer,
              selected: _selected,
              selectedCollection: _selectedCollection,
              showEdit1: _showEdit1,
              showEdit2: _showEdit2,
              showEdit3: _showEdit3,
              showEdit4: _showEdit4,
              showEdit5: _showEdit5,
              showEdit6: _showEdit6,
              showEdit7: _showEdit7,
              showEdit8: _showEdit8,
              showEdit9: _showEdit9,
            ),
          ));
    
      
    }
    
    

    我有一个抽象类LogicalBoard,它实例化了所有 81 个 SquareModel,并将它们保存在一个静态列表中,因此它们可用于应用程序中的任何其他对象。 LogicalBoard 提供了管理状态的方法,并设置了数独板背后的逻辑(例如,哪些 SquareModels 属于哪些列、行和框)。 LogicalBoard 还创建了一个包含 9 个 SudokuRow 的列表,它们是一个 StatefulWidget,每个都有 9 个 Squares 并以适当的间隙显示它们。设置数独板时会迭代此列表以显示板。

    import 'package:sudoku_total/square_model.dart';
    import 'package:sudoku_total/sudoku_row.dart';
    import 'package:sudoku_total/square_collection.dart';
    
    class LogicalBoard {
      static final List<SquareModel> squareModels = _initSquares();
      static final List<SudokuRow> rowsWidgets = _initRowsWidgets();
      static final List<LogicalBox> boxes = _initBoxes();
      static final List<LogicalRow> rows = _initRows();
      static final List<LogicalCol> cols = _initCols();
    
      static SquareModel? selectedSquare;
      static int numberLastClicked=0;
    
      static void setNumber(int number){
        if(selectedSquare != null){
          selectedSquare?.mainNumber = number;
        }
    
      }
    
      static void setSelectedSquare(int squareIndex){
        selectedSquare = squareModels[squareIndex];
        for (var sm in squareModels) {
          sm.selected = (sm.squareIndex==squareIndex);
          sm.selectedCollection = sm.boxIndex==selectedSquare?.boxIndex || sm.rowIndex==selectedSquare?.rowIndex || sm.colIndex==selectedSquare?.colIndex;
        }
      }
    
    
    
      static List<LogicalBox> _initBoxes(){
        List<LogicalBox> boxes = [];
        for(var i = 0; i<9; i++){
          boxes.add(LogicalBox(_getBoxSquares(i)));
        }
        return List.unmodifiable(boxes);
      }
    
      static List<LogicalRow> _initRows(){
        List<LogicalRow> rows = [];
        for(var i = 0; i<9; i++){
          rows.add(LogicalRow(_getRowSquares(i)));
        }
        return List.unmodifiable(rows);
      }
    
      static List<LogicalCol> _initCols(){
        List<LogicalCol> boxes = [];
        for(var i = 0; i<9; i++){
          boxes.add(LogicalCol(_getColSquares(i)));
        }
        return List.unmodifiable(boxes);
      }
    
      static List<SudokuRow> _initRowsWidgets() {
        List<SudokuRow> rows = [];
        for(var i = 0; i<9; i++){
          rows.add(SudokuRow(_getRowSquares(i), i));
        }
        return List.unmodifiable(rows);
      }
    
      static List<SquareModel> _getRowSquares(int rowIndex){
        return List.unmodifiable(squareModels.where((element) => element.rowIndex == rowIndex));
      }
    
      static List<SquareModel> _getColSquares(int colIndex){
        return List.unmodifiable(squareModels.where((element) => element.colIndex == colIndex));
      }
    
      static List<SquareModel> _getBoxSquares(int boxIndex){
        return List.unmodifiable(squareModels.where((element) => element.boxIndex == boxIndex));
      }
    
      static List<SquareModel> _initSquares() {
        List<SquareModel> initSquares = [];
        //Initialise squares with squareIndex, rowIndex, colIndex and boxIndex
        int squareIndex = 0;
        int rowIndex = 0;
        int colStartIndex = 0;
        int colIndex = 0;
        int boxStartIndex = 0;
        int boxIndex = 0;
        for (var count = 1; count < 82; count++) {
          //Add row, col and box index to the new square
          initSquares.add(SquareModel(squareIndex, rowIndex, colIndex, boxIndex));
          //Every square
          //col index increments
          colIndex++;
          //square index increments
          squareIndex++;
    
          //Every 3 squares
          if (count % 3 == 0) {
            //Box index increments
            boxIndex++;
          }
    
          //Every 9 squares
          if (count % 9 == 0) {
            //col index goes back to start
            colIndex = colStartIndex;
            //row index increments
            rowIndex++;
            //Box index goes back to the start
            boxIndex = boxStartIndex;
          }
    
          //Every 27 squares
          if (count % 27 == 0) {
            //Box start index increments by 3
            boxStartIndex = boxStartIndex + 3;
            boxIndex = boxStartIndex;
          }
        }
        return List.unmodifiable(initSquares);
      }
    
    }
    

    目前LogicalBoard中的大部分功能都与设置板子有关。但是,请注意 setNumbersetSelectedSquare 函数,它们展示了状态管理使用这种结构是多么容易。

    我想添加一个功能,即用户单击 Square,Square 的边框颜色会发生变化以显示它是“选定”的 Square。使用所描述的 ChangeNotifier 设置,这只需要三个小的更改:

    1. 我将函数setSelectedSquare 添加到LogicalBoard。它迭代所有 SquareModel 并确保所有 SquareModel 状态都有false 对应_selected,除了用户点击的 Square 对应的 SquareModel,它将有true 对应_selected。它还将 LogicalBoard 上 selectedSquare 的值设置为用户单击的 Square 的 SquareModel,以便记住它并可以在将来使用。
    2. 我使用带有 onTap 功能的 Inkwell 小部件更新了 Square 的代码。请注意,Inkwell onTap 函数调用 LogicalBoard.setSelectedSquare。
    3. 我根据_selected 的值设置了第一个Container 小部件(它定义了Square 的边框颜色)的颜色(我是英国人,我们拼写颜色'color')

    这是Square的代码

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    import 'logical_board.dart';
    
    class Square extends StatelessWidget {
      final int _squareIndex;
      final int? _mainNumber;
      final int? _answer;
      final bool _showEdit1;
      final bool _showEdit2;
      final bool _showEdit3;
      final bool _showEdit4;
      final bool _showEdit5;
      final bool _showEdit6;
      final bool _showEdit7;
      final bool _showEdit8;
      final bool _showEdit9;
      final bool _selected;
      final bool _selectedCollection;
      const Square({
        Key? key,
        required int squareIndex,
        required int? mainNumber,
        required int? answer,
        required bool showEdit1,
        required bool showEdit2,
        required bool showEdit3,
        required bool showEdit4,
        required bool showEdit5,
        required bool showEdit6,
        required bool showEdit7,
        required bool showEdit8,
        required bool showEdit9,
        required bool selected,
        required bool selectedCollection,
      })  : _squareIndex = squareIndex,
            _mainNumber = mainNumber,
            _answer = answer,
            _showEdit1 = showEdit1,
            _showEdit2 = showEdit2,
            _showEdit3 = showEdit3,
            _showEdit4 = showEdit4,
            _showEdit5 = showEdit5,
            _showEdit6 = showEdit6,
            _showEdit7 = showEdit7,
            _showEdit8 = showEdit8,
            _showEdit9 = showEdit9,
            _selected = selected,
            _selectedCollection = selectedCollection,
            super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Material(
            child: InkWell(
                onTap: () => LogicalBoard.setSelectedSquare(_squareIndex),
                child: Container(
                  padding: const EdgeInsets.all(2.0),
                  color: _selected
                      ? Theme.of(context).errorColor
                      : Theme.of(context).primaryColorDark,
                  width: 52.0,
                  height: 52.0,
                  child: Container(
                      padding: _mainNumber == null
                          ? const EdgeInsets.fromLTRB(2.0, 8.0, 2.0, 2.0)
                          : const EdgeInsets.all(0.0),
                      color: _selectedCollection
                          ? Theme.of(context).backgroundColor
                          : Theme.of(context).primaryColor,
                      width: 48.0,
                      height: 48.0,
                      child: _mainNumber != null
                          ? Center(
                              child: Text(
                              _mainNumber.toString(),
                              style: Theme.of(context).textTheme.headline3,
                            ))
                          : Column(mainAxisSize: MainAxisSize.min, children: [
                              Flexible(
                                  child: Row(
                                      mainAxisSize: MainAxisSize.min,
                                      children: [
                                    Flexible(
                                        child: Text(
                                      _showEdit1 ? "1" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    )),
                                    Flexible(
                                        child: Text(
                                      _showEdit2 ? "2" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    )),
                                    Flexible(
                                        child: Text(
                                      _showEdit3 ? "3" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    ))
                                  ])),
                              Flexible(
                                  child: Row(
                                      mainAxisSize: MainAxisSize.min,
                                      children: [
                                    Flexible(
                                        child: Text(
                                      _showEdit4 ? "4" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    )),
                                    Flexible(
                                        child: Text(
                                      _showEdit5 ? "5" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    )),
                                    Flexible(
                                        child: Text(
                                      _showEdit6 ? "6" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    ))
                                  ])),
                              Flexible(
                                  child: Row(
                                      mainAxisSize: MainAxisSize.min,
                                      children: [
                                    Flexible(
                                        child: Text(
                                      _showEdit7 ? "7" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    )),
                                    Flexible(
                                        child: Text(
                                      _showEdit8 ? "8" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    )),
                                    Flexible(
                                        child: Text(
                                      _showEdit9 ? "9" : "",
                                      style: Theme.of(context).textTheme.bodyText2,
                                    ))
                                  ]))
                            ])),
                )));
      }
    }
    

    LogicalBoard 中的函数setNumber 以类似的方式用于将所选 Square 的状态(如果有的话)更新为刚刚单击的数字按钮上的数字。

    顺便说一句,让我大开眼界的是,Flutter 非常乐意在每次状态更改时重新渲染小部件,而不是试图最小化重新渲染(根据 React)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-03
      • 2013-09-04
      相关资源
      最近更新 更多