方法“findRenderObject”在 null 上被调用 - 使用 StackTrace

时间:2021-05-09 09:22:19

标签: flutter dart state

嗨,我是 Flutter Dart 的新手。
在我的项目中,我得到了例外:The method 'findRenderObject' was called on null. Receiver: null Tried calling: findRenderObject()

当我触发 ElevatedButton 文件中的 main.dart 时会发生这种情况。
将被触发的函数是我的 startGame 文件中的 play_field.dart 函数。

有人知道为什么我在触发这个按钮时会出现这个异常吗?
堆栈跟踪:

#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1      PlayFieldState.startGame
package:woost_games/…/game/play_field.dart:74
#2      _TetrisState.build.<anonymous closure>
package:woost_games/main.dart:88
#3      _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:991
#4      GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:182
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#dbf10
    debugOwner: GestureDetector
    state: possible
    won arena
    finalPosition: Offset(332.0, 284.3)
    finalLocalPosition: Offset(33.1, 7.9)
    button: 1
    sent tap down

请参阅下面的代码:

Main.dart

import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

import 'widgets/block/block.dart';
import 'widgets/block/next_block.dart';
import 'widgets/game/play_field.dart';
import 'widgets/game/score_bar.dart';

void main() => runApp(
  ChangeNotifierProvider(
    create: (context) => Data(),
    child: WoostGames()
  )
);

class WoostGames extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);

    return MaterialApp(home: Tetris());
  }
}

class Tetris extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TetrisState();
}

class _TetrisState extends State<Tetris> {
  GlobalKey<PlayFieldState> _playFieldKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Woost Tetris',
          style: TextStyle(
            color: Colors.black,
            fontFamily: 'Neue Montreal',
            fontSize: 25,
            fontWeight: FontWeight.w700
          )
        ),
        centerTitle: true,
        backgroundColor: Colors.yellow,
      ),
      backgroundColor: Colors.yellow,
      body: SafeArea(
        child: Column(children: <Widget>[
          ScoreBar(),
          Expanded(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Flexible(
                  flex: 3,
                  child: Padding(
                    padding: EdgeInsets.fromLTRB(5, 0, 2.5, 5),
                    child: PlayField(key: _playFieldKey),
                  )
                ),
                Flexible(
                  child: Padding(
                    padding: EdgeInsets.fromLTRB(2.5, 0, 5, 5),
                    child: Column(
                      children: <Widget>[
                        NextBlock(),
                        SizedBox(height: 30),
                        ElevatedButton(
                          child: Text(
                            Provider.of<Data>(context, listen: false).inGame
                              ? 'End'
                              : 'Start',
                            style: TextStyle(
                              fontSize: 18,
                              color: Colors.yellow
                            )
                          ),
                          style: ButtonStyle(
                            backgroundColor: MaterialStateProperty.all<Color>(Colors.black)
                          ),
                          onPressed: () {
                            Provider.of<Data>(context, listen: false).inGame
                              ? _playFieldKey.currentState.endGame()
                              : _playFieldKey.currentState.startGame();
                          }
                        )
                      ]
                    )
                  )
                ),
              ],
            ),
          )
        ])
      )
    );
  }
}

class Data with ChangeNotifier {
  int score = 0;
  bool inGame = false;
  Block nextBlock;

  void setScore(score) {
    this.score = score ;
    notifyListeners();
  }

  void addScore(score) {
    this.score += score;
    notifyListeners();
  }

  void setInGame(inGame) {
    this.inGame = inGame;
    notifyListeners();
  }

  void setNextBlock(Block nextBlock) {
    this.nextBlock = nextBlock;
    notifyListeners();
  }

  Widget getNextBlockWidget() {
    if (!inGame) return Container();

    var width = nextBlock.width;
    var height = nextBlock.height;
    var color;

    List<Widget> columns = [];
    for (var y = 0; y < height; y++) {
      List<Widget> rows = [];
      for (var x = 0; x < width; ++ x) {
        color = (nextBlock.subBlocks
          .where((subBlock) => subBlock.x == x && subBlock.y == y)
          .length > 0) ? nextBlock.color : Colors.transparent;

        rows.add(Container(width: 12, height: 12, color: color));
      }

      columns.add(Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: rows
      ));
    }

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: columns,
    );
  }
}

play_field.dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../main.dart';
import '../block/block.dart';
import '../block/blocks.dart';
import '../block/sub_block.dart';

enum Collision {
  LANDED_ON_BOTTOM,
  LANDED_ON_BLOCK,
  HIT_WALL,
  HIT_BLOCK,
  NONE
}

const PLAY_FIELD_WIDTH = 10;
const PLAY_FIELD_HEIGHT = 20;
const REFRESH_RATE = 300;
const GAME_AREA_BORDER_WIDTH = 2.0;
const SUB_BLOCK_EDGE_WIDTH = 2.0;

class PlayField extends StatefulWidget {
  PlayField({Key key}) : super(key: key);

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

class PlayFieldState extends State<PlayField> {
  bool isGameOver = false;
  double subBlockWidth;
  Duration blockMovementDuration = Duration(milliseconds: REFRESH_RATE);

  GlobalKey _playFieldAreaKey = GlobalKey();

  BlockMovement action;
  Block block;
  Timer timer;

  List<SubBlock> oldSubBlocks;

  Block getNewBlock() {
    int blockType = Random().nextInt(7);
    int orientationIndex = Random().nextInt(4);

    switch (blockType) {
      case 0:
        return IBlock(orientationIndex);
      case 1:
        return JBlock(orientationIndex);
      case 2:
        return LBlock(orientationIndex);
      case 3:
        return OBlock(orientationIndex);
      case 4:
        return TBlock(orientationIndex);
      case 5:
        return SBlock(orientationIndex);
      case 6:
        return ZBlock(orientationIndex);
      default:
        return null;
    }
  }

  void startGame() {
    Provider.of<Data>(context, listen: false).setInGame(true);
    Provider.of<Data>(context, listen: false).setScore(0);
    oldSubBlocks = [];

    RenderBox renderBoxPlayField = _playFieldAreaKey.currentContext.findRenderObject();
    subBlockWidth = (renderBoxPlayField.size.width - GAME_AREA_BORDER_WIDTH * 2) / PLAY_FIELD_WIDTH;

    Provider.of<Data>(context, listen: false).setNextBlock(getNewBlock());
    block = getNewBlock();

    timer = Timer.periodic(blockMovementDuration, onPlay);
  }

  void endGame() {
    Provider.of<Data>(context, listen: false).setInGame(false);
    timer.cancel();
  }

  void onPlay(Timer timer) {
    var status = Collision.NONE;

    setState(() {
      if (action != null) {
        if (!checkOnEdge(action)) {
          block.move(action);
        }
      }

      oldSubBlocks.forEach((oldSubBlock) {
        block.subBlocks.forEach((subBlock) {
          var x = block.x + subBlock.x;
          var y = block.y + subBlock.y;
          if (x == oldSubBlock.x && y == oldSubBlock.y) {
            switch (action) {
              case BlockMovement.LEFT:
                block.move(BlockMovement.RIGHT);
                break;
              case BlockMovement.RIGHT:
                block.move(BlockMovement.LEFT);
                break;
              case BlockMovement.ROTATE_CLOCKWISE:
                block.move(BlockMovement.ROTATE_COUNTER_CLOCKWISE);
                break;
              default:
                break;
            }
          }
        });
      });

      if (!checkAtBottom()) {
        if (!checkOnBlock()) {
          block.move(BlockMovement.DOWN);
        } else {
          status = Collision.LANDED_ON_BLOCK;
        }
      } else {
        status = Collision.LANDED_ON_BOTTOM;
      }

      if (status == Collision.LANDED_ON_BLOCK && block.y < 0) {
        isGameOver = true;
        endGame();
      }
      else if (status == Collision.LANDED_ON_BOTTOM
      || status == Collision.LANDED_ON_BLOCK) {
        block.subBlocks.forEach((subBlock) {
          subBlock.x += block.x;
          subBlock.y += block.y;
          oldSubBlocks.add(subBlock);
        });
      }

      block = Provider.of<Data>(context, listen: false).nextBlock;
      Provider.of<Data>(context, listen: false).setNextBlock(getNewBlock());
    });

    action = null;
    updateScore();
  }

  void updateScore() {
    var combo = 1;
    Map<int, int> rows = Map();
    List<int> rowsToBeRemoved = [];

    oldSubBlocks?.forEach((oldSubBlock) {
      rows.update(oldSubBlock.y, (value) => ++value, ifAbsent: () => 1);
    });

    rows.forEach((rowNum, count) {
      if (count == PLAY_FIELD_WIDTH) {
        Provider.of<Data>(context, listen: false).setScore(combo++);
        rowsToBeRemoved.add(rowNum);
      }
    });

    if (rowsToBeRemoved.length > 0)
      removeRows(rowsToBeRemoved);
  }

  void removeRows(List<int> rows) {
    rows.sort();
    rows.forEach((row) {
      oldSubBlocks.removeWhere((oldSubBlock) => oldSubBlock.y == row);
      oldSubBlocks.forEach((oldSubBlock) {
        if (oldSubBlock.y < row)
          ++oldSubBlock.y;
      });
    });
  }

  bool checkAtBottom() {
    return block.y + block.height == PLAY_FIELD_HEIGHT;
  }

  bool checkOnBlock() {
    oldSubBlocks.forEach((oldSubBlock) {
      block.subBlocks.forEach((subBlock) {
        var x = block.x + subBlock.x;
        var y = block.y + subBlock.y;
        if (x == oldSubBlock.x && y + 1 == oldSubBlock.y)
          return true;
      });
    });

    return false;
  }

  bool checkOnEdge(BlockMovement action) {
    return (action == BlockMovement.LEFT && block.x <= 0) ||
      (action == BlockMovement.RIGHT && block.x + block.width >= PLAY_FIELD_WIDTH);
  }

  Widget getPositionedSquareContainer(Color color, int x, int y) {
    return Positioned(
      left: x * subBlockWidth,
      top: y * subBlockWidth,
      child: Container(
        width: subBlockWidth - SUB_BLOCK_EDGE_WIDTH,
        height: subBlockWidth - SUB_BLOCK_EDGE_WIDTH,
        decoration: BoxDecoration(
          color: color,
          shape: BoxShape.rectangle,
          borderRadius: BorderRadius.all(Radius.circular(2.5))
        )
      )
    );
  }

  Widget drawBlock() {
    if (block == null) return null;
    List<Positioned> subBlocks = [];

    block.subBlocks.forEch((subBlock) {
      subBlocks.add(getPositionedSquareContainer(
        subBlock.color, subBlock.x + block.x, subBlock.y + block.y
      ));
    });

    oldSubBlocks?.forEach((oldSubBlock) {
      subBlocks.add(getPositionedSquareContainer(
        oldSubBlock.color, oldSubBlock.x, oldSubBlock.y
      ));
    });

    if (isGameOver) {
      subBlocks.add(getGameOverRect());
    }

    return Stack(children: subBlocks);
  }

  Widget getGameOverRect() {
    return Positioned(
      child: Container(
        width: subBlockWidth * 8,
        height: subBlockWidth * 3,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.all(Radius.circular(5)),
          color: Colors.black
        ),
        child: Text(
          'Game Over',
          style: TextStyle(
            fontSize: 30,
            fontWeight: FontWeight.bold,
            color: Colors.yellow
          )
        )
      ),
      left: subBlockWidth * 1,
      top: subBlockWidth * 6
    );
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onHorizontalDragUpdate: (event) {
        action = (event.delta.dx < 1)
          ? BlockMovement.LEFT
          : BlockMovement.RIGHT;
      },
      onTap: () {
        action = BlockMovement.ROTATE_CLOCKWISE;
      },
      child: AspectRatio(
        aspectRatio: PLAY_FIELD_WIDTH / PLAY_FIELD_HEIGHT,
        child: Container(
          decoration: BoxDecoration(
            color: Colors.yellow,
            border: Border.all(
              width: GAME_AREA_BORDER_WIDTH,
              color: Colors.black
            ),
            borderRadius: BorderRadius.all(Radius.circular(2.5))
          ),
        ),
      )
    );
  }
}

=== 编辑 ====

完整异常的截图: enter image description here

0 个答案:

没有答案