Mi información de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Recientemente, estuve ocupado escribiendo Flutter para adaptarlo a terminales cruzados y completar un conjunto común de códigos para Android, iOS y HarmonyOS para reducir los costos de mano de obra y mantenimiento. En el proyecto, encontré la necesidad de implementar lo mismo que el anterior. Nativo de Android, cuando un fragmento de texto excede el número máximo de líneas, se trunca al final y se expande/contrae. Al principio no estaba familiarizado con él, así que primero usé Stack para controlar dinámicamente maxLines y el desbordamiento, y puse una máscara y texto en la esquina inferior derecha para lograrlo. Más tarde descubrí que algunos teléfonos con pantalla tendrán una situación embarazosa. Se cubre parte del texto y se filtra la mitad de la palabra, hoy finalmente tengo tiempo para optimizar el plan de implementación.
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 es un widget de Flutter personalizado que se utiliza para mostrar contenido de texto largo. Cuando la longitud del texto excede el número máximo preestablecido de líneas, el texto se contraerá y se mostrará un enlace "expandir". Haga clic para expandir completamente el texto y haga clic nuevamente para contraer el texto.
Utilice StatefulWidget y _ExpandableTextState para administrar el estado expandido/contraído del texto.
La variable isExpanded controla el estado de visualización del texto.
Utilice los componentes TextPainter y RichText para medir y representar texto.
Cuando el texto excede el número máximo de líneas, al calcular e interceptar el texto, asegúrese de que el texto se muestre correctamente en el estado contraído y agregue un enlace "expandir".
Agregue monitoreo de eventos de clic a través de TapGestureRecognizer para implementar las funciones de "expandir" y "contraer".
El método setState se utiliza para actualizar el estado de visualización del texto.
Utilice LayoutBuilder para obtener anchos disponibles para adaptarse a diferentes tamaños de pantalla y entornos de diseño.
El método checkOverflow se utiliza para determinar si el texto excede el número máximo de líneas establecido.
Proporciona múltiples parámetros configurables, como maxLines, textStyle, linkTextExpand, linkTextCollapse, linkTextColor, lo que permite a los usuarios personalizar la apariencia y el comportamiento del texto.
ExpandableText(
text: "这是一段很长的文本,它将被折叠起来,只有在点击'展开'链接后才会完全显示。",
maxLines: 2,
textStyle: TextStyle(fontSize: 16, color: Colors.black),
linkTextExpand: " 展开",
linkTextCollapse: " 收起",
linkTextColor: Colors.blue,
);
Cuando utilice ExpandableText, asegúrese de proporcionar suficiente ancho de contexto para que el punto de plegado del texto se calcule correctamente.
Los parámetros de estilo personalizados deben ajustarse según las necesidades reales para lograr el mejor efecto visual.