You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在水平布局下使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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 00:07:08