最终效果?
思路:
首先还是使用Wrap组件,然后计算item的宽度,当item们的的宽度超过屏幕-展开按钮的时候,隐藏剩余标签。
如何计算宽度?
这里没有采用GlobalKey的方式,是采用渲染完成之后把宽度放在组件内完成的。
用到的是 NotificationListener 用来监听 LayoutChangedNotification
子组件用SizeChangedLayoutNotifier包裹,这样在渲染完成的时候会调用onNotification方法,然后再通过context获取到组件的宽高,付给自身属性,一共组件使用。
但是由于SizeChangedLayoutNotifier的回掉方法是有触发机制的,看下SizeChangedLayoutNotification的源码,只有在初次初始化的时候才会发送通知。
这里直接复制一份,新起一个CustomSizeChangedLayoutNotification类,直接改成
?这里借鉴的是:flutter text内容高度不定问题解决_whs867712232的博客-CSDN博客_flutter text高度
其他的我已经写好了注释,直接上代码了。这里引用了 flutter_screenutil 组件
本文为抛砖引玉,肯定有不太合理的地方,路过的大佬们提提意见。
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_html/shims/dart_ui_real.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui';
import 'package:scan/scan.dart';
class WrapAndExpanded extends StatefulWidget {
const WrapAndExpanded({Key? key}) : super(key: key);
@override
_WrapAndExpandedState createState() => _WrapAndExpandedState();
}
class _WrapAndExpandedState extends State<WrapAndExpanded>
with WidgetsBindingObserver {
// 页面的宽度
double screenWidth = 1.sw;
// 两边的间隔
double screenPadding = 15;
// 原始数据
List<Widget> sourceWidgets = [];
// 显示得数据
List<Widget> showWidgets = [];
double btnWidth = 50;
fetchData() {
List<String> list = [];
list.add("value1value1value1value1value1value1");
list.add("value1value1value1value1value1value1value1value1value1");
list.add("value1value1value1");
list.add("value1value1value1");
list.add("value1");
list.add("value1");
list.add("value1");
list.add("value1");
list.add("value1value1value1");
list.add("value1value1value1");
list.add("value1value1value1");
list.add("value1");
list.add("value1");
// 限制标签的最大宽度,多减10个单位,以防万一。
double maxWidth = 1.sw - (screenPadding * 2) - btnWidth - 10;
sourceWidgets = List.generate(list.length, (index) {
return _CustomChip(
text: list[index],
maxWidth: maxWidth,
);
});
// 数据同步
showWidgets = sourceWidgets;
}
double chipWidth = 0;
@override
void initState() {
fetchData();
super.initState();
// 当页面渲染完成的时候完成调用
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
renderShowData();
});
}
renderShowData() {
// 临时存放要展示的数据
List<Widget> tempList = [];
// 展开按钮的宽度度
// 计算长度
for (var i = 0; i < sourceWidgets.length; i++) {
chipWidth += (sourceWidgets[i] as _CustomChip).width;
// 如果要是大于屏幕的宽度
if (chipWidth >= (screenWidth * 2 - (screenPadding * 2) - btnWidth)) {
tempList.add(Container(
width: btnWidth,
child: GestureDetector(
child: Text("展开"),
onTap: () {
showWidgets = sourceWidgets;
setState(() {});
},
),
));
break;
} else {
tempList.add(sourceWidgets[i]);
}
}
showWidgets = tempList;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: screenPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
children: showWidgets,
),
Text("测试是否有视觉问题")
],
),
);
}
}
class _CustomChip extends StatefulWidget {
final String text;
// 组件宽度
double width = 0;
// 组件高度
double height = 0;
// 水平外边距
double horizontalMargin;
// 水平内边距
double horizontalPadding;
// 垂直外边距
double verticalMargin;
// 垂直内边距
double verticalPadding;
// maxWidth
double maxWidth;
_CustomChip(
{Key? key,
required this.text,
this.horizontalMargin = 2.0,
this.horizontalPadding = 2.0,
this.verticalMargin = 2.0,
this.verticalPadding = 2.0,
this.maxWidth = 100.0})
: super(key: key);
@override
__CustomChipState createState() => __CustomChipState();
}
class __CustomChipState extends State<_CustomChip> {
_printSize() {
if (!mounted) return;
var size = context.findRenderObject()?.paintBounds.size;
if (size != null) {
// 计算宽度的时候加上两边的间距
widget.width = size.width +
(widget.horizontalMargin * 2) +
(widget.horizontalPadding * 2);
// 如果计算高速则加上
widget.height = size.height +
(widget.verticalMargin * 2) +
(widget.verticalPadding * 2);
// print(size.toString());
}
}
@override
Widget build(BuildContext context) {
return NotificationListener<LayoutChangedNotification>(
onNotification: (notification) {
/// 收到布局结束通知,打印尺寸
_printSize();
return false;
},
child: CustomSizeChangedLayoutNotifier(
child: Container(
constraints: BoxConstraints(
// 限制单个标签最长长度
maxWidth: widget.maxWidth),
color: Colors.grey,
margin: EdgeInsets.symmetric(
vertical: widget.verticalMargin,
horizontal: widget.horizontalPadding),
padding: EdgeInsets.symmetric(
vertical: widget.verticalPadding,
horizontal: widget.horizontalMargin),
child: Text(
widget.text,
style: TextStyle(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
);
}
}
class CustomSizeChangedLayoutNotifier extends SingleChildRenderObjectWidget {
/// Creates a [SizeChangedLayoutNotifier] that dispatches layout changed
/// notifications when [child] changes layout size.
const CustomSizeChangedLayoutNotifier({
Key? key,
Widget? child,
}) : super(key: key, child: child);
@override
_RenderSizeChangedWithCallback createRenderObject(BuildContext context) {
return _RenderSizeChangedWithCallback(
onLayoutChangedCallback: () {
SizeChangedLayoutNotification().dispatch(context);
},
);
}
}
class _RenderSizeChangedWithCallback extends RenderProxyBox {
_RenderSizeChangedWithCallback({
RenderBox? child,
required this.onLayoutChangedCallback,
}) : assert(onLayoutChangedCallback != null),
super(child);
// There's a 1:1 relationship between the _RenderSizeChangedWithCallback and
// the `context` that is captured by the closure created by createRenderObject
// above to assign to onLayoutChangedCallback, and thus we know that the
// onLayoutChangedCallback will never change nor need to change.
final VoidCallback onLayoutChangedCallback;
Size? _oldSize;
@override
void performLayout() {
super.performLayout();
// Don't send the initial notification, or this will be SizeObserver all
// over again!
if (size != _oldSize) onLayoutChangedCallback();
_oldSize = size;
}
}
?
|