如何为多行TextField每行添加匹配尺寸的Container背景?
实现多行TextField每行匹配尺寸的背景Container
核心思路
要实现每行背景与TextField行尺寸完全匹配,关键是获取TextField文本的每行布局信息——借助TextPainter模拟TextField的文本排版,计算出每行的位置、宽高,再结合Stack将背景Container与TextField叠加,背景层在下方,TextField设为透明以便透出背景。
具体实现代码
import 'package:flutter/material.dart'; class TextFieldLineBackground extends StatefulWidget { const TextFieldLineBackground({super.key}); @override State<TextFieldLineBackground> createState() => _TextFieldLineBackgroundState(); } class _TextFieldLineBackgroundState extends State<TextFieldLineBackground> { late TextEditingController _controller; final _textStyle = const TextStyle(fontSize: 16, color: Colors.black); double _textFieldWidth = 0; List<LineInfo> _lineInfos = []; @override void initState() { super.initState(); _controller = TextEditingController()..addListener(_calculateLineInfos); } @override void dispose() { _controller.dispose(); super.dispose(); } // 计算每行的位置和文本内容 void _calculateLineInfos() { final text = _controller.text; if (text.isEmpty || _textFieldWidth == 0) { setState(() => _lineInfos = []); return; } final textPainter = TextPainter( text: TextSpan(text: text, style: _textStyle), textDirection: TextDirection.ltr, maxLines: null, )..layout(maxWidth: _textFieldWidth); final lineMetrics = textPainter.computeLineMetrics(); final lineInfos = <LineInfo>[]; for (var i = 0; i < lineMetrics.length; i++) { final line = lineMetrics[i]; // 获取该行文本的起始和结束偏移量 final startOffset = textPainter.getPositionForOffset(Offset(line.left, line.baseline)).offset; final endOffset = i == lineMetrics.length - 1 ? text.length : textPainter.getPositionForOffset(Offset(lineMetrics[i+1].left, lineMetrics[i+1].baseline)).offset; final lineText = text.substring(startOffset, endOffset); lineInfos.add(LineInfo( rect: Rect.fromLTWH( line.left, line.baseline - line.ascent, line.width, line.height, ), text: lineText, )); } setState(() => _lineInfos = lineInfos); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { // 监听TextField的可用宽度变化,重新计算行布局 if (_textFieldWidth != constraints.maxWidth) { _textFieldWidth = constraints.maxWidth - 16; // 减去TextField的内边距 _calculateLineInfos(); } return Stack( children: [ // 每行的背景Container ..._lineInfos.map((info) { // 根据每行文本动态设置背景样式 Color bgColor = Colors.yellow.withOpacity(0.3); if (info.text.contains('重要')) { bgColor = Colors.red.withOpacity(0.3); } else if (info.text.startsWith('-')) { bgColor = Colors.green.withOpacity(0.3); } return Positioned.fromRect( rect: info.rect, child: Container( decoration: BoxDecoration( color: bgColor, border: Border.all(color: Colors.grey.shade300, width: 0.5), ), ), ); }), // 上层透明背景的TextField TextField( controller: _controller, maxLines: null, style: _textStyle, decoration: const InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.all(8), fillColor: Colors.transparent, filled: true, ), ), ], ); }, ); } } // 存储每行的位置和文本信息 class LineInfo { final Rect rect; final String text; LineInfo({required this.rect, required this.text}); }
关键说明
- TextPainter的作用:完全复用TextField的文本样式和布局宽度,精准计算每行的
Rect(位置、宽高),确保背景Container与文本行完美对齐。 - 动态样式设置:通过
LineInfo存储每行文本,可根据文本内容(如包含特定关键词、前缀)自定义背景颜色、边框等样式。 - 适配布局变化:用
LayoutBuilder监听TextField的宽度变化,确保窗口缩放或父容器尺寸改变时,背景行能实时更新位置和尺寸。
内容的提问来源于stack exchange,提问作者Mohammad Hosseini
相关产品推荐
相关产品推荐

