flutter - Flutter:从具有正确重叠的矩阵变换中布局具有不同 z 坐标的多个子级
问题描述
我正在尝试构建 Threejs 周期表 Helix 视图的 Flutter Web 版本(请参见此处:https ://mrdoob.com/lab/javascript/threejs/css3d/periodictable/并单击“Helix”)
目前,我将所有元素图块放在一个堆栈中,并使用矩阵变换定位和旋转它们。见下面的代码:
@override
Widget build(BuildContext context) {
double initialRotateX = (widget.atomicNumber - 1) *
(math.pi / Constants.numberOfElementsPerHalfCircle);
return Transform(
transform: Matrix4.identity()
..translate(
math.sin((widget.atomicNumber - 1) *
(math.pi / Constants.numberOfElementsPerHalfCircle)) *
Constants.radius,
widget.atomicNumber * 4,
-math.cos((widget.atomicNumber - 1) *
(math.pi / Constants.numberOfElementsPerHalfCircle)) *
Constants.radius,
)
..translate(Constants.elementCardHalfSize)
..rotateY(-initialRotateX)
..translate(-Constants.elementCardHalfSize),
child: Container(
decoration: BoxDecoration(
color: Constants.elementCardColor,
border: Border.all(width: 1, color: Colors.white.withOpacity(0.3))),
child: SizedBox(
width: Constants.elementCardWidth.toDouble(),
height: Constants.elementCardHeight.toDouble(),
child: ElementText()
),
),
);
然后将所有这些卡片放在另一个类的堆栈小部件中,然后旋转该小部件,如下所示:
return AnimatedBuilder(
animation: widget.animationControllerX,
builder: (context, _) {
double rotateX =
widget.animationControllerX.value / Constants.turnResolution;
double rotateY =
widget.animationControllerY.value / Constants.turnResolution;
return Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(-rotateX)
..rotateX(rotateY),
alignment: Alignment.center,
child: Container(
child: Stack(
children: List<Widget>.generate(
Constants.numberOfElements,
(index) => ElementCard(
atomicNumber:
Constants.elements[(index + 1).toString()].atomicNumber,
),
),
),
),
);
},
);
如您所见,应用了“..rotateY”转换,这使得卡片在堆栈旋转时看起来向前移动。
然而,在旋转时,应该在后面的卡片会更小,但会在应该在前面的卡片上涂漆。(毫无疑问,卡片的位置是正确的,因为我可以沿 x 轴旋转它们并亲眼看到,但是在绘画时,背面的卡片会覆盖在正面卡片中的文字上。我相信这是因为堆栈总是根据它们提供的顺序定位其小部件的高度,并且顺序在列表中是固定的。有什么办法可以解决这个问题吗?
截图来解释我的意思:
我可以更改图块的不透明度以使问题更加明显:
如您所见,较大的卡片更靠近屏幕,因为它们是这样转换的,但它们是在较小的卡片后面绘制的。关于如何实现这一点的任何想法?
解决方案
所以我找到了解决方案。它并不完美,但到目前为止它仍然有效。
问题是 Flutter 按照它们出现的顺序绘制小部件。由于较大的卡片在列表中较高,即使它们具有更接近的 z 坐标,它们也会首先被绘制,而较小的卡片被绘制在它们的顶部。
解决方案是使用 Flow() 小部件来动态更改卡片的绘制顺序。在我通常构建它们的子小部件中,我将它们的转换存储在地图中。然后,在流委托函数中,访问这些转换,计算转换后每个小部件的 z 坐标(转换矩阵中的第 3 行,第 3 列(1 索引))。按 z 坐标对小部件进行排序,然后按该顺序在屏幕上绘制它们。
这是相同的代码:
带有根 Flow 小部件的部分:
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.animationControllerX,
builder: (context, _) {
double rotateX =
widget.animationControllerX.value / Constants.turnResolution;
double rotateY =
widget.animationControllerY.value / Constants.turnResolution;
return Container(
transform: Matrix4.identity()
..translate(
MediaQuery.of(context).size.width / 2,
),
child: Flow(
clipBehavior: Clip.none,
delegate: CustomFlowDelegate(rotateY: -rotateX, rotateX: rotateY),
children: List<Widget>.generate(
Constants.numberOfElements,
(index) => ElementCard(
atomicNumber:
Constants.elements[(index + 1).toString()].atomicNumber,
),
),
),
);
},
);
}
小部件的 FlowDelegate:
class CustomFlowDelegate extends FlowDelegate {
CustomFlowDelegate({this.rotateX, this.rotateY});
final rotateY, rotateX;
@override
void paintChildren(FlowPaintingContext context) {
Map<int, double> zValue = {};
for (int i = 0; i < context.childCount; i++) {
var map = Constants.transformationsMap[i + 1];
Matrix4 transformedMatrix = Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(3, 1, 0.001)
..rotateY(this.rotateY)
..rotateX(this.rotateX)
..translate(
map['translateOneX'], map['translateOneY'], map['translateOneZ'])
..translate(map['translateTwoX'])
..rotateY(map['rotateThreeY'])
..translate(map['translateFourX']);
zValue[i + 1] = transformedMatrix.getRow(2)[2];
}
var sortedKeys = zValue.keys.toList(growable: false)
..sort((k1, k2) => zValue[k1].compareTo(zValue[k2]));
LinkedHashMap sortedMap = new LinkedHashMap.fromIterable(sortedKeys,
key: (k) => k, value: (k) => zValue[k]);
for (int key in sortedMap.keys) {
var map = Constants.transformationsMap[key];
context.paintChild(
key - 1,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(3, 1, 0.001)
..rotateY(this.rotateY)
..rotateX(this.rotateX)
..translate(
map['translateOneX'], map['translateOneY'], map['translateOneZ'])
..translate(map['translateTwoX'])
..rotateY(map['rotateThreeY'])
..translate(map['translateFourX']),
);
}
}
@override
bool shouldRepaint(covariant FlowDelegate oldDelegate) {
return true;
}
}
结果如下:
随着不透明度的提高,你可以真正看到修复的效果:
是的,我知道将转换存储在地图中,然后从另一个页面对它们进行排序并不是最佳实践。
希望这可以帮助某人。
推荐阅读
- javascript - 在 redux reducer 和 action 中获取数据 - 怎么做
- c# - 将一行中的重复项整理出来,但保留一个特定的
- c++ - C++ 字符串返回和 c_str() 强制转换的令人困惑的行为
- javascript - CSS:背景img不居中
- excel - 使用 EPPlus for Excel,如何将焦点设置在活动工作表中的单元格上?
- python - 如何在 dockerpy 中从 docker-run 管道标准输出
- python - 在 DJANGO 中运行服务器时应用程序名称未注册
- cocoapods - 创建一个 cocoapods nexus 存储库
- h3 - 计算周围的索引键
- powershell - powershell中的嵌套foreach循环