Flutter
在移动开发中,常常需要处理一些长文本显示的场景,如何优雅地展示这些文本并允许用户展开和收起是一个常见的需求。在本文中,我将分享如何使用Flutter实现一个可展开和收起的文本控件。
效果
我们将实现一个可展开和收起的文本控件。当文本超过指定的最大行数时,会显示省略号和“展开”按钮。点击“展开”按钮后,文本会全部显示,并且按钮变成“收起”,点击“收起”按钮后,文本会恢复到初始的折叠状态。
需求
- 文本内容可以动态展开和收起。
- 当文本内容超过指定的最大行数时,显示“展开”按钮。
- 当文本内容全部显示时,显示“收起”按钮。
- 具有自定义文本样式的能力。
实现思路
- 使用
LayoutBuilder
来获取文本控件的最大宽度。 - 使用
TextPainter
来计算文本的高度和是否超过最大行数。 - 通过判断文本是否超出最大行数来决定显示“展开”或“收起”按钮。
- 使用
RichText
和TextSpan
来动态构建可点击的“展开”和“收起”按钮。
实现代码
以下是实现可展开文本控件的完整代码:
import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Expandable Text View'), ), body: const Padding( padding: EdgeInsets.all(16.0), child: ExpandableTextView( text: '我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据', maxLines: 2, ), ), ), ); } } class ExpandableText extends StatefulWidget { final String text; final int maxLines; final TextStyle? textStyle; const ExpandableText({ Key? key, required this.text, required this.maxLines, this.textStyle, }) : super(key: key); @override ExpandableTextState createState() => ExpandableTextState(); } class ExpandableTextState extends State<ExpandableText> { bool isExpanded = false; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final maxWidth = constraints.maxWidth; final textSpan = TextSpan( text: widget.text, style: widget.textStyle ?? const TextStyle(color: Colors.black), ); final textPainter = TextPainter( text: textSpan, maxLines: isExpanded ? null : widget.maxLines, textDirection: TextDirection.ltr, ); textPainter.layout(maxWidth: maxWidth); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ isExpanded ? _buildExpandedText() : _buildCollapsedText(textPainter, maxWidth), ], ); }, ); } Widget _buildCollapsedText(TextPainter textPainter, double maxWidth) { final expandSpan = TextSpan( text: " 展开", style: const TextStyle(color: Colors.blue), recognizer: TapGestureRecognizer() ..onTap = () { setState(() { isExpanded = !isExpanded; }); }, ); final linkTextSpan = TextSpan( text: '...', style: widget.textStyle ?? const TextStyle(color: Colors.black), children: [expandSpan], ); final linkPainter = TextPainter( text: linkTextSpan, textDirection: TextDirection.ltr, ); linkPainter.layout(maxWidth: maxWidth); final position = textPainter.getPositionForOffset( Offset(maxWidth - linkPainter.width, textPainter.height)); final endOffset = textPainter.getOffsetBefore(position.offset) ?? position.offset; final truncatedText = widget.text.substring(0, endOffset); return RichText( text: TextSpan( text: truncatedText, style: widget.textStyle ?? const TextStyle(color: Colors.black), children: [linkTextSpan], ), maxLines: widget.maxLines, overflow: TextOverflow.ellipsis, ); } Widget _buildExpandedText() { final collapseSpan = TextSpan( text: " 收起", style: const TextStyle(color: Colors.blue), recognizer: TapGestureRecognizer() ..onTap = () { setState(() { isExpanded = !isExpanded; }); }, ); return RichText( text: TextSpan( text: widget.text, style: widget.textStyle ?? const TextStyle(color: Colors.black), children: [collapseSpan], ), ); } }
代码解析
- ExpandableText Widget: 自定义的文本控件,接收文本内容和最大行数作为输入参数。
- isExpanded: 控制文本是否展开的状态变量。
- LayoutBuilder: 用于获取父容器的最大宽度,以便于后续的文本布局计算。
- TextPainter: 用于计算文本的高度和是否超过最大行数。
- RichText: 用于显示带有点击事件的文本(“展开”和“收起”)。
总结
通过以上实现,我们可以轻松地在Flutter应用中使用可展开和收起的文本控件,提升用户体验。这种实现方式不仅简洁高效,还具备良好的可维护性和扩展性。如果你有更复杂的需求,可以在此基础上进行进一步的定制和优化。
详情见:github.com/yixiaolunhui/flutter_xy