Flutter 多标签自动换行及展开

最终效果 

思路:

首先还是使用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;
  }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值