Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tejaswini-dev-techie/8bd3d8dea49b6f32acb32e54ba9d8f49 to your computer and use it in GitHub Desktop.
Save tejaswini-dev-techie/8bd3d8dea49b6f32acb32e54ba9d8f49 to your computer and use it in GitHub Desktop.
Animated Read More/Read Less Functionality to Expandable Text
import 'package:flutter/material.dart';
/// The main function that runs the Flutter app by calling [runApp] with an instance of [MyApp].
void main() {
runApp(const MyApp());
}
/// The MyApp class defines the root widget of the app.
/// It extends [StatelessWidget] and overrides [build] to return a [MaterialApp].
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter',
home: Demo(),
);
}
}
/// The Demo widget is the root widget of the app. It creates an instance of
/// _DemoState which builds the UI.
///
/// The _DemoState widget builds a Scaffold with a ListView containing an
/// ExpandableWidget to demonstrate expandable text functionality.
class Demo extends StatefulWidget {
const Demo({super.key});
@override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: const [
ExpandableWidget(
content:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Risus ultricies tristique nulla aliquet enim tortor at. Integer quis auctor elit sed vulputate mi sit amet mauris. Turpis nunc eget lorem dolor sed. Leo vel fringilla est ullamcorper eget. Est pellentesque elit ullamcorper dignissim cras tincidunt lobortis. Mattis vulputate enim nulla aliquet porttitor lacus luctus. Elit at imperdiet dui accumsan sit amet nulla. Venenatis a condimentum vitae sapien pellentesque habitant morbi. Varius duis at consectetur lorem donec massa. Nunc sed id semper risus in hendrerit. Porta lorem mollis aliquam ut porttitor leo. Tristique magna sit amet purus gravida quis blandit. At in tellus integer feugiat scelerisque. Scelerisque felis imperdiet proin fermentum leo vel orci porta. Viverra justo nec ultrices dui sapien eget. Nunc eget lorem dolor sed viverra ipsum nunc. Id consectetur purus ut faucibus pulvinar elementum integer. Quam adipiscing vitae proin sagittis nisl rhoncus mattis. Consequat nisl vel pretium lectus quam id leo in. At erat pellentesque adipiscing commodo elit at imperdiet dui. Nam at lectus urna duis. Semper risus in hendrerit gravida rutrum quisque non tellus orci. Vel turpis nunc eget lorem dolor sed. Amet mattis vulputate enim nulla aliquet. At varius vel pharetra vel turpis. Massa tincidunt dui ut ornare lectus sit amet. Vitae turpis massa sed elementum tempus egestas sed sed risus. Hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Non odio euismod lacinia at quis risus. Feugiat sed lectus vestibulum mattis ullamcorper. Tellus in metus vulputate eu scelerisque felis imperdiet proin fermentum. Amet consectetur adipiscing elit ut aliquam purus sit amet luctus. Sit amet risus nullam eget felis eget nunc. Egestas purus viverra accumsan in nisl. Tincidunt praesent semper feugiat nibh sed pulvinar proin. Rhoncus aenean vel elit scelerisque mauris. Nulla facilisi morbi tempus iaculis urna id. Imperdiet nulla malesuada pellentesque elit eget. Aliquam faucibus purus in massa tempor nec. Pulvinar etiam non quam lacus. Aliquam sem et tortor consequat id. Mi quis hendrerit dolor magna eget est lorem. Congue mauris rhoncus aenean vel elit. Tellus in metus vulputate eu scelerisque felis imperdiet proin. A diam sollicitudin tempor id eu nisl. Id aliquet risus feugiat in. Semper eget duis at tellus at urna. Rutrum tellus pellentesque eu tincidunt tortor aliquam. Sit amet volutpat consequat mauris nunc congue nisi vitae. Nisi est sit amet facilisis magna. In mollis nunc sed id semper. Quis ipsum suspendisse ultrices gravida dictum fusce ut. Ut pharetra sit amet aliquam id diam maecenas ultricies mi. Eu mi bibendum neque egestas congue. Quam quisque id diam vel quam elementum. Sit amet est placerat in egestas erat imperdiet sed euismod. Eu facilisis sed odio morbi. Rhoncus dolor purus non enim praesent elementum facilisis leo. Gravida arcu ac tortor dignissim convallis aenean. Sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum. Nunc sed blandit libero volutpat. Elementum facilisis leo vel fringilla est ullamcorper eget.",
),
],
),
),
),
);
}
}
/// A widget that can expand or collapse to show more or less content.
///
/// Takes in a [String] for the content that can be expanded/collapsed.
/// Manages its own state to handle the expanded/collapsed state.
class ExpandableWidget extends StatefulWidget {
final String? content;
const ExpandableWidget({
Key? key,
required this.content,
}) : super(key: key);
@override
State<ExpandableWidget> createState() => _ExpandableWidgetState();
}
/// Manages the expanded/collapsed state of the widget to show more
/// or less content. Uses a ValueNotifier and AnimatedSize to animate
/// between limited and full text content.
class _ExpandableWidgetState extends State<ExpandableWidget>
with SingleTickerProviderStateMixin {
final ValueNotifier<bool> expanded = ValueNotifier(false);
final int maxLinesToShow = 15;
final GlobalKey _textKey = GlobalKey();
@override
Widget build(BuildContext context) {
/// Creates a TextSpan to style the content text.
///
/// The text style is set to normal font weight, font size 11, and black color.
/// The content text is taken from the widget's content property or empty string if null.
final TextSpan textSpan = TextSpan(
text: widget.content ?? "",
style: const TextStyle(
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
fontSize: 11.0,
color: Colors.black,
),
);
/// Creates a TextPainter to layout and measure the text.
///
/// The TextPainter is configured with the textSpan containing the content text,
/// the maxLines set based on the expanded state, and left-to-right text direction.
/// This allows measuring the size of the text and computing the number of lines.
final TextPainter textPainter = TextPainter(
text: textSpan,
maxLines: expanded.value ? null : maxLinesToShow,
textDirection: TextDirection.ltr,
);
/// Lays out the text painter using the available width from the MediaQuery.
///
/// This will compute the text layout based on the provided constraints.
/// The number of lines is then retrieved from the text painter's line metrics.
textPainter.layout(maxWidth: MediaQuery.of(context).size.width);
final int numberOfLines = textPainter.computeLineMetrics().length;
return ValueListenableBuilder(
valueListenable: expanded,
builder: (context, values, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Renders the content text in an expandable/collapsible way to toggle
/// between limited and full text content.
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (!expanded.value && numberOfLines >= maxLinesToShow) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Animates the size of the text widget to expand/collapse when toggling between
/// limited and full text content. The duration is set to 300ms for smooth animation.
/// The text content is truncated with ellipsis when maxLines is reached. The text
/// style is configured for normal font, size 11, black color.
AnimatedSize(
duration: const Duration(milliseconds: 300),
child: Text(
widget.content ?? "",
key: _textKey,
maxLines: maxLinesToShow,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
fontSize: 11.0,
color: Colors.black,
),
),
),
/* See More :: type 1 - See More | 2 - See Less */
SeeMoreLessWidget(
textData: 'See More',
type: 1,
section: 1,
onSeeMoreLessTap: () {
expanded.value = true;
},
),
/* See More :: type 1 - See More | 2 - See Less */
],
);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Animates the size of the text widget to expand/collapse when toggling between
/// limited and full text content. The duration is set to 300ms for smooth animation.
AnimatedSize(
duration: const Duration(milliseconds: 300),
child: Text(
widget.content ?? "",
key: _textKey,
style: const TextStyle(
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
fontSize: 11.0,
color: Colors.black,
),
),
),
if (expanded.value && numberOfLines >= maxLinesToShow)
/* See Less :: type 1 - See More | 2 - See Less */
SeeMoreLessWidget(
textData: 'See Less',
type: 2,
section: 1,
onSeeMoreLessTap: () {
expanded.value = false;
},
),
/* See Less :: type 1 - See More | 2 - See Less */
],
);
}
},
),
],
);
},
);
}
}
/// Renders a rich text widget to show a "See More/See Less" expandable text.
/// It takes in the text to show, icon type, tap handler and section number.
/// The textData parameter specifies the main text content.
/// The type parameter indicates whether to show the "See More" or "See Less" icon.
/// The onSeeMoreLessTap callback handles the tap event to expand/collapse the text.
/// The section number indicates which section this widget is used for.
class SeeMoreLessWidget extends StatelessWidget {
final String? textData;
final int? type; /* type 1 - See More | 2 - See Less */
final Function? onSeeMoreLessTap;
final int?
section; /* 1: About the course | 2 - Who can take up this course? | 3 - Mentors | 4 - Course Video Reviews */
const SeeMoreLessWidget({
Key? key,
required this.textData,
required this.type,
required this.onSeeMoreLessTap,
required this.section,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(top: 12.0),
child: InkWell(
onTap: () {
if (onSeeMoreLessTap != null) {
onSeeMoreLessTap!();
}
},
/// Renders a rich text widget with the given [textData] and an icon that toggles
/// between keyboard_arrow_down and keyboard_arrow_up based on the [type] value.
/// Styles the text and icon with blue color.
child: Text.rich(
softWrap: true,
style: const TextStyle(
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
fontSize: 13.0,
color: Colors.blue,
),
textAlign: TextAlign.start,
TextSpan(
text: "",
children: [
TextSpan(
text: textData,
style: const TextStyle(
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
fontSize: 13.0,
color: Colors.blue,
),
),
const WidgetSpan(
child: SizedBox(
width: 3.0,
),
),
WidgetSpan(
child: Icon(
(type == 1)
? Icons.keyboard_arrow_down
: Icons.keyboard_arrow_up,
color: Colors.blue,
size: 17.5,
),
),
],
),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment