minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Recentemente, tenho estado ocupado escrevendo Flutter para se adaptar a terminais cruzados, completando um conjunto comum de códigos para Android, iOS e HarmonyOS para reduzir custos de manutenção de mão de obra. No projeto, encontrei a necessidade de implementar a mesma coisa que o anterior. Nativo do Android quando um trecho de texto excede o número máximo de linhas, trunca no final e emenda, expande/retrai. Eu não estava familiarizado com isso no início, então usei primeiro Stack para controlar dinamicamente maxLines e overflow, e coloquei uma máscara e texto no canto inferior direito para conseguir isso. Mais tarde, descobri que alguns telefones com tela terão uma situação embaraçosa onde parte. do texto está coberto e metade da palavra vaza, hoje finalmente tenho tempo para otimizar o plano de implementação.
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 é um widget Flutter personalizado usado para exibir conteúdo de texto longo. Quando o comprimento do texto exceder o número máximo predefinido de linhas, o texto será recolhido e um link "expandir" será exibido. Clique para expandir totalmente o texto e clique novamente para recolhê-lo.
Use StatefulWidget e _ExpandableTextState para gerenciar o estado expandido/recolhido do texto.
A variável isExpanded controla o estado de exibição do texto.
Use os componentes TextPainter e RichText para medir e renderizar texto.
Quando o texto ultrapassar o número máximo de linhas, ao calcular e interceptar o texto, certifique-se de que o texto seja exibido corretamente no estado recolhido e adicione um link "expandir".
Adicione monitoramento de eventos de clique por meio do TapGestureRecognizer para implementar as funções "expandir" e "recolher".
O método setState é usado para atualizar o estado de exibição do texto.
Use o LayoutBuilder para obter larguras disponíveis para acomodar diferentes tamanhos de tela e ambientes de layout.
O método checkOverflow é usado para determinar se o texto excede o número máximo de linhas definido.
Fornece vários parâmetros configuráveis, como maxLines, textStyle, linkTextExpand, linkTextCollapse, linkTextColor, permitindo aos usuários personalizar a aparência e o comportamento do texto.
ExpandableText(
text: "这是一段很长的文本,它将被折叠起来,只有在点击'展开'链接后才会完全显示。",
maxLines: 2,
textStyle: TextStyle(fontSize: 16, color: Colors.black),
linkTextExpand: " 展开",
linkTextCollapse: " 收起",
linkTextColor: Colors.blue,
);
Ao usar ExpandableText, certifique-se de fornecer largura de contexto suficiente para que o ponto de dobra do texto seja calculado corretamente.
Os parâmetros de estilo personalizado devem ser ajustados de acordo com as necessidades reais para obter o melhor efeito visual.