You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中flutter_html组件如何检测文本是否超出最大行数?

问题:如何检测flutter_html文本是否超出3行以显示“查看更多”按钮?

我在实现Notification List Item组件时需要实现:当文本超过3行时显示“查看更多”按钮。但我没有使用普通Text组件,而是采用flutter_html组件,并在其全局样式的"*"规则中设置了maxLines为3。当前组件代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:vecinoportal/component/preloader/multiline_preloader_box.dart';
import 'package:vecinoportal/component/preloader/preloader_box.dart';
import 'package:vecinoportal/config/app_colors.dart';

class NotificationListItem extends StatelessWidget {
  final String? header;
  final String? message;
  final String? unit;
  final String? time;

  const NotificationListItem({
    Key? key,
    this.header,
    this.message,
    this.unit,
    this.time,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // TODO: OPEN NOTIF FUNCTION
        print('notif');
      },
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ((header != null && message != null))
                ? Image.asset(
                    'asset/notification/notification.png',
                    height: 40,
                    width: 40,
                  )
                : const PreloaderBox(
                    height: 40,
                    width: 40,
                  ),
            const SizedBox(width: 8),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  (header != null)
                      ? Text(
                          header!,
                          style: const TextStyle(
                            fontSize: 17,
                            fontWeight: FontWeight.w600,
                          ),
                        )
                      : const PreloaderBox(height: 20, minWidth: 48, maxWidth: 128),
                  const SizedBox(height: 2),
                  Row(
                    children: [
                      (time != null)
                          ? Text(
                              time!,
                              style: const TextStyle(
                                fontSize: 15,
                                color: AppColors.lightGray01,
                              ),
                            )
                          : const PreloaderBox(height: 14, minWidth: 48, maxWidth: 64),
                    ],
                  ),
                  const SizedBox(height: 16),
                  (message != null)
                      ? Html(
                          data: message!,
                          style: {
                            '*': Style(
                              margin: EdgeInsets.zero,
                              fontSize: const FontSize(17),
                              padding: EdgeInsets.zero,
                              maxLines: 3,
                            ),
                            'b': Style(fontWeight: FontWeight.w600),
                            'strong': Style(fontWeight: FontWeight.w600),
                            'ul': Style(
                              padding: EdgeInsets.zero,
                              alignment: Alignment.centerLeft,
                              textAlign: TextAlign.left,
                            ),
                            'li': Style(
                              padding: const EdgeInsets.symmetric(vertical: 2),
                              alignment: Alignment.centerLeft,
                              textAlign: TextAlign.left,
                            ),
                          },
                        )
                      : const MultilinePreloaderBox(
                          lines: 3,
                          height: 14,
                          minWidth: 192,
                          maxWidth: 320,
                        ),
                  // TODO: ADD "READ MORE BUTTON" IF TEXT IS MORE THAN 3 LINES
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

请问如何检测flutter_html中的文本是否超出设定的3行,从而显示“查看更多”按钮?


解决方案

1. 转换为StatefulWidget并添加状态变量

因为需要根据渲染后的文本状态动态更新UI,首先把NotificationListItem从StatelessWidget改为StatefulWidget,添加以下状态变量:

  • _isTextOverflowed:标记文本是否超出3行
  • _isExpanded:标记文本是否处于展开状态(用于切换展开/收起)

2. 计算3行文本的预期高度

根据你设置的字体大小(17)和明确的行高,计算3行文本的预期高度:

final double fontSize = 17;
final double lineHeight = 1.2; // 可根据实际样式调整
final double expectedThreeLineHeight = fontSize * lineHeight * 3;

3. 使用Html组件的onRendered回调检测溢出

flutter_html的Html组件提供了onRendered回调,会在内容渲染完成后返回渲染后的Widget尺寸。我们可以在这个回调中对比实际高度和预期高度,判断是否溢出。

4. 完整修改后的代码

import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:vecinoportal/component/preloader/multiline_preloader_box.dart';
import 'package:vecinoportal/component/preloader/preloader_box.dart';
import 'package:vecinoportal/config/app_colors.dart';

class NotificationListItem extends StatefulWidget {
  final String? header;
  final String? message;
  final String? unit;
  final String? time;

  const NotificationListItem({
    Key? key,
    this.header,
    this.message,
    this.unit,
    this.time,
  }) : super(key: key);

  @override
  State<NotificationListItem> createState() => _NotificationListItemState();
}

class _NotificationListItemState extends State<NotificationListItem> {
  bool _isTextOverflowed = false;
  bool _isExpanded = false;
  final double fontSize = 17;
  final double lineHeight = 1.2;

  @override
  Widget build(BuildContext context) {
    final double expectedThreeLineHeight = fontSize * lineHeight * 3;

    return GestureDetector(
      onTap: () {
        // TODO: OPEN NOTIF FUNCTION
        print('notif');
      },
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ((widget.header != null && widget.message != null))
                ? Image.asset(
                    'asset/notification/notification.png',
                    height: 40,
                    width: 40,
                  )
                : const PreloaderBox(
                    height: 40,
                    width: 40,
                  ),
            const SizedBox(width: 8),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  (widget.header != null)
                      ? Text(
                          widget.header!,
                          style: const TextStyle(
                            fontSize: 17,
                            fontWeight: FontWeight.w600,
                          ),
                        )
                      : const PreloaderBox(height: 20, minWidth: 48, maxWidth: 128),
                  const SizedBox(height: 2),
                  Row(
                    children: [
                      (widget.time != null)
                          ? Text(
                              widget.time!,
                              style: const TextStyle(
                                fontSize: 15,
                                color: AppColors.lightGray01,
                              ),
                            )
                          : const PreloaderBox(height: 14, minWidth: 48, maxWidth: 64),
                    ],
                  ),
                  const SizedBox(height: 16),
                  (widget.message != null)
                      ? Html(
                          data: widget.message!,
                          onRendered: (RenderContext context, Widget widget, Size size) {
                            // 对比实际高度和预期3行高度,判断是否溢出
                            if (size.height > expectedThreeLineHeight) {
                              setState(() {
                                _isTextOverflowed = true;
                              });
                            } else {
                              setState(() {
                                _isTextOverflowed = false;
                              });
                            }
                          },
                          style: {
                            '*': Style(
                              margin: EdgeInsets.zero,
                              fontSize: FontSize(fontSize),
                              padding: EdgeInsets.zero,
                              maxLines: _isExpanded ? null : 3, // 展开时取消行数限制
                              lineHeight: LineHeight(lineHeight), // 明确设置行高确保计算准确
                            ),
                            'b': Style(fontWeight: FontWeight.w600),
                            'strong': Style(fontWeight: FontWeight.w600),
                            'ul': Style(
                              padding: EdgeInsets.zero,
                              alignment: Alignment.centerLeft,
                              textAlign: TextAlign.left,
                            ),
                            'li': Style(
                              padding: const EdgeInsets.symmetric(vertical: 2),
                              alignment: Alignment.centerLeft,
                              textAlign: TextAlign.left,
                            ),
                          },
                        )
                      : const MultilinePreloaderBox(
                          lines: 3,
                          height: 14,
                          minWidth: 192,
                          maxWidth: 320,
                        ),
                  // 条件显示查看更多/收起按钮
                  if (_isTextOverflowed)
                    TextButton(
                      onPressed: () {
                        setState(() {
                          _isExpanded = !_isExpanded;
                        });
                      },
                      style: TextButton.styleFrom(
                        padding: EdgeInsets.zero,
                        minimumSize: Size.zero,
                        tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                      ),
                      child: Text(
                        _isExpanded ? '收起' : '查看更多',
                        style: const TextStyle(
                          color: Colors.blue,
                          fontSize: 15,
                        ),
                      ),
                    ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

关键说明

  • 明确设置lineHeight:确保计算的预期高度和实际渲染高度一致,避免因默认行高差异导致判断错误
  • onRendered回调:在内容渲染完成后获取实际尺寸,对比后更新溢出状态
  • 展开/收起逻辑:点击按钮切换_isExpanded状态,动态设置maxLines为null(展开)或3(收起)

内容的提问来源于stack exchange,提问作者Richard Johnson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:14:56