Reputation: 7629
My Container having a description of movies.
Initially, I want to show only a few lines of description. And below that there should be a link (more...), After Tapping more... all content of description should be get displayed.
For example, check this JQuery plugin.
Upvotes: 57
Views: 98620
Reputation: 199
AnimatedCrossFade(
duration: Duration(milliseconds: 100),
firstChild: Text(
"Short text",
key: ValueKey<int>(0),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
secondChild: Text(
"short text that can be longer",
key: ValueKey<int>(1),
),
crossFadeState: isVisible ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),
SizedBox(
height: 10,
),
Center(
child: TextButton.icon(
onPressed: () {
setState(() {
isVisible = !isVisible;
});
},
icon: Text("View More"),
label: Icon(Icons.keyboard_arrow_down),
)
Upvotes: 1
Reputation: 141
Easily by using a bool:
open
var with default value = false,
Then, create a button to-->
setState(() {
open = !open;
});
After That,
open == true
? Text(
des,//your long text. in hidden mode
style: TextStyle(color: black),
maxLines: 3,//by controlling the no. lines will appear
overflow: TextOverflow.ellipsis,//shape of disappeared txt
)
: Text(
des,//your long text in normal view.
style: TextStyle(color: black),
in hidden
in normal
Upvotes: 0
Reputation: 1
Using maxLines on the text widget gives a clean and precise result. Change the reveal bool value when the user clicks on the text. Hope this helps someone.
class SingleReview extends StatefulWidget {
const SingleReview({
Key? key,
required this.reviewText,
}) : super(key: key);
final String reviewText;
@override
State<SingleReview> createState() => _SingleReviewState();
}
class _SingleReviewState extends State<SingleReview> {
bool reveal = false;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18.0,
vertical: 10,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () {
setState(() {
reveal = !reveal;
});
},
child: Text(
widget.reviewText,
maxLines: reveal ? 2 : null,
overflow: reveal ? TextOverflow.ellipsis : null,
),
),
],
),
);
}
}
Upvotes: 0
Reputation: 11
3rd screenshot When text length less than 197
Code:
bool isExpanded = false;
Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
widget.title,
style: TextStyles.regularStyle.copyWith(
color: const Color(0xFF1A1A1A),
),
maxLines: isExpanded ? 11 : 4,
softWrap: true,
// overflow: TextOverflow.fade,
),
),
const SizedBox(
height: 4,
),
widget.title.length < 197
? SizedBox()
: InkWell(
onTap: () {
setState(() {
isExpanded = !isExpanded;
});
},
child: Align(
alignment: Alignment.topLeft,
child: Text(
isExpanded == false
? 'Показать полностью...'
: 'Show less',
style: TextStyles.mediumStyle.copyWith(
fontSize: 14,
color: const Color(0xff3D3DD8),
),
maxLines: 1,
),
),
),
],
),
Upvotes: -1
Reputation: 747
This an old question, but hope this could help anybody who looks for simple fast solution with simple code: Just check if the text less than (for example) 125 chars, then show the whole text, if not show the first 125 chars then use '... show more' as textspan where when you click on it show the whole text and change 'show more' with 'show less'
I am using here getx for state mang, if you are not using it just use statefull screen with setstate to change its value
final RxBool showLongDes = false.obs;
final String shortDescription = 'This is great product ...'
...
shortDescription.length <= 125
? Text(shortDescription)
:Obx(() {
return RichText(
text: TextSpan(
text: showLongDes.value
?shortDescription
: shortDescription.substring(0, 125) + ' ... '
children: [
TextSpan(
text: showLongDes.value
? ' ' + 'Show less'
: 'Show more',
recognizer: TapGestureRecognizer()
..onTap = () => showLongDes.value =!showLongDes.value,
style: GoogleFonts.rubik(
color: Colors.blue,
decoration: TextDecoration.underline),
)
],
),
);
Upvotes: 3
Reputation: 1304
Option 1: if you are looking for something like facebook,Linkedin you just need one variable isOpen. If isOpen true return MoreText else LessText (use substring) . May not be a perfect solution but you can get what you need
Warning!!! you have to handle text length when using substring
import 'package:flutter/material.dart';
import 'package:linky/presentation/extension/utils.dart';
class ExpandableText extends StatefulWidget {
final String text;
final double max;
const ExpandableText({Key? key, required this.text, required this.max})
: super(key: key);
@override
_ExpandableTextState createState() => _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText> {
TextPainter? textPainter;
bool isOpen = false;
@override
Widget build(BuildContext context) {
return isOpen
? SizedBox(
child: Align(
alignment: Alignment.centerLeft,
child: RichText(
textAlign: TextAlign.start,
text: TextSpan(children: [
TextSpan(
text: widget.text, style: textTheme(context).bodyText1),
WidgetSpan(
child: InkWell(
onTap: () {
setState(() {
isOpen = !isOpen;
});
},
child: Text(
"Less more",
style: textTheme(context).bodyText1!.copyWith(
fontWeight: FontWeight.bold,
color: Colors.blueAccent),
)),
style: textTheme(context).bodyText1)
]),
)))
: Align(
alignment: Alignment.centerLeft,
child: RichText(
textAlign: TextAlign.start,
maxLines: 2,
text: TextSpan(children: [
TextSpan(
text: widget.text.substring(
0,
int.parse(
"${(widget.text.length * widget.max).toInt()}")) +
"...",
style: textTheme(context).bodyText1),
WidgetSpan(
child: InkWell(
mouseCursor: SystemMouseCursors.click,
onTap: () {
setState(() {
isOpen = !isOpen;
});
},
child: Text(
"more",
style: textTheme(context).bodyText1!.copyWith(
fontWeight: FontWeight.bold,
color: Colors.blueAccent),
)),
style: textTheme(context).bodyText1)
]),
),
);
}
}
Exemple :
ExpandableText(
text: users[0].loremIpsum,
max: 0.2,
)
Option 2: If you need something like ExpansionPanel you juste use widget ExpansionTile .
ExpansionTile(title: title,children: [])
Inside Childress moreText & title your LessText . You can hide LessText when isOpen
Upvotes: 2
Reputation: 159
I examine all the answer and make a good solution. Just pass your text here and you can change number of lines just by changing maxLines in text widget..
class ExpandableText extends StatefulWidget {
final String text;
const ExpandableText(this.text, {Key? key}) : super(key: key);
@override
_ExpandableTextState createState() => _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText>
with TickerProviderStateMixin<ExpandableText> {
bool isExpanded = false;
late int numLines;
@override
void initState() {
numLines = '\n'.allMatches(widget.text).length + 1;
super.initState();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedSize(
duration: const Duration(milliseconds: 200),
child: Text(
widget.text,
maxLines: isExpanded ? null : 3,
softWrap: true,
overflow: TextOverflow.fade,
),
),
numLines > 2
? TextButton(
child: const Text('View more'),
onPressed: () => setState(() =>
isExpanded = !isExpanded),
)
: const SizedBox()
],
),
);
}
}
Upvotes: -1
Reputation: 4336
If you want to just use a package to simply do it, you can use the Readmore package. its working just fine and so simple to use.
import 'package:readmore/readmore.dart';
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',
moreStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
);
Upvotes: 13
Reputation: 771
To get Exact Behavior like JQuery plugin. you need to use TextPainter
and calculate the hight on given Width.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
void main() => runApp(MaterialApp( home: DemoApp()));
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Read More Text',
)),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: ExpandableText(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque scelerisque efficitur posuere. Curabitur tincidunt placerat diam ac efficitur. Cras rutrum egestas nisl vitae pulvinar. Donec id mollis diam, id hendrerit neque. Donec accumsan efficitur libero, vitae feugiat odio fringilla ac. Aliquam a turpis bibendum, varius erat dictum, feugiat libero. Nam et dignissim nibh. Morbi elementum varius elit, at dignissim ex accumsan a',
trimLines: 2,
),
),
],
),
),
);
}
}
class ExpandableText extends StatefulWidget {
const ExpandableText(
this.text, {
Key key,
this.trimLines = 2,
}) : assert(text != null),
super(key: key);
final String text;
final int trimLines;
@override
ExpandableTextState createState() => ExpandableTextState();
}
class ExpandableTextState extends State<ExpandableText> {
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 ? "... read more" : " read less",
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: TextStyle(
color: widgetColor,
),
children: <TextSpan>[link],
);
} else {
textSpan = TextSpan(
text: widget.text,
);
}
return RichText(
softWrap: true,
overflow: TextOverflow.clip,
text: textSpan,
);
},
);
return result;
}
}
Upvotes: 39
Reputation: 346
ExpandableText with regrex validation as well.
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class ExpandableText extends StatefulWidget {
ExpandableText(this.text);
final String text;
// bool isExpanded = false;
@override
_ExpandableTextState createState() => new _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText> {
String text;
bool canExpand = false;
bool isExpand = false;
@override
Widget build(BuildContext context) {
//
canExpand = widget.text != null && widget.text.length >= 150;
text = canExpand
? (isExpand ? widget.text : widget.text.substring(0, 150))
: (widget.text);
return canExpand
? Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
buildTextWithLinks(text.trim()),
GestureDetector(
onTap: () {
setState(() {
isExpand = !isExpand;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Text(isExpand ? ' ... show less' : ' ... show more'
),
),
),
],
)
: Text(text != null ? text : "");
}
}
Text buildTextWithLinks(String textToLink, {String text}) =>
Text.rich(TextSpan(children: linkify(textToLink)));
Future<void> openUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
const String urlPattern =
r"(https?|http)://([-A-Z0-9.]+)(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:,.;]*)?";
const String emailPattern = r'\S+@\S+';
const String phonePattern = r'[\d-]{9,}';
final RegExp linkRegExp = RegExp(
'($urlPattern)|($emailPattern)|($phonePattern)',
caseSensitive: false);
WidgetSpan buildLinkComponent(String text, String linkToOpen) => WidgetSpan(
child: InkWell(
child: Text(
text,
style: TextStyle(
color: Colors.red,
decoration: TextDecoration.underline,
),
),
onTap: () => openUrl(linkToOpen),
));
List<InlineSpan> linkify(String text) {
final List<InlineSpan> list = <InlineSpan>[];
final RegExpMatch match = linkRegExp.firstMatch(text);
if (match == null) {
list.add(TextSpan(text: text));
return list;
}
if (match.start > 0) {
list.add(TextSpan(text: text.substring(0, match.start)));
}
final String linkText = match.group(0);
if (linkText.contains(RegExp(urlPattern, caseSensitive: false))) {
list.add(buildLinkComponent(linkText, linkText));
} else if (linkText.contains(RegExp(emailPattern, caseSensitive: false))) {
list.add(buildLinkComponent(linkText, 'mailto:$linkText'));
} else if (linkText.contains(RegExp(phonePattern, caseSensitive: false))) {
list.add(buildLinkComponent(linkText, 'tel:$linkText'));
} else {
throw 'Unexpected match: $linkText';
}
list.addAll(linkify(text.substring(match.start + linkText.length)));
return list;
}
Upvotes: 3
Reputation: 1862
Widget _text() {
var exceeded;
return LayoutBuilder(builder: (context, size) {
// Build the textspan
var span = TextSpan(
text:
"The red-tailed tropicbird is a seabird native to the tropical Indian and Pacific Oceans. One of three closely related species of tropicbird, it has four subspecies. Text wrapping is quite a pain for me too. I find that putting Text in a Container and then wrapping that container in a Expanded/Flexible works well.",
style: Theme.of(context).textTheme.body1.copyWith(color: Colors.white),
);
// Use a textpainter to determine if it will exceed max lines
var tp = TextPainter(
maxLines: _maxLine.toInt(),
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
text: span,
);
// trigger it to layout
tp.layout(maxWidth: size.maxWidth);
// whether the text overflowed or not
exceeded = tp.didExceedMaxLines;
// return Column(children: <Widget>[
return Container(
child: exceeded && seeMoreClicked
? _seeMoreLess(span, "See Less ")
: exceeded && !seeMoreClicked
? _seeMoreLess(span, "See More", 3)
: Text.rich(
span,
overflow: TextOverflow.visible,
),
);
});
}
Widget _seeMoreLess(TextSpan span, String _text, [int maxLine = 0]) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
maxLine > 0
? Text.rich(
span,
overflow: TextOverflow.ellipsis,
maxLines: 3,
)
: Text.rich(
span,
overflow: TextOverflow.visible,
),
InkWell(
child: Text(
_text,
style: Theme.of(context)
.textTheme
.body1
.copyWith(color: Colors.blue),
),
onTap: () {
setState(() {
seeMoreClicked = !seeMoreClicked;
});
}),
],
);
Upvotes: 1
Reputation: 54397
Please use package flutter-expandable.
This package can produce an effect of expanding an image or text.
https://github.com/aryzhov/flutter-expandable
import 'package:expandable/expandable.dart';
...
ExpandablePanel(
header: Text( content,
maxLines: 2,
style: Theme.of(context).textTheme.body2,
),
expanded: Align(
alignment: Alignment.centerLeft,
child: Text(
content,
softWrap: true,
)),
tapHeaderToExpand: true,
hasIcon: true,
),
Upvotes: 4
Reputation: 11005
Try this.
Watch the output from here ( Video )
or refer below images
import 'package:flutter/material.dart';
class DemoPage extends StatefulWidget {
final Widget child;
DemoPage({Key key, this.child}) : super(key: key);
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
String descText = "Description Line 1\nDescription Line 2\nDescription Line 3\nDescription Line 4\nDescription Line 5\nDescription Line 6\nDescription Line 7\nDescription Line 8";
bool descTextShowFlag = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DemoPage"),
),
body: new Container(
margin: EdgeInsets.all(16.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(descText,
maxLines: descTextShowFlag ? 8 : 2,textAlign: TextAlign.start),
InkWell(
onTap: (){ setState(() {
descTextShowFlag = !descTextShowFlag;
}); },
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
descTextShowFlag ? Text("Show Less",style: TextStyle(color: Colors.blue),) : Text("Show More",style: TextStyle(color: Colors.blue))
],
),
),
],
),
),
);
}
}
Upvotes: 10
Reputation: 16300
you can do that this way
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
final String description =
"Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.";
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Demo App"),
),
body: new Container(
child: new DescriptionTextWidget(text: description),
),
);
}
}
class DescriptionTextWidget extends StatefulWidget {
final String text;
DescriptionTextWidget({@required this.text});
@override
_DescriptionTextWidgetState createState() => new _DescriptionTextWidgetState();
}
class _DescriptionTextWidgetState extends State<DescriptionTextWidget> {
String firstHalf;
String secondHalf;
bool flag = true;
@override
void initState() {
super.initState();
if (widget.text.length > 50) {
firstHalf = widget.text.substring(0, 50);
secondHalf = widget.text.substring(50, widget.text.length);
} else {
firstHalf = widget.text;
secondHalf = "";
}
}
@override
Widget build(BuildContext context) {
return new Container(
padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: secondHalf.isEmpty
? new Text(firstHalf)
: new Column(
children: <Widget>[
new Text(flag ? (firstHalf + "...") : (firstHalf + secondHalf)),
new InkWell(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(
flag ? "show more" : "show less",
style: new TextStyle(color: Colors.blue),
),
],
),
onTap: () {
setState(() {
flag = !flag;
});
},
),
],
),
);
}
}
Upvotes: 74
Reputation: 657871
A simple example
class ExpandableText extends StatefulWidget {
ExpandableText(this.text);
final String text;
bool isExpanded = false;
@override
_ExpandableTextState createState() => new _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText> {
@override
Widget build(BuildContext context) {
return new Column(children: <Widget>[
new ConstrainedBox(
constraints: widget.isExpanded
? new BoxConstraints()
: new BoxConstraints(maxHeight: 50.0),
child: new Text(
widget.text,
softWrap: true,
overflow: TextOverflow.fade,
)),
widget.isExpanded
? new Container()
: new FlatButton(
child: const Text('...'),
onPressed: () => setState(() => widget.isExpanded = true))
]);
}
}
with animation
class ExpandableText extends StatefulWidget {
ExpandableText(this.text);
final String text;
bool isExpanded = false;
@override
_ExpandableTextState createState() => new _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText>
with TickerProviderStateMixin<ExpandableText> {
@override
Widget build(BuildContext context) {
return new Column(children: <Widget>[
new AnimatedSize(
vsync: this,
duration: const Duration(milliseconds: 500),
child: new ConstrainedBox(
constraints: widget.isExpanded
? new BoxConstraints()
: new BoxConstraints(maxHeight: 50.0),
child: new Text(
widget.text,
softWrap: true,
overflow: TextOverflow.fade,
))),
widget.isExpanded
? new ConstrainedBox(constraints: new BoxConstraints())
: new FlatButton(
child: const Text('...'),
onPressed: () => setState(() => widget.isExpanded = true))
]);
}
}
Upvotes: 34