Flutter中多行TextField的自定义背景实现需求
Flutter 实现带独立行背景与行间距的多行 TextField
核心思路
原生TextField无法直接给每行设置独立背景,采用Stack堆叠方案实现需求:
- 上层是透明样式的
TextField,负责处理输入交互 - 下层根据输入内容的每行位置,绘制带圆角的背景块,同时控制行间距
完整实现代码
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class LineBackgroundTextField extends StatefulWidget { final String hintText; final TextStyle? textStyle; final Color lineBackgroundColor; final double lineSpacing; final double borderRadius; const LineBackgroundTextField({ super.key, required this.hintText, this.textStyle, this.lineBackgroundColor = const Color(0xFFE8F3FF), this.lineSpacing = 8.0, this.borderRadius = 8.0, }); @override State<LineBackgroundTextField> createState() => _LineBackgroundTextFieldState(); } class _LineBackgroundTextFieldState extends State<LineBackgroundTextField> { final TextEditingController _controller = TextEditingController(); final FocusNode _focusNode = FocusNode(); List<Rect> _lineRects = []; @override void initState() { super.initState(); _controller.addListener(_updateLineRects); _focusNode.addListener(_updateLineRects); } @override void dispose() { _controller.dispose(); _focusNode.dispose(); super.dispose(); } void _updateLineRects() { WidgetsBinding.instance.addPostFrameCallback((_) { final renderObject = context.findRenderObject() as RenderBox?; if (renderObject == null) return; final textPainter = TextPainter( text: TextSpan( text: _controller.text.isEmpty ? widget.hintText : _controller.text, style: widget.textStyle ?? const TextStyle(fontSize: 16, color: Colors.black), ), textDirection: TextDirection.ltr, maxLines: null, )..layout(maxWidth: renderObject.size.width); final lineMetrics = textPainter.computeLineMetrics(); setState(() { _lineRects = lineMetrics.map((line) { return Rect.fromLTWH( 0, line.baseline - line.ascent + (line == lineMetrics.first ? 0 : widget.lineSpacing), line.width, line.height, ); }).toList(); }); }); } @override Widget build(BuildContext context) { return Stack( children: [ // 绘制每行背景 if (_lineRects.isNotEmpty) Positioned.fill( child: CustomPaint( painter: LineBackgroundPainter( lineRects: _lineRects, color: widget.lineBackgroundColor, borderRadius: widget.borderRadius, ), ), ), // 透明输入框 TextField( controller: _controller, focusNode: _focusNode, maxLines: null, expands: false, style: widget.textStyle ?? const TextStyle(fontSize: 16, color: Colors.black), decoration: InputDecoration( hintText: widget.hintText, hintStyle: widget.textStyle?.copyWith(color: Colors.grey) ?? const TextStyle(fontSize: 16, color: Colors.grey), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: widget.lineSpacing / 2, horizontal: 8), ), inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'^\n')), // 禁止首行输入换行 ], ), ], ); } } // 自定义背景绘制器 class LineBackgroundPainter extends CustomPainter { final List<Rect> lineRects; final Color color; final double borderRadius; LineBackgroundPainter({ required this.lineRects, required this.color, required this.borderRadius, }); @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = color; for (final rect in lineRects) { canvas.drawRRect( RRect.fromRectAndRadius(rect, Radius.circular(borderRadius)), paint, ); } } @override bool shouldRepaint(covariant LineBackgroundPainter oldDelegate) { return oldDelegate.lineRects != lineRects || oldDelegate.color != color || oldDelegate.borderRadius != borderRadius; } }
使用示例
在页面中直接调用自定义组件:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("行背景输入框")), body: Padding( padding: const EdgeInsets.all(16.0), child: LineBackgroundTextField( hintText: "输入内容...", textStyle: const TextStyle(fontSize: 18, color: Colors.black87), lineBackgroundColor: const Color(0xFFE8F3FF), lineSpacing: 10.0, borderRadius: 12.0, ), ), ); } }
关键说明
- 通过
TextPainter计算文本的每行位置和尺寸,实现背景块与文本行精准对齐 - 利用
Stack分离背景绘制层与输入层,既保证输入交互正常,又实现自定义背景效果 - 支持自定义背景色、行间距、圆角半径和文本样式,适配不同场景需求
内容的提问来源于stack exchange,提问作者Mohammad Hosseini
相关产品推荐
相关产品推荐

