моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Последнее время я занят написанием Flutter для адаптации к кросс-терминалам, доработкой общего набора кодов для Android, iOS и HarmonyOS для снижения трудозатрат на содержание. В проекте я столкнулся с необходимостью реализовать то же самое, что и предыдущий. Нативный для Android, когда фрагмент текста превышает максимальное количество строк, обрезается в конце и разворачивается/сворачивается. Сначала я не был знаком с этим, поэтому сначала использовал Stack для динамического управления maxLines и переполнением, а для этого поместил маску и текст в нижний правый угол. Позже я обнаружил, что на некоторых телефонах с экраном возникает неловкая ситуация, когда часть. текста перекрыто и половина слова вытекла, сегодня у меня наконец-то появилось время оптимизировать план реализации.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class ExpandableText extends StatefulWidget {
final String text;
final int maxLines;
final TextStyle textStyle;
final String linkTextExpand;
final String linkTextCollapse;
final Color linkTextColor;
const ExpandableText(
{super.key,
required this.text,
this.maxLines = 2,
this.textStyle = const TextStyle(color: Colors.black),
this.linkTextColor = Colors.blue,
this.linkTextExpand = " 展开",
this.linkTextCollapse = " 收起"});
State<StatefulWidget> createState() => _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText> {
bool isExpanded = false;
late TextSpan expandSpan;
late TextSpan linkTextSpan;
void initState() {
super.initState();
expandSpan = TextSpan(
text: widget.linkTextExpand,
style: TextStyle(color: widget.linkTextColor),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
isExpanded = !isExpanded;
});
},
);
linkTextSpan = TextSpan(
text: '...',
style: widget.textStyle,
children: [expandSpan],
);
}
/// 检查文本是否溢出
bool checkOverflow(double width) {
final textPainter = TextPainter(
text: TextSpan(text: widget.text, style: widget.textStyle),
textDirection: TextDirection.ltr,
);
textPainter.layout(maxWidth: width);
return textPainter.height > widget.maxLines * textPainter.preferredLineHeight;
}
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final maxWidth = constraints.maxWidth;
final textSpan = TextSpan(
text: widget.text,
style: widget.textStyle,
);
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) {
var truncatedText = widget.text;
if (checkOverflow(maxWidth)) {
// 文本的长度超过了最大行数,需要截取然后拼接展开部分的逻辑
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;
truncatedText = widget.text.substring(0, endOffset);
}
return RichText(
text: TextSpan(
text: truncatedText,
style: widget.textStyle,
children: widget.text.length == truncatedText.length ? [] : [linkTextSpan],
),
maxLines: widget.maxLines,
overflow: TextOverflow.ellipsis,
);
}
/// 构建展开状态下的文本
Widget _buildExpandedText() {
final collapseSpan = TextSpan(
text: widget.linkTextCollapse,
style: TextStyle(color: widget.linkTextColor),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
isExpanded = !isExpanded;
});
},
);
return RichText(
text: TextSpan(
text: widget.text,
style: widget.textStyle,
children: [collapseSpan],
),
);
}
}
ExpandableText — это пользовательский виджет Flutter, используемый для отображения длинного текстового содержимого. Когда длина текста превышает заданное максимальное количество строк, текст будет свернут и отобразится ссылка «Развернуть». Щелкните, чтобы полностью развернуть текст, и щелкните еще раз, чтобы свернуть текст.
Используйте StatefulWidget и _ExpandableTextState для управления развернутым/свернутым состоянием текста.
Переменная isExpanded управляет состоянием отображения текста.
Используйте компоненты TextPainter и RichText для измерения и визуализации текста.
Когда текст превышает максимальное количество строк, путем вычисления и перехвата текста убедитесь, что текст отображается правильно в свернутом состоянии, и добавьте ссылку «развернуть».
Добавьте мониторинг событий кликов через TapGestureRecouncer для реализации функций «развернуть» и «свернуть».
Метод setState используется для обновления состояния отображения текста.
Используйте LayoutBuilder, чтобы получить доступную ширину для разных размеров экрана и сред макета.
Метод checkOverflow используется для определения того, превышает ли текст установленное максимальное количество строк.
Предоставляет несколько настраиваемых параметров, таких как maxLines, textStyle, linkTextExpand, linkTextCollapse, linkTextColor, что позволяет пользователям настраивать внешний вид и поведение текста.
ExpandableText(
text: "这是一段很长的文本,它将被折叠起来,只有在点击'展开'链接后才会完全显示。",
maxLines: 2,
textStyle: TextStyle(fontSize: 16, color: Colors.black),
linkTextExpand: " 展开",
linkTextCollapse: " 收起",
linkTextColor: Colors.blue,
);
При использовании ExpandableText убедитесь, что вы предоставляете достаточную ширину контекста, чтобы точка сгиба текста рассчитывалась правильно.
Параметры пользовательского стиля следует настраивать в соответствии с реальными потребностями для достижения наилучшего визуального эффекта.