flutter - Flutter 中的渐进式菜单(隐藏下拉菜单中的溢出元素)
问题描述
我正在构建一个可调整大小的 Flutter 桌面应用程序,我想知道如何自动隐藏菜单中溢出的项目(例如 a Row
)并使它们在“更多”下拉列表中可见。
概念样本图片(来源:https ://css-tricks.com/container-adapting-tabs-with-more-button/ )
提前致谢!
更新:我有使用Wrap
-element 的想法,但后来我遇到了以下问题:
- 如何限制
Wrap
只显示一行孩子?(与https://github.com/flutter/flutter/issues/65331相关) - 如何获取有关哪些元素在第 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(),
),
),
],
),
);
}
}
尝试观看此视频以了解更多它是如何工作的。
推荐阅读
- java - 整数数组索引
- excel - 如何将可重复使用的多行/多列表从一个 Excel 工作表复制到另一个工作表上的数据库
- java - Android Webview:只显示网站的内容
- arrays - Swift 2 字符串数组删除特定索引错误
- server - Redis 简单生产服务器规范
- javascript - Google 应用程序脚本 UrlFetchApp 错误“访问未授予或已过期”
- python - Mypy 不使用 Type[NamedTuple] 进行类型检查功能
- sql - SQL 合并不返回任何行
- sql-server - 在 SSIS 包中面临登录超时问题
- javascript - .babelrc 配置放在 package.JSON