flutter - 在 null 上调用了方法“findRenderObject” - 使用 StackTrace
问题描述
嗨,我是 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
请参阅下面的代码:
主要.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))
),
),
)
);
}
}
=== 编辑 ====
解决方案
推荐阅读
- javascript - React - 从嵌套菜单中关闭 MUI 抽屉
- python - Matplotlib:控制分类轴的刻度位置
- android - 动画到 ObjectAnimator
- flutter - 如何在颤动中将字符串转换为动态列表
- github - gh CLI,如何将拉取请求标题强制为分支名称?
- python - 删除一列等于另一列的重复行
- python - 如何写入匹配类型的前 n 条记录和最后 n 条记录(基于行)
- reactjs - 虚拟化列表不应该嵌套在最新的 react-native 中的普通滚动视图中
- mysql - mysql 存储过程继续运行 - 当从 shell 脚本触发时
- go - Defer gzip.Close() 不写页脚