我有这样的代码,是我跟随一位 YouTuber 同伴编写的,用于创建带有动画的 3D 立方体,它工作得很好,直到我在任何面中引入 GridView 或 ListView 小部件。
import 'dart:async';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sizer/sizer.dart';
import 'package:tictactoe/Controllers/gameEngine.dart';
import 'package:tictactoe/UIUX/themesAndStyles.dart';
import 'UIUX/customWidgets.dart';
import 'gamePage.dart';
enum faces {top, bottom, right, left, front, back}
class CubeGame extends StatefulWidget {
const CubeGame({super.key});
@override
State<CubeGame> createState() => _CubeGameState();
}
class _CubeGameState extends State<CubeGame> with SingleTickerProviderStateMixin{
Offset offset = Offset.zero;
double rx = 0.0;
double ry = 0.0;
double rz = 0.0;
List<Widget> children = [];
GameEngine frontGame = GameEngine();
GameEngine backGame = GameEngine();
GameEngine rightGame = GameEngine();
GameEngine leftGame = GameEngine();
late AnimationController _animationController;
late Animation _animation;
late Widget right;
late Widget front;
late Widget back ;
late Widget left;
late Widget top;
late Widget bottom;
double cubeSize = 70.w;
updateFaces(double bX, double bY){
rx += bX;
ry += bY;
rx %= pi*2;
ry %= pi*2;
if ((rx >= 0 && rx <= 1.70) || (rx >= 4.70)){
if (ry < pi / 4) {
children = [right, front];
} else if (ry < pi / 2) {
children = [front, right];
} else if (ry < 3 * pi / 4) {
children = [back, right];
} else if (ry < pi) {
children = [right, back];
} else if (ry < 5 * pi / 4) {
children = [left, back];
} else if (ry < 3 * pi / 2) {
children = [back, left];
} else if (ry < 7 * pi / 4) {
children = [front, left];
} else {
children = [left, front];
}
}else{
if (ry < pi / 4) {
children = [left, back];
} else if (ry < pi / 2) {
children = [back, left];
} else if (ry < 3 * pi / 4) {
children = [front, left];
} else if (ry < pi) {
children = [left, front];
} else if (ry < 5 * pi / 4) {
children = [right, front];
} else if (ry < 3 * pi / 2) {
children = [front, right];
} else if (ry < 7 * pi / 4) {
children = [back, right];
} else {
children = [right, back];
}
}
if (rx >= 0 && rx <= pi) {
// TODO: not perfect - does not consider perspective:
// When `rotateX` is positive but very small, like 0.1, when taking
// account of perspective, the "top" face should be drawn *behind* the
// front face, not *in front* of it. But this works reasonably well for
// larger values (like > 0.1) of `rotateX`.
if (rx >= 0 && rx <= 0.1 || rx > 3){
children = [top,...children];
}else{
children = [...children, top];
}
} else if(rx >= pi && rx <= pi*2) {
if (rx <= pi + 0.1 || (rx >= 6.18)){
children = [bottom, ...children];
}else{
children = [...children, bottom];
}
}
setState(() {});
}
generateFaces(){
for (var face in faces.values){
switch (face){
case faces.top:
top = buildBoardFace(face: faces.top, size: cubeSize);
break;
case faces.bottom:
bottom = buildBoardFace(face: faces.bottom, size: cubeSize);
break;
case faces.right:
right = buildBoardFace(face: faces.right, size: cubeSize, customEngine: frontGame);
break;
case faces.front:
front = buildBoardFace(face: faces.front, size: cubeSize, customEngine: frontGame);
break;
case faces.back:
back = buildBoardFace(face: faces.back, size: cubeSize, customEngine: frontGame);
break;
case faces.left:
left = buildBoardFace(face: faces.left, size: cubeSize, customEngine: frontGame);
}
}
}
@override
void initState() {
_animationController = AnimationController(duration: Duration(milliseconds: 3000), vsync: this);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
generateFaces();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
updateFaces(0.65, 0.8);
});
super.initState();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (det){
double bX = 0.0, bY = 0.0;
if ((rx >= 0 && rx <= 1.70) || (rx >= 4.70)){
bX = det.delta.dy * 0.01;
bY = -det.delta.dx * 0.01;
}else{
bX = det.delta.dy * 0.01;
bY = det.delta.dx * 0.01;
}
updateFaces(bX, bY);
},
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ChangeNotifierProvider(
create: (context)=>frontGame,
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(rx)
..rotateY(ry)
..rotateZ(rz),
alignment: Alignment.center,
child: SizedBox(
height: cubeSize,
width: cubeSize,
child: Stack(
children: [
...children
],
),
),
),
),
SizedBox(height: 20.h),
Slider(value: rx,
min: 0,
max: pi * 2,
onChanged: (value){
rx = value;
setState(() {
updateFaces(rx, 0);
});
}),
Text(rx.toString()),
Slider(value: ry,
min: 0,
max: pi * 2,
onChanged: (value){
ry = value;
setState(() {
updateFaces(0, ry);
});
}),
Text(ry.toString()),
Slider(value: rz,
min: 0,
max: pi * 2,
onChanged: (value){
rz = value;
setState(() {
});
}),
Text(rz.toString())
],
)
),
),
);
}
buildBoardFace({required faces face, required double size, GameEngine? customEngine}){
late double rotateX;
late double rotateY;
late double trX;
late double trY;
late double trZ;
switch (face){
case faces.top:
trX = 0.0;
trY = -cubeSize/2;
trZ = 0.0;
rotateX = -pi / 2;
rotateY = 0.0;
case faces.bottom:
trX = 0.0;
trY = cubeSize/2;
trZ = 0.0;
rotateX = pi / 2;
rotateY = 0.0;
case faces.right:
trX = cubeSize/2;
trY = 0.0;
trZ = 0.0;
rotateX = 0.0;
rotateY = -pi / 2;
case faces.left:
trX = -cubeSize/2;
trY = 0.0;
trZ = 0.0;
rotateX = 0.0;
rotateY = pi / 2;
case faces.front:
trX = 0.0;
trY = 0.0;
trZ = -cubeSize/2;
rotateX = 0.0;
rotateY = 0.0;
case faces.back:
trX = 0.0;
trY = 0.0;
trZ = cubeSize/2;
rotateX = 0.0;
rotateY = pi;
}
List<Widget> lines = [];
// Draw box lines
init(size, size, lines, Colors.lightBlueAccent.withOpacity(0.5), _animationController);
final linearGrid = <int>[];
if (customEngine != null) {
for (var i in customEngine.grid){
linearGrid.addAll(i);
}
}
return face != faces.front ? Transform(
transform: Matrix4.identity()
..translate(trX, trY, trZ)
..rotateX(rotateX)
..rotateY(rotateY),
alignment: Alignment.center,
child: Container(
height: size,
width: size,
color: colorLightYellow,
child: Image.asset('assets/patternXO.png'),
)
) :
SizedBox(
height: size,
width: size,
child: Container(
alignment: Alignment.topLeft,
child: Stack(
children: lines..addAll([
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: linearGrid.length,
itemBuilder: (context, index){
return InkWell(
onTap: () async{
gameWinner? winner;
if (linearGrid[index] == -1){
winner = customEngine?.setManualMove(isO: false, ((index ~/ 3),(index % 3)));
setState(() {});
await Future.delayed(const Duration(milliseconds: 1000));
winner = customEngine?.setAiMove(isO: true);
}
setState(() {});
},
child: Container(
padding: const EdgeInsets.all(5),
child: linearGrid[index] == -1 ? Container() :
Icon(linearGrid[index] == 0 ? CupertinoIcons.circle : CupertinoIcons.xmark,
color: Colors.blue, size: 12.w),
),
);
}),
],
),
]),
),
),
);
}
}
下面是我运行此顶部代码时的记录,该代码仅给正面(1/6 面)一个 gridView 子项:
现在我已经尝试了多种解决方案,但问题似乎主要是因为一旦我引入这些 GridView 或 ListView 小部件,我为旋转并将侧面固定到其 3d 位置而进行的计算就会变得混乱,应该重新计算为其他一些我无法想象。
供参考:
这段代码我已经尝试过,并添加了带有 gridview 的代码部分,它工作正常,它有另一种计算位置的方法,并且只渲染 3 个边,我不希望它发生在我的应用程序:
你能解决这个问题吗?因为我atm也面临这个问题