Flutter
在移動(dòng)開(kāi)發(fā)中,常常需要處理一些長(zhǎng)文本顯示的場(chǎng)景,如何優(yōu)雅地展示這些文本并允許用戶展開(kāi)和收起是一個(gè)常見(jiàn)的需求。在本文中,我將分享如何使用Flutter實(shí)現(xiàn)一個(gè)可展開(kāi)和收起的文本控件。
效果
我們將實(shí)現(xiàn)一個(gè)可展開(kāi)和收起的文本控件。當(dāng)文本超過(guò)指定的最大行數(shù)時(shí),會(huì)顯示省略號(hào)和“展開(kāi)”按鈕。點(diǎn)擊“展開(kāi)”按鈕后,文本會(huì)全部顯示,并且按鈕變成“收起”,點(diǎn)擊“收起”按鈕后,文本會(huì)恢復(fù)到初始的折疊狀態(tài)。

屏幕錄制2024-06-02 01.07.52.gif
需求
- 文本內(nèi)容可以動(dòng)態(tài)展開(kāi)和收起。
- 當(dāng)文本內(nèi)容超過(guò)指定的最大行數(shù)時(shí),顯示“展開(kāi)”按鈕。
- 當(dāng)文本內(nèi)容全部顯示時(shí),顯示“收起”按鈕。
- 具有自定義文本樣式的能力。
實(shí)現(xiàn)思路
- 使用
LayoutBuilder來(lái)獲取文本控件的最大寬度。 - 使用
TextPainter來(lái)計(jì)算文本的高度和是否超過(guò)最大行數(shù)。 - 通過(guò)判斷文本是否超出最大行數(shù)來(lái)決定顯示“展開(kāi)”或“收起”按鈕。
- 使用
RichText和TextSpan來(lái)動(dòng)態(tài)構(gòu)建可點(diǎn)擊的“展開(kāi)”和“收起”按鈕。
實(shí)現(xiàn)代碼
以下是實(shí)現(xiàn)可展開(kāi)文本控件的完整代碼:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
詳情見(jiàn):github.com/yixiaolunhui/flutter_xy
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Expandable Text View'),
),
body: const Padding(
padding: EdgeInsets.all(16.0),
child: ExpandableTextView(
text: '我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù),我是一個(gè)測(cè)試的數(shù)據(jù)',
maxLines: 2,
),
),
),
);
}
}
class ExpandableText extends StatefulWidget {
final String text;
final int maxLines;
final TextStyle? textStyle;
const ExpandableText({
Key? key,
required this.text,
required this.maxLines,
this.textStyle,
}) : super(key: key);
@override
ExpandableTextState createState() => ExpandableTextState();
}
class ExpandableTextState extends State<ExpandableText> {
bool isExpanded = false;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final maxWidth = constraints.maxWidth;
final textSpan = TextSpan(
text: widget.text,
style: widget.textStyle ?? const TextStyle(color: Colors.black),
);
final textPainter = TextPainter(
text: textSpan,
maxLines: isExpanded ? null : widget.maxLines,
textDirection: TextDirection.ltr,
);
textPainter.layout(maxWidth: maxWidth);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isExpanded
? _buildExpandedText()
: _buildCollapsedText(textPainter, maxWidth),
],
);
},
);
}
Widget _buildCollapsedText(TextPainter textPainter, double maxWidth) {
final expandSpan = TextSpan(
text: " 展開(kāi)",
style: const TextStyle(color: Colors.blue),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
isExpanded = !isExpanded;
});
},
);
final linkTextSpan = TextSpan(
text: '...',
style: widget.textStyle ?? const TextStyle(color: Colors.black),
children: [expandSpan],
);
final linkPainter = TextPainter(
text: linkTextSpan,
textDirection: TextDirection.ltr,
);
linkPainter.layout(maxWidth: maxWidth);
final position = textPainter.getPositionForOffset(
Offset(maxWidth - linkPainter.width, textPainter.height));
final endOffset =
textPainter.getOffsetBefore(position.offset) ?? position.offset;
final truncatedText = widget.text.substring(0, endOffset);
return RichText(
text: TextSpan(
text: truncatedText,
style: widget.textStyle ?? const TextStyle(color: Colors.black),
children: [linkTextSpan],
),
maxLines: widget.maxLines,
overflow: TextOverflow.ellipsis,
);
}
Widget _buildExpandedText() {
final collapseSpan = TextSpan(
text: " 收起",
style: const TextStyle(color: Colors.blue),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
isExpanded = !isExpanded;
});
},
);
return RichText(
text: TextSpan(
text: widget.text,
style: widget.textStyle ?? const TextStyle(color: Colors.black),
children: [collapseSpan],
),
);
}
}
代碼解析
- ExpandableText Widget: 自定義的文本控件,接收文本內(nèi)容和最大行數(shù)作為輸入?yún)?shù)。
- isExpanded: 控制文本是否展開(kāi)的狀態(tài)變量。
- LayoutBuilder: 用于獲取父容器的最大寬度,以便于后續(xù)的文本布局計(jì)算。
- TextPainter: 用于計(jì)算文本的高度和是否超過(guò)最大行數(shù)。
- RichText: 用于顯示帶有點(diǎn)擊事件的文本(“展開(kāi)”和“收起”)。
總結(jié)
通過(guò)以上實(shí)現(xiàn),我們可以輕松地在Flutter應(yīng)用中使用可展開(kāi)和收起的文本控件,提升用戶體驗(yàn)。這種實(shí)現(xiàn)方式不僅簡(jiǎn)潔高效,還具備良好的可維護(hù)性和擴(kuò)展性。如果你有更復(fù)雜的需求,可以在此基礎(chǔ)上進(jìn)行進(jìn)一步的定制和優(yōu)化。