Sunday, July 14, 2019

Flutter - Text widget with read more or expand feature.

In the mobile applications, sometimes we have to create an item that shows only two or three lines of the complete paragraph. To create such kind of view, we have a to add a button that displays complete content. When the user clicks on that button.

flutter read more textview widgetThe read more or expandable feature in a Text and other widgets will help you to show large sentences or paragraphs.  It shows complete paragraphs when a user clicks on read more button. We can display these feature at the end of sentences, as you can see below. The Flutter framework provides a Text widget to display content. But it's not providing any expandable feature to display text.

In this post, we going to share a read more text widget for Flutter. You will learn, how to implement that text widget in Flutter application to show read more option in the text widget. Here, we have created an example that's final output will look like below: 


flutter read more textview widget example


Creating a new Project
1. Create a new project from File ⇒ New Flutter Project with your development IDE.
2. After that open main.dart file and edit it. As you can see, we have created a tree of ReadMoreText widgets to display read more content.

main.dart
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/read_more_text.dart'; void main() => runApp(MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFF02BB9F), ), title: 'Read More Text', home: DemoApp(), )); class DemoApp extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( 'Read More Text', style: TextStyle(color: Colors.white), )), body: DefaultTextStyle.merge( style: const TextStyle( fontSize: 16.0, //fontFamily: 'monospace', ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: const EdgeInsets.all(16.0), child: ReadMoreText( 'Flutter is Google’s mobile UI open source framework to build high-quality native (super fast) interfaces for iOS and Android apps with the unified codebase.', trimLines: 2, colorClickableText: Colors.pink, trimMode: TrimMode.Line, trimCollapsedText: '...Show more', trimExpandedText: ' show less', ), ), Divider( color: const Color(0xFF167F67), ), Padding( padding: const EdgeInsets.all(16.0), child: ReadMoreText( 'Flutter has its own UI components, along with an engine to render them on both the Android and iOS platforms. Most of those UI components, right out of the box, conform to the guidelines of Material Design.', trimLines: 3, colorClickableText: Colors.pink, trimMode: TrimMode.Line, trimCollapsedText: '...Expand', trimExpandedText: ' Collapse ', ), ), Divider( color: const Color(0xFF167F67), ), Padding( padding: const EdgeInsets.all(16.0), child: ReadMoreText( 'The Flutter framework builds its layout via the composition of widgets, everything that you construct programmatically is a widget and these are compiled together to create the user interface. ', trimLines: 2, colorClickableText: Colors.pink, trimMode: TrimMode.Line, trimCollapsedText: '...Read more', trimExpandedText: ' Less', ), ), ], ), ), ), ); } }

3.  Here, we have read_more_text.dart widget that you have to add in your code base. If you going to use it.  

read_more_text.dart
import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; enum TrimMode { Length, Line, } class ReadMoreText extends StatefulWidget { const ReadMoreText( this.data, { Key key, this.trimExpandedText = ' read less', this.trimCollapsedText = ' ...read more', this.colorClickableText, this.trimLength = 240, this.trimLines = 2, this.trimMode = TrimMode.Length, this.style, this.textAlign, this.textDirection, this.locale, this.textScaleFactor, this.semanticsLabel, }) : assert(data != null), super(key: key); final String data; final String trimExpandedText; final String trimCollapsedText; final Color colorClickableText; final int trimLength; final int trimLines; final TrimMode trimMode; final TextStyle style; final TextAlign textAlign; final TextDirection textDirection; final Locale locale; final double textScaleFactor; final String semanticsLabel; @override ReadMoreTextState createState() => ReadMoreTextState(); } const String _kEllipsis = '\u2026'; const String _kLineSeparator = '\u2028'; class ReadMoreTextState extends State<ReadMoreText> { bool _readMore = true; void _onTapLink() { setState(() => _readMore = !_readMore); } @override Widget build(BuildContext context) { final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); TextStyle effectiveTextStyle = widget.style; if (widget.style == null || widget.style.inherit) { effectiveTextStyle = defaultTextStyle.style.merge(widget.style); } final textAlign = widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start; final textDirection = widget.textDirection ?? Directionality.of(context); final textScaleFactor = widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context); final overflow = defaultTextStyle.overflow; final locale = widget.locale ?? Localizations.localeOf(context, nullOk: true); final colorClickableText = widget.colorClickableText ?? Theme.of(context).accentColor; TextSpan link = TextSpan( text: _readMore ? widget.trimCollapsedText : widget.trimExpandedText, style: effectiveTextStyle.copyWith( 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( style: effectiveTextStyle, text: widget.data, ); // Layout and measure link TextPainter textPainter = TextPainter( text: link, textAlign: textAlign, textDirection: textDirection, textScaleFactor: textScaleFactor, maxLines: widget.trimLines, ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, locale: locale, ); 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; print('linkSize $linkSize textSize $textSize'); // Get the endIndex of data bool linkLongerThanLine = false; int endIndex; if (linkSize.width < maxWidth) { final pos = textPainter.getPositionForOffset(Offset( textSize.width - linkSize.width, textSize.height, )); endIndex = textPainter.getOffsetBefore(pos.offset); } else { var pos = textPainter.getPositionForOffset( textSize.bottomLeft(Offset.zero), ); endIndex = pos.offset; linkLongerThanLine = true; } var textSpan; switch (widget.trimMode) { case TrimMode.Length: if (widget.trimLength < widget.data.length) { textSpan = TextSpan( style: effectiveTextStyle, text: _readMore ? widget.data.substring(0, widget.trimLength) : widget.data, children: <TextSpan>[link], ); } else { textSpan = TextSpan( style: effectiveTextStyle, text: widget.data, ); } break; case TrimMode.Line: if (textPainter.didExceedMaxLines) { textSpan = TextSpan( style: effectiveTextStyle, text: _readMore ? widget.data.substring(0, endIndex) + (linkLongerThanLine ? _kLineSeparator : '') : widget.data, children: <TextSpan>[link], ); } else { textSpan = TextSpan( style: effectiveTextStyle, text: widget.data, ); } break; default: throw Exception( 'TrimMode type: ${widget.trimMode} is not supported'); } return RichText( textAlign: textAlign, textDirection: textDirection, softWrap: true, //softWrap, overflow: TextOverflow.clip, //overflow, textScaleFactor: textScaleFactor, text: textSpan, ); }, ); if (widget.semanticsLabel != null) { result = Semantics( textDirection: widget.textDirection, label: widget.semanticsLabel, child: ExcludeSemantics( child: result, ), ); } return result; } }

If you have followed the article carefully, you can see the app running very smoothly as shown above. But if you are facing any problem, please feel free to ask from comments.
Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...