我有一个
Container
和一个孩子,我想轮换。旋转时,我希望 Container
的大小与子项的新边框相匹配。
假设我有以下设置:
Container(
margin: EdgeInsets.all(200),
child: Stack(
children: [
IntrinsicHeight(
child: Container(
color: Colors.green,
child: Transform.rotate(
angle: 45 * 3.14 / 180,
child: Container(
color: Colors.amber,
child: Text("Whatever"),
)
),
),
),
],
),
),
这给出了以下输出:
我想要的输出是:
以便
Container
与旋转子项的实际尺寸相匹配。我怎样才能实现这个目标?
我认为
IntrinsicHeight
可以解决问题,但它似乎并不关心旋转。我也尝试用RotationTransition
旋转,但这也不起作用
真是一个测验!我认为这是可能的,但我不知道如何使用任何预定义的小部件来实现这一点。相反,我尝试使用自定义
RenderObject
和 RenderBox
来实现此逻辑。这是我得到的。
简而言之:我们在布局模式期间对内部容器应用转换,并根据转换后的容器的大小计算外部小部件的大小。
首先,不得不说这将包括一些繁重的数学计算,所以要考虑到这一点。
现在,我们需要一些导入来计算旋转(我们将自己完成,因为不幸的是
Transform.rotate
不会为我们提供有关旋转角度的必要信息):
import 'dart:math' as math;
import 'package:vector_math/vector_math_64.dart' show Vector3;
接下来,创建类似容器的小部件,它将采用 one 子级(独占)和旋转。我还添加了颜色参数,但如果需要,您可以添加更多属性。
class FittedRotationContainer extends StatelessWidget {
const FittedRotationContainer({
Key? key,
required this.child,
required this.transform,
this.color,
}) : super(key: key);
final Widget child;
final Matrix4 transform;
final Color? color;
@override
Widget build(BuildContext context) {
return FittedTransformBox(
transform: transform,
color: color,
child: child,
);
}
}
现在,事情变得有点沉重了。我们必须实现实际的绘画逻辑。我们将在其中使用
SingleChildRenderObjectWidget
和 RenderProxyBox
。他们一起工作有点像StatefulWidget
和State
。
class FittedTransformBox extends SingleChildRenderObjectWidget {
const FittedTransformBox({
super.key,
required this.transform,
super.child,
this.color,
});
final Matrix4 transform;
final Color? color;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderFittedTransformBox(transform: transform, color: color);
}
@override
void updateRenderObject(
BuildContext context, _RenderFittedTransformBox renderObject) {
renderObject
..transform = transform
..color = color;
}
}
class _RenderFittedTransformBox extends RenderProxyBox {
_RenderFittedTransformBox({
required Matrix4 transform,
Color? color,
}) : _transform = transform,
_color = color;
Matrix4 get transform => _transform;
Matrix4 _transform;
set transform(Matrix4 value) {
if (_transform != value) {
_transform = value;
markNeedsLayout();
}
}
Color? get color => _color;
Color? _color;
set color(Color? value) {
if (_color != value) {
_color = value;
markNeedsPaint();
}
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! BoxParentData) {
child.parentData = BoxParentData();
}
}
@override
void performLayout() {
if (child != null) {
child!.layout(constraints.loosen(), parentUsesSize: true);
final childSize = child!.size;
final childParentData = child!.parentData as BoxParentData;
final transformedCorners = [
_transform.transform3(Vector3(0, 0, 0)),
_transform.transform3(Vector3(childSize.width, 0, 0)),
_transform.transform3(Vector3(0, childSize.height, 0)),
_transform.transform3(Vector3(childSize.width, childSize.height, 0)),
];
double minX = double.infinity, minY = double.infinity;
double maxX = double.negativeInfinity, maxY = double.negativeInfinity;
for (final corner in transformedCorners) {
minX = math.min(minX, corner.x);
minY = math.min(minY, corner.y);
maxX = math.max(maxX, corner.x);
maxY = math.max(maxY, corner.y);
}
final fittedSize = Size(maxX - minX, maxY - minY);
size = constraints.constrain(fittedSize);
childParentData.offset = Offset(
(size.width - fittedSize.width) / 2 - minX,
(size.height - fittedSize.height) / 2 - minY,
);
} else {
size = constraints.smallest;
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (_color != null) {
context.canvas.drawRect(offset & size, Paint()..color = _color!);
}
if (child != null) {
final childParentData = child!.parentData as BoxParentData;
context.pushTransform(
needsCompositing,
offset + childParentData.offset,
_transform,
super.paint,
);
}
}
}
这就是您在应用程序中使用它的方式:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
margin: EdgeInsets.all(200),
child: Stack(
children: [
Center(
child: FittedRotationContainer(
color: Colors.green,
transform: Matrix4.rotationZ(90 * math.pi / 180),
child: Container(
color: Colors.amber,
child: const Padding(
padding: EdgeInsets.all(20.0),
child: Text("Whatever"),
),
),
),
)
],
),
),
);
}