首页 > 解决方案 > Flutter 中的渐进式菜单(隐藏下拉菜单中的溢出元素)

问题描述

我正在构建一个可调整大小的 Flutter 桌面应用程序,我想知道如何自动隐藏菜单中溢出的项目(例如 a Row)并使它们在“更多”下拉列表中可见。

概念样本图片(来源:https ://css-tricks.com/container-adapting-tabs-with-more-button/ )

有很多可用的宽度(显示所有项目): 有很多可用的宽度

可用宽度较小(隐藏下拉菜单中的项目): 在此处输入图像描述

提前致谢!

更新:我有使用Wrap-element 的想法,但后来我遇到了以下问题:

标签: flutteruser-interfacedartflutter-layout

解决方案


也许您可以尝试创建一个MultiChildRenderObjectWidget可以在绘画之前计算孩子大小的模型。虽然它更复杂,因为您喜欢制作自定义Row类。

我创建了一个示例,但它可能仍然包含错误并且需要一些改进。

示例...阈值 collapsible_menu_bar.dart

import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class MenuBar extends StatelessWidget {
  MenuBar({
    required this.onItemPressed,
    required this.children,
    this.minItemWidth = 110,
    this.minMoreItemsWidth = 70,
    Key? key,
  }) : super(key: key);

  final ValueChanged<int> onItemPressed;
  final List<MenuBarItem> children;
  final double minItemWidth;
  final double minMoreItemsWidth;

  static int tmpStartIndex = 0;

  @override
  Widget build(BuildContext context) {
    return CollapsibleMenuBar(
      onCollapseIndex: (int startIndex) {
        if (tmpStartIndex == startIndex) {
          return;
        }
        tmpStartIndex = startIndex;
      },
      minItemWidth: minItemWidth,
      minMoreItemsWidth: minMoreItemsWidth,
      children: [
        ...children,
        PopupMenuButton(
          offset: const Offset(0, 40),
          color: Colors.red,
          child: Container(
            height: 40,
            padding: const EdgeInsets.symmetric(horizontal: 10),
            alignment: Alignment.center,
            color: Colors.amber,
            child: const Text('More'),
          ),
          itemBuilder: (_) => children
              .sublist(tmpStartIndex)
              .map((e) => PopupMenuItem(child: e))
              .toList(),
        ),
      ],
    );
  }
}

///
///
///
class MenuBarItem extends StatelessWidget {
  const MenuBarItem({
    required this.onPressed,
    required this.text,
    Key? key,
  }) : super(key: key);

  final VoidCallback? onPressed;
  final String text;

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: onPressed,
      style: TextButton.styleFrom(
        backgroundColor: Colors.red,
        padding: const EdgeInsets.all(20),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.zero,
        ),
      ),
      child: Text(
        text,
        style: const TextStyle(color: Colors.white),
      ),
    );
  }
}

///
///
///
class CollapsibleMenuBar extends MultiChildRenderObjectWidget {
  CollapsibleMenuBar({
    required this.onCollapseIndex,
    required List<Widget> children,
    required this.minItemWidth,
    required this.minMoreItemsWidth,
    Key? key,
  }) : super(key: key, children: children);

  final ValueChanged<int> onCollapseIndex;
  final double minItemWidth;
  final double minMoreItemsWidth;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCollapsibleMenuBar(
      onCollapseIndex,
      minItemWidth,
      minMoreItemsWidth,
    );
  }
}

///
///
///
class CollapsibleMenuBarParentData extends ContainerBoxParentData<RenderBox> {}

///
///
///
class RenderCollapsibleMenuBar extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, CollapsibleMenuBarParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox,
            CollapsibleMenuBarParentData> {
  RenderCollapsibleMenuBar(
    this.onCollapseIndex,
    this.minItemWidth,
    this.minMoreItemsWidth,
  );

  final ValueChanged<int> onCollapseIndex;
  final double minItemWidth;
  final double minMoreItemsWidth;

  @override
  void setupParentData(covariant RenderObject child) {
    if (child.parentData is! CollapsibleMenuBarParentData) {
      child.parentData = CollapsibleMenuBarParentData();
    }
  }

  @override
  void performLayout() {
    // Make width of children equal.
    final double childWidth = max(
      constraints.maxWidth / (childCount - 1),
      minItemWidth,
    );

    double totalWidth = 0;
    double totalHeight = 0;

    RenderBox? child = firstChild;
    Offset childOffset = Offset(0, 0);

    int childIdx = 0;

    while (child != null && child != lastChild) {
      CollapsibleMenuBarParentData childParentData =
          child.parentData as CollapsibleMenuBarParentData;

      // Set child's dimension.
      child.layout(
        BoxConstraints(
          minWidth: childWidth,
          maxWidth: childWidth,
          maxHeight: constraints.maxHeight,
        ),
        parentUsesSize: true,
      );

      // If the total width exceeds the max screen width,
      // display "more" item.
      if (totalWidth + child.size.width > constraints.maxWidth) {
        // Set overflow item dimension to 0.
        child.layout(
          BoxConstraints(
            minWidth: 0,
            maxWidth: 0,
            maxHeight: constraints.maxHeight,
          ),
          parentUsesSize: true,
        );

        // Get popup menu item.
        child = lastChild!;
        childParentData = child.parentData as CollapsibleMenuBarParentData;
        // Set popup menu item's dimension. Will cover the remaining width.
        child.layout(
          BoxConstraints(
            minWidth: constraints.maxWidth - totalWidth,
            maxWidth: constraints.maxWidth - totalWidth,
            maxHeight: constraints.maxHeight,
          ),
          parentUsesSize: true,
        );
      }

      if (child == lastChild) {
        // If "more" item's width is below threshold, hide left item.
        if (child.size.width <= minMoreItemsWidth) {
          childIdx--;
          RenderBox nthChild = getChildrenAsList()[childIdx];

          // Hide left item of "more" item.
          totalWidth -= nthChild.size.width;
          childOffset -= Offset(nthChild.size.width, 0);
          nthChild.layout(
            BoxConstraints(
              minWidth: 0,
              maxWidth: 0,
              maxHeight: constraints.maxHeight,
            ),
            parentUsesSize: true,
          );

          // Resize "more" item.
          child.layout(
            BoxConstraints(
              minWidth: constraints.maxWidth - totalWidth,
              maxWidth: constraints.maxWidth - totalWidth,
              maxHeight: constraints.maxHeight,
            ),
            parentUsesSize: true,
          );
        }

        // Update the start index of children to be displayed
        // in "more" items.
        onCollapseIndex(childIdx);
      }

      totalWidth += child.size.width;
      totalHeight = max(totalHeight, child.size.height);

      childParentData.offset = Offset(childOffset.dx, 0);
      childOffset += Offset(child.size.width, 0);

      if (child != lastChild) {
        childIdx++;
      }
      child = childParentData.nextSibling;
    }

    // If all children is displayed except for "more" item.
    if (childIdx == childCount - 1) {
      // Set the layout of popup button to size 0.
      lastChild!.layout(BoxConstraints(
        minWidth: 0,
        maxWidth: 0,
        maxHeight: constraints.maxHeight,
      ));
    }

    size = Size(totalWidth, totalHeight);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    return defaultHitTestChildren(result, position: position);
  }
}

用法:

class HomePage extends StatelessWidget {
  final List<String> _data = const <String>[
    'Falkenberg',
    'Braga',
    'Stockholm',
    'Trnnava',
    'Plodiv',
    'Klaipeda',
    'Punta Cana',
    'Lisbon',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(20),
            child: MenuBar(
              onItemPressed: (int i) {},
              children: _data
                  .map((String data) => MenuBarItem(
                        onPressed: () => print(data),
                        text: data,
                      ))
                  .toList(),
            ),
          ),
        ],
      ),
    );
  }
}

尝试观看此视频以了解更多它是如何工作的。


推荐阅读