import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; class ReadMoreText extends StatefulWidget { const ReadMoreText( this.text, { Key? key, this.trimLines = 2, this.collapsedText = '... read more', this.expandedText = ' read less', this.textStyle, }) : assert(text != null), super(key: key); final String text; final int trimLines; final String collapsedText; final String expandedText; final TextStyle? textStyle; @override ReadMoreTextState createState() => ReadMoreTextState(); } class ReadMoreTextState extends State { bool _readMore = true; void _onTapLink() { setState(() => _readMore = !_readMore); } @override Widget build(BuildContext context) { final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); final colorClickableText = Colors.blue; final widgetColor = Colors.black; TextSpan link = TextSpan( text: _readMore ? widget.collapsedText : widget.expandedText, style: TextStyle( color: colorClickableText, ), recognizer: TapGestureRecognizer()..onTap = _onTapLink ); Widget result = LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { assert(constraints.hasBoundedWidth); final double maxWidth = constraints.maxWidth; // Create a TextSpan with data final text = TextSpan( text: widget.text, ); // Layout and measure link TextPainter textPainter = TextPainter( text: link, textDirection: TextDirection.rtl,//better to pass this from master widget if ltr and rtl both supported maxLines: widget.trimLines, ellipsis: '...', ); textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth); final linkSize = textPainter.size; // Layout and measure text textPainter.text = text; textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth); final textSize = textPainter.size; // Get the endIndex of data int? endIndex; final pos = textPainter.getPositionForOffset(Offset( textSize.width - linkSize.width, textSize.height, )); endIndex = textPainter.getOffsetBefore(pos.offset); var textSpan; if (textPainter.didExceedMaxLines) { textSpan = TextSpan( text: _readMore ? widget.text.substring(0, endIndex) : widget.text, style: widget.textStyle ?? TextStyle( color: widgetColor, ), children: [link], ); } else { textSpan = TextSpan( text: widget.text, style: widget.textStyle ?? TextStyle( color: widgetColor, ), ); } return RichText( softWrap: true, overflow: TextOverflow.clip, text: textSpan, ); }, ); return result; } }