【问题标题】:Progressive Menu in Flutter (Hide Overflowing Elements in Dropdown)Flutter 中的渐进式菜单(隐藏下拉菜单中的溢出元素)
【发布时间】:2021-07-27 01:37:23
【问题描述】:

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

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

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

可用宽度较小(隐藏下拉菜单中的项目):

提前致谢!

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

【问题讨论】:

    标签: flutter user-interface dart flutter-layout


    【解决方案1】:

    也许您可以尝试创建一个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(),
                ),
              ),
            ],
          ),
        );
      }
    }
    

    尝试观看此video 以了解其工作原理。

    【讨论】:

    • 华丽的答案!正是我想要的。我让它工作,但现在会更深入地调试它,因为它会向我抛出一些警告。也许如果我有时间,我会从中创建一个可重用的包。我已经修复了一个错误:将 if (child.size.width &lt;= minMoreItemsWidth) 更改为 if (child.size.width &lt;= minMoreItemsWidth &amp;&amp; childIdx &gt; 0)
    • 23 小时后我会给你一个赏金(如果我忘了提醒我)
    • 不错!这个问题让我学习了RenderObject,所以也谢谢 :D 有这个包会很好。祝你好运!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 2019-11-28
    相关资源
    最近更新 更多