如何在水平布局下使OverflowBar子组件沿交叉轴垂直对齐?
实现OverflowBar水平布局时子组件垂直对齐的方法
原生OverflowBar的overflowAlignment仅在垂直布局时生效,水平布局时未处理垂直方向的交叉轴对齐。以下两种方案可解决你的需求:
方案一:自定义CustomOverflowBar(推荐)
通过复制并修改OverflowBar的源码,新增crossAxisAlignment参数,实现水平布局时的垂直对齐逻辑:
自定义组件代码
import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; class CustomOverflowBar extends MultiChildRenderObjectWidget { CustomOverflowBar({ super.key, required super.children, this.axis = Axis.horizontal, this.spacing = 0.0, this.overflowSpacing = 0.0, this.overflowAlignment = OverflowBarAlignment.start, this.crossAxisAlignment = CrossAxisAlignment.center, }); final Axis axis; final double spacing; final double overflowSpacing; final OverflowBarAlignment overflowAlignment; final CrossAxisAlignment crossAxisAlignment; @override RenderObject createRenderObject(BuildContext context) { return RenderCustomOverflowBar( axis: axis, spacing: spacing, overflowSpacing: overflowSpacing, overflowAlignment: overflowAlignment, crossAxisAlignment: crossAxisAlignment, ); } @override void updateRenderObject(BuildContext context, RenderCustomOverflowBar renderObject) { renderObject ..axis = axis ..spacing = spacing ..overflowSpacing = overflowSpacing ..overflowAlignment = overflowAlignment ..crossAxisAlignment = crossAxisAlignment; } } class RenderCustomOverflowBar extends RenderBox with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>, RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> { RenderCustomOverflowBar({ List<RenderBox>? children, Axis axis = Axis.horizontal, double spacing = 0.0, double overflowSpacing = 0.0, OverflowBarAlignment overflowAlignment = OverflowBarAlignment.start, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, }) : _axis = axis, _spacing = spacing, _overflowSpacing = overflowSpacing, _overflowAlignment = overflowAlignment, _crossAxisAlignment = crossAxisAlignment { addAll(children); } Axis get axis => _axis; Axis _axis; set axis(Axis value) { if (_axis == value) return; _axis = value; markNeedsLayout(); } double get spacing => _spacing; double _spacing; set spacing(double value) { if (_spacing == value) return; _spacing = value; markNeedsLayout(); } double get overflowSpacing => _overflowSpacing; double _overflowSpacing; set overflowSpacing(double value) { if (_overflowSpacing == value) return; _overflowSpacing = value; markNeedsLayout(); } OverflowBarAlignment get overflowAlignment => _overflowAlignment; OverflowBarAlignment _overflowAlignment; set overflowAlignment(OverflowBarAlignment value) { if (_overflowAlignment == value) return; _overflowAlignment = value; markNeedsLayout(); } CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment; CrossAxisAlignment _crossAxisAlignment; set crossAxisAlignment(CrossAxisAlignment value) { if (_crossAxisAlignment == value) return; _crossAxisAlignment = value; markNeedsLayout(); } @override void performLayout() { if (children.isEmpty) { size = constraints.smallest; return; } final bool isHorizontal = axis == Axis.horizontal; final double mainAxisSpacing = spacing; final double crossAxisSpacing = overflowSpacing; final List<RenderBox> visibleChildren = []; double totalMainAxisExtent = 0.0; double maxCrossAxisExtent = 0.0; for (final child in children) { child.layout(constraints.loosen(), parentUsesSize: true); final childMainAxisExtent = isHorizontal ? child.size.width : child.size.height; final childCrossAxisExtent = isHorizontal ? child.size.height : child.size.width; totalMainAxisExtent += childMainAxisExtent; if (childCrossAxisExtent > maxCrossAxisExtent) { maxCrossAxisExtent = childCrossAxisExtent; } visibleChildren.add(child); } totalMainAxisExtent += mainAxisSpacing * (visibleChildren.length - 1); final double availableMainAxisExtent = isHorizontal ? constraints.maxWidth : constraints.maxHeight; if (totalMainAxisExtent <= availableMainAxisExtent) { double currentMainAxisPosition = 0.0; for (final child in visibleChildren) { final childMainAxisExtent = isHorizontal ? child.size.width : child.size.height; final childCrossAxisExtent = isHorizontal ? child.size.height : child.size.width; double crossAxisPosition = 0.0; if (isHorizontal) { switch (crossAxisAlignment) { case CrossAxisAlignment.start: crossAxisPosition = 0.0; break; case CrossAxisAlignment.center: crossAxisPosition = (maxCrossAxisExtent - childCrossAxisExtent) / 2; break; case CrossAxisAlignment.end: crossAxisPosition = maxCrossAxisExtent - childCrossAxisExtent; break; case CrossAxisAlignment.stretch: crossAxisPosition = 0.0; child.layout( isHorizontal ? constraints.copyWith(minHeight: maxCrossAxisExtent, maxHeight: maxCrossAxisExtent) : constraints.copyWith(minWidth: maxCrossAxisExtent, maxWidth: maxCrossAxisExtent), parentUsesSize: true, ); break; default: crossAxisPosition = 0.0; } } else { switch (overflowAlignment) { case OverflowBarAlignment.start: crossAxisPosition = 0.0; break; case OverflowBarAlignment.center: crossAxisPosition = (constraints.maxWidth - child.size.width) / 2; break; case OverflowBarAlignment.end: crossAxisPosition = constraints.maxWidth - child.size.width; break; } } final parentData = child.parentData as MultiChildLayoutParentData; parentData.offset = isHorizontal ? Offset(currentMainAxisPosition, crossAxisPosition) : Offset(crossAxisPosition, currentMainAxisPosition); currentMainAxisPosition += childMainAxisExtent + mainAxisSpacing; } size = isHorizontal ? Size(totalMainAxisExtent, maxCrossAxisExtent) : Size(maxCrossAxisExtent, totalMainAxisExtent); } else { List<RenderBox> mainLineChildren = []; double mainLineMainAxisExtent = 0.0; double mainLineMaxCrossAxisExtent = 0.0; for (final child in visibleChildren) { final childMainAxisExtent = isHorizontal ? child.size.width : child.size.height; final childCrossAxisExtent = isHorizontal ? child.size.height : child.size.width; final newMainAxisExtent = mainLineMainAxisExtent + (mainLineChildren.isEmpty ? 0 : mainAxisSpacing) + childMainAxisExtent; if (newMainAxisExtent <= availableMainAxisExtent || mainLineChildren.isEmpty) { mainLineChildren.add(child); mainLineMainAxisExtent = newMainAxisExtent; if (childCrossAxisExtent > mainLineMaxCrossAxisExtent) { mainLineMaxCrossAxisExtent = childCrossAxisExtent; } } else { break; } } final List<RenderBox> overflowChildren = visibleChildren.sublist(mainLineChildren.length); double overflowMainAxisExtent = 0.0; double overflowMaxCrossAxisExtent = 0.0; for (final child in overflowChildren) { final childMainAxisExtent = isHorizontal ? child.size.width : child.size.height; final childCrossAxisExtent = isHorizontal ? child.size.height : child.size.width; overflowMainAxisExtent += childMainAxisExtent + (overflowChildren.indexOf(child) == 0 ? 0 : overflowSpacing); if (childCrossAxisExtent > overflowMaxCrossAxisExtent) { overflowMaxCrossAxisExtent = childCrossAxisExtent; } } final double totalCrossAxisExtent = mainLineMaxCrossAxisExtent + crossAxisSpacing + overflowMaxCrossAxisExtent; double currentMainAxisPosition = 0.0; for (final child in mainLineChildren) { final childMainAxisExtent = isHorizontal ? child.size.width : child.size.height; final childCrossAxisExtent = isHorizontal ? child.size.height : child.size.width; double crossAxisPosition = 0.0; if (isHorizontal) { switch (crossAxisAlignment) { case CrossAxisAlignment.start: crossAxisPosition = 0.0; break; case CrossAxisAlignment.center: crossAxisPosition = (mainLineMaxCrossAxisExtent - childCrossAxisExtent) / 2; break; case CrossAxisAlignment.end: crossAxisPosition = mainLineMaxCrossAxisExtent - childCrossAxisExtent; break; case CrossAxisAlignment.stretch: crossAxisPosition = 0.0; child.layout( isHorizontal ? constraints.copyWith(minHeight: mainLineMaxCrossAxisExtent, maxHeight: mainLineMaxCrossAxisExtent) : constraints.copyWith(minWidth: mainLineMaxCrossAxisExtent, maxWidth: mainLineMaxCrossAxisExtent), parentUsesSize: true, ); break; default: crossAxisPosition = 0.0; } } else { switch (overflowAlignment) { case OverflowBarAlignment.start: crossAxisPosition = 0.0; break; case OverflowBarAlignment.center: crossAxisPosition = (constraints.maxWidth - child.size.width) / 2; break; case OverflowBarAlignment.end: crossAxisPosition = constraints.maxWidth - child.size.width; break; } } final parentData = child.parentData as MultiChildLayoutParentData; parentData.offset = isHorizontal ? Offset(currentMainAxisPosition, crossAxisPosition) : Offset(crossAxisPosition, currentMainAxisPosition); currentMainAxisPosition += childMainAxisExtent + mainAxisSpacing; } if (isHorizontal) { switch (overflowAlignment) { case OverflowBarAlignment.start: currentMainAxisPosition = 0.0; break; case OverflowBarAlignment.center: currentMainAxisPosition = (availableMainAxisExtent - overflowMainAxisExtent) / 2; break; case OverflowBarAlignment.end: currentMainAxisPosition = availableMainAxisExtent - overflowMainAxisExtent; break; } } else { currentMainAxisPosition = mainLineMainAxisExtent + crossAxisSpacing; } for (final child in overflowChildren) { final childMainAxisExtent = isHorizontal ? child.size.width : child.size.height; final childCrossAxisExtent = isHorizontal ? child.size.height : child.size.width; double crossAxisPosition = mainLineMaxCrossAxisExtent + crossAxisSpacing; if (isHorizontal) { switch (crossAxisAlignment) { case CrossAxisAlignment.start: crossAxisPosition = mainLineMaxCrossAxisExtent + crossAxisSpacing; break; case CrossAxisAlignment.center: crossAxisPosition = mainLineMaxCrossAxisExtent + crossAxisSpacing + (overflowMaxCrossAxisExtent - childCrossAxisExtent) / 2; break; case CrossAxisAlignment.end: crossAxisPosition = mainLineMaxCrossAxisExtent + crossAxisSpacing + overflowMaxCrossAxisExtent - childCrossAxisExtent; break; case CrossAxisAlignment.stretch: crossAxisPosition = mainLineMaxCrossAxisExtent + crossAxisSpacing; child.layout( isHorizontal ? constraints.copyWith(minHeight: overflowMaxCrossAxisExtent, maxHeight: overflowMaxCrossAxisExtent) : constraints.copyWith(minWidth: overflowMaxCrossAxisExtent, maxWidth: overflowMaxCrossAxisExtent), parentUsesSize: true, ); break; default: crossAxisPosition = mainLineMaxCrossAxisExtent + crossAxisSpacing; } } else { switch (overflowAlignment) { case OverflowBarAlignment.start: crossAxisPosition = 0.0; break; case OverflowBarAlignment.center: crossAxisPosition = (constraints.maxWidth - child.size.width) / 2; break; case OverflowBarAlignment.end: crossAxisPosition = constraints.maxWidth - child.size.width; break; } } final parentData = child.parentData as MultiChildLayoutParentData; parentData.offset = isHorizontal ? Offset(currentMainAxisPosition, crossAxisPosition) : Offset(crossAxisPosition, currentMainAxisPosition); currentMainAxisPosition += childMainAxisExtent + overflowSpacing; } size = isHorizontal ? Size(availableMainAxisExtent, totalCrossAxisExtent) : Size(maxCrossAxisExtent, mainLineMainAxisExtent + crossAxisSpacing + overflowMainAxisExtent); } } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { return defaultHitTestChildren(result, position: position); } @override void paint(PaintingContext context, Offset offset) { defaultPaint(context, offset); } }
使用示例
CustomOverflowBar( axis: Axis.horizontal, spacing: 8.0, overflowSpacing: 8.0, crossAxisAlignment: CrossAxisAlignment.center, // 设置垂直居中对齐 children: [ Container(width: 100, height: 40, color: Colors.red), Container(width: 100, height: 60, color: Colors.green), Container(width: 100, height: 80, color: Colors.blue), Container(width: 100, height: 50, color: Colors.yellow), ], )
方案二:给子组件套Align组件(快速实现)
如果是简单场景,无需自定义组件,可给每个子组件包裹Align指定垂直对齐方式:
OverflowBar( axis: Axis.horizontal, spacing: 8.0, overflowSpacing: 8.0, children: [ Align(alignment: Alignment.center, child: Container(width: 100, height: 40, color: Colors.red)), Align(alignment: Alignment.center, child: Container(width: 100, height: 60, color: Colors.green)), Align(alignment: Alignment.center, child: Container(width: 100, height: 80, color: Colors.blue)), Align(alignment: Alignment.center, child: Container(width: 100, height: 50, color: Colors.yellow)), ], )
该方法缺点是需要给每个子组件手动添加Align,适合临时或简单布局场景。
内容的提问来源于stack exchange,提问作者Abigotado
相关产品推荐
相关产品推荐

