如何实现文本环绕Icon Widget布局?优先无硬编码自定义方案
实现文本环绕Icon的动态布局方案
要实现文本环绕Icon,且文本高度超过Icon时从第四行开始换行到Icon下方的需求,这里提供一个无硬编码的自定义布局方案,核心思路是通过TextPainter预计算文本行数和内容,动态拆分文本并组合布局:
核心实现代码
class WrapTextAroundIcon extends StatelessWidget { final String text; final Widget icon; final TextStyle? textStyle; final int wrapAfterLines; // 前N行环绕,这里设置为3,对应第四行开始换行 const WrapTextAroundIcon({ super.key, required this.text, required this.icon, this.textStyle, this.wrapAfterLines = 3, }); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { return FutureBuilder<LayoutMetrics>( future: _calculateLayoutMetrics(constraints.maxWidth), builder: (context, snapshot) { if (!snapshot.hasData) return const SizedBox.shrink(); final metrics = snapshot.data!; final totalLines = metrics.totalLines; final iconWidth = metrics.iconWidth; final iconHeight = metrics.iconHeight; // 拆分文本:前3行保留在Icon右侧,剩余文本移到Icon下方 final wrapText = _getTextForLines(metrics.textPainter, wrapAfterLines); final remainingText = text.substring(wrapText.length); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: iconWidth, height: iconHeight, child: icon), const SizedBox(width: 8), // 图标和文本的间距 Expanded( child: Text(wrapText, style: textStyle), ), ], ), // 只有当总行数超过3时才显示剩余文本 if (totalLines > wrapAfterLines) Padding( padding: const EdgeInsets.only(top: 8), child: Text(remainingText, style: textStyle), ), ], ); }, ); }, ); } // 计算图标尺寸和文本布局信息 Future<LayoutMetrics> _calculateLayoutMetrics(double maxWidth) async { final iconRenderBox = await _getWidgetSize(icon); final iconWidth = iconRenderBox.width; final iconHeight = iconRenderBox.height; // 文本可用宽度 = 总宽度 - 图标宽度 - 间距 final textAvailableWidth = maxWidth - iconWidth - 8; final textPainter = TextPainter( text: TextSpan(text: text, style: textStyle), textDirection: TextDirection.ltr, maxLines: null, )..layout(maxWidth: textAvailableWidth); return LayoutMetrics( iconWidth: iconWidth, iconHeight: iconHeight, textPainter: textPainter, totalLines: textPainter.computeLineMetrics().length, ); } // 获取Widget的实际尺寸 Future<Size> _getWidgetSize(Widget widget) async { final renderObject = widget.createRenderObject(WidgetsBinding.instance.context); renderObject.layout(const BoxConstraints()); return renderObject.size; } // 根据行数截取文本内容 String _getTextForLines(TextPainter painter, int lineCount) { final lineMetrics = painter.computeLineMetrics(); if (lineMetrics.length <= lineCount) return text; final endOffset = lineMetrics[lineCount - 1].end; return text.substring(0, endOffset); } } class LayoutMetrics { final double iconWidth; final double iconHeight; final TextPainter textPainter; final int totalLines; LayoutMetrics({ required this.iconWidth, required this.iconHeight, required this.textPainter, required this.totalLines, }); }
使用示例
WrapTextAroundIcon( text: "这是一段测试文本,当文本内容足够长,超过3行之后,第四行及以后的内容就会自动换行到图标的下方区域显示,完全不需要硬编码高度或行数,适配不同屏幕尺寸和文本长度。", icon: const Icon(Icons.info_outline, size: 48), textStyle: const TextStyle(fontSize: 16), )
方案优势
- 完全无硬编码:通过动态计算图标尺寸、文本行数和内容,适配不同屏幕宽度和文本长度
- 自动适配:文本行数不足3行时,正常显示在Icon右侧;超过3行时自动拆分布局
- 灵活可调:通过
wrapAfterLines参数可以自由调整从第几行开始换行到Icon下方
内容的提问来源于stack exchange,提问作者Shanu
相关产品推荐
相关产品推荐

