Flutter 颤振:使用与矩阵变换不同的z坐标布局多个子元素,并具有正确的重叠

Flutter 颤振:使用与矩阵变换不同的z坐标布局多个子元素,并具有正确的重叠,flutter,dart,flutter-layout,flutter-web,flutter-animation,Flutter,Dart,Flutter Layout,Flutter Web,Flutter Animation,我正在尝试构建TrithJS元素周期表螺旋视图的颤振Web版本(请参见此处:并单击“螺旋”) 目前,我将所有元素平铺放在一个堆栈中,并使用矩阵变换对它们进行定位和旋转。见以下代码: @override Widget build(BuildContext context) { double initialRotateX = (widget.atomicNumber - 1) * (math.pi / Constants.numberOfElementsPerHalf

我正在尝试构建TrithJS元素周期表螺旋视图的颤振Web版本(请参见此处:并单击“螺旋”)

目前,我将所有元素平铺放在一个堆栈中,并使用矩阵变换对它们进行定位和旋转。见以下代码:

  @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,
                ),
              ),
            ),
          ),
        );
      },
    );
返回AnimatedBuilder(
动画:widget.animationControllerX,
构建器:(上下文,ux){
双旋转=
widget.animationControllerX.value/Constants.turnsolution;
双旋转=
widget.animationControllerY.value/Constants.turnsolution;
返回变换(
转换:Matrix4.identity()
..设置条目(3,2,0.001)
…rotateY(-rotateX)
…rotateX(rotateY),
对齐:对齐.center,
子:容器(
子:堆栈(
子项:List.generate(
常量。numberOfElements,
(索引)=>ElementCard(
原子编号:
常量.elements[(索引+1).toString()].atomicNumber,
),
),
),
),
);
},
);
如您所见,应用了“.rotateY”变换,这使卡片在堆栈旋转时看起来向前移动

然而,当旋转时,应该在后面的卡片较小,但是应该在前面的卡片上涂漆。(毫无疑问,这些卡片的位置是正确的,因为我可以沿着x轴旋转它们,并亲眼看到,但是当绘制时,背面的卡片被绘制在前面卡片中的文本上。我相信这是因为堆栈总是根据它们在中提供的顺序来定位其小部件的高程,并且订单在列表中是固定的。我有什么办法可以解决这个问题吗

屏幕截图解释我的意思:

我可以更改瓷砖的不透明度,使问题更加明显:


正如你所看到的,较大的卡片离屏幕更近,因为这就是它们的转换方式,但它们在较小的卡片后面进行绘画。关于如何实现这一点,有什么想法吗?

因此我找到了一个解决方案。它并不完美,但迄今为止仍然有效

想法是使用Flow()小部件。在我通常构建它们的子小部件中,我将它们的转换存储在映射中。然后,在Flow delegate函数中,访问这些转换,计算转换后每个小部件的z坐标(转换矩阵中的第3行第3列(1个索引)).按z坐标对小部件进行排序,然后按该顺序在屏幕上绘制它们

以下是相同的代码:

具有根流小部件的部件:

@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,
              ),
            ),
          ),
        );
      },
    );
  }
@覆盖
小部件构建(构建上下文){
返回动画生成器(
动画:widget.animationControllerX,
构建器:(上下文,ux){
双旋转=
widget.animationControllerX.value/Constants.turnsolution;
双旋转=
widget.animationControllerY.value/Constants.turnsolution;
返回容器(
转换:Matrix4.identity()
…翻译(
MediaQuery.of(context).size.width/2,
),
孩子:流(
clipBehavior:Clip.none,
代表:CustomFlowDelegate(rotateY:-rotateX,rotateX:rotateY),
子项:List.generate(
常量。numberOfElements,
(索引)=>ElementCard(
原子编号:
常量.elements[(索引+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;
  }
}
class CustomFlowDelegate扩展了FlowDelegate{
CustomFlowDelegate({this.rotateX,this.rotateY});
最终旋转,旋转;
@凌驾
void paintChildren(FlowPaintingContext上下文){
映射zValue={};
for(int i=0;izValue[k1]。比较(zValue[k2]);
LinkedHashMap sortedMap=新建LinkedHashMap.fromIterable(sortedKeys,
键:(k)=>k,值:(k)=>zValue[k]);
用于(分拣地图中的int键){
var map=常量。transformationsMap[键];
context.paintChild(
键-1,
转换:Matrix4.identity()
..设置条目(3,2,0.001)
..设置条目(3,1,0.001)
…rotateY(这个,rotateY)
..rotateX(这个是rotateX)
…翻译(
映射['translateOneX'],映射['translateOneY'],映射['translateOneZ'])
…翻译(映射['translateBox'])
…rotateY(映射['rotateThreeY'])
…翻译(映射['translateForx']),
);
}
}
@凌驾
布尔应重新绘制(协变流委托oldDelegate){
返回true;
}
}
结果如下:

随着不透明度的提高,您可以真正看到修复的效果:

是的,我知道将转换存储在地图中,然后从另一个页面对它们进行排序不是最佳做法

希望这对别人有帮助