Last active
June 7, 2024 04:29
-
-
Save ltvu93/81c6a62b939ab739d8b2fd14f80de176 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ui'; | |
import 'package:flutter/gestures.dart'; | |
import 'package:flutter/material.dart'; | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
const text = | |
'Flutter is an open-source UI software development kit created by Google. It is used to develop cross platform applications from a single codebase for any web browser, Fuchsia, Android, iOS, Linux, macOS, and Windows. First described in 2015, Flutter was released in May 2017.'; | |
return MaterialApp( | |
home: Scaffold( | |
body: SafeArea( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Text( | |
text, | |
// maxLines: 2, | |
), | |
SizedBox(height: 20), | |
ReadMoreText( | |
content: text, | |
maxLines: 2, | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
} | |
class ReadMoreText extends StatefulWidget { | |
final TextStyle? style; | |
final String content; | |
final String showMoreText; | |
final String showLessText; | |
final int maxLines; | |
const ReadMoreText({ | |
super.key, | |
this.style, | |
required this.content, | |
this.showMoreText = 'show more', | |
this.showLessText = 'show less', | |
this.maxLines = 2, | |
}); | |
@override | |
State<ReadMoreText> createState() => _ReadMoreTextState(); | |
} | |
const String _kEllipsis = '\u2026'; | |
class _ReadMoreTextState extends State<ReadMoreText> { | |
bool _isExpanded = false; | |
TextStyle get style { | |
final defaultTextStyle = DefaultTextStyle.of(context); | |
TextStyle effectiveTextStyle = defaultTextStyle.style; | |
if (widget.style == null || widget.style!.inherit) { | |
effectiveTextStyle = defaultTextStyle.style.merge(widget.style); | |
} | |
return effectiveTextStyle; | |
} | |
TextDirection get textDirection => Directionality.of(context); | |
TextSpan get contentTextSpan => TextSpan( | |
text: widget.content, | |
style: style, | |
); | |
TextSpan get ellipsisTextSpan => TextSpan( | |
text: _kEllipsis, | |
style: style, | |
); | |
TextSpan get spaceTextSpan => TextSpan( | |
text: ' ', | |
style: style, | |
); | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder( | |
builder: (context, constraints) { | |
final width = constraints.maxWidth; | |
final contentTextPainter = TextPainter( | |
text: contentTextSpan, | |
textDirection: textDirection, | |
); | |
contentTextPainter.layout(maxWidth: width); | |
final contentLines = contentTextPainter.computeLineMetrics(); | |
final spaceTextPainter = TextPainter( | |
text: spaceTextSpan, | |
textDirection: textDirection, | |
); | |
spaceTextPainter.layout(maxWidth: width); | |
final spaceWidth = spaceTextPainter.width; | |
if (_isExpanded) { | |
return _buildShowLessContent(width, contentLines, spaceWidth); | |
} | |
return _buildShowMoreContent(width, contentLines, spaceWidth, contentTextPainter); | |
}, | |
); | |
} | |
Widget _buildShowLessContent(double width, List<LineMetrics> contentLines, double spaceWidth) { | |
final showLessTextSpan = TextSpan( | |
text: widget.showLessText, | |
style: style.copyWith(color: Colors.pinkAccent), | |
recognizer: TapGestureRecognizer() | |
..onTap = () { | |
setState(() { | |
_isExpanded = false; | |
}); | |
}, | |
); | |
final showLessTextPainter = TextPainter( | |
text: showLessTextSpan, | |
textDirection: textDirection, | |
); | |
showLessTextPainter.layout(maxWidth: width); | |
final showLessWidth = showLessTextPainter.width; | |
final lastLine = contentLines.last; | |
final isShowLessFitOnLastLine = lastLine.width + spaceWidth + showLessWidth <= width; | |
return Text.rich( | |
TextSpan( | |
children: [ | |
TextSpan( | |
text: widget.content, | |
), | |
isShowLessFitOnLastLine ? spaceTextSpan : TextSpan(text: '\n'), | |
showLessTextSpan, | |
], | |
style: style, | |
), | |
); | |
} | |
Widget _buildShowMoreContent( | |
double width, List<LineMetrics> contentLines, double spaceWidth, TextPainter contentTextPainter) { | |
final showMoreTextSpan = TextSpan( | |
text: widget.showMoreText, | |
style: style.copyWith(color: Colors.pinkAccent), | |
recognizer: TapGestureRecognizer() | |
..onTap = () { | |
setState(() { | |
_isExpanded = true; | |
}); | |
}, | |
); | |
final isExceedMaxLine = contentLines.length > widget.maxLines; | |
String content = widget.content; | |
if (isExceedMaxLine) { | |
final ellipsisTextPainter = TextPainter( | |
text: ellipsisTextSpan, | |
textDirection: textDirection, | |
); | |
ellipsisTextPainter.layout(maxWidth: width); | |
final ellipsisWidth = ellipsisTextPainter.width; | |
final spaceTextPainter = TextPainter( | |
text: spaceTextSpan, | |
textDirection: textDirection, | |
); | |
spaceTextPainter.layout(maxWidth: width); | |
final showMoreTextPainter = TextPainter( | |
text: showMoreTextSpan, | |
textDirection: textDirection, | |
); | |
showMoreTextPainter.layout(maxWidth: width); | |
final showMoreWidth = showMoreTextPainter.width; | |
final collapseLastLine = contentLines[widget.maxLines]; | |
final collapseLastLineLeft = width - (ellipsisWidth + spaceWidth + showMoreWidth); | |
final collapseLastLineTop = collapseLastLine.baseline - collapseLastLine.ascent; | |
final collapseLastLineEndPos = | |
contentTextPainter.getPositionForOffset(Offset(collapseLastLineLeft, collapseLastLineTop)); | |
final collapseTextEndIndex = contentTextPainter.getOffsetBefore(collapseLastLineEndPos.offset) ?? 0; | |
final collapseText = widget.content.substring(0, collapseTextEndIndex); | |
content = collapseText.trimRight(); | |
} | |
return Text.rich( | |
TextSpan( | |
children: [ | |
TextSpan( | |
text: content, | |
), | |
if (isExceedMaxLine) ...[ | |
ellipsisTextSpan, | |
spaceTextSpan, | |
showMoreTextSpan, | |
] | |
], | |
), | |
style: style, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment