2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Récemment, j'ai été occupé à écrire Flutter pour s'adapter aux terminaux croisés, en complétant un ensemble commun de codes pour Android, iOS et HarmonyOS afin de réduire les coûts de maintenance de la main-d'œuvre. Dans le projet, j'ai rencontré la nécessité d'implémenter la même chose que le précédent. Natif Android lorsqu'un morceau de texte dépasse le nombre maximum de lignes, tronqué à la fin et divisé en expansion/réduction. Je ne le connaissais pas au début, j'ai donc d'abord utilisé Stack pour contrôler dynamiquement les maxLines et le débordement, et j'ai mis un masque et du texte dans le coin inférieur droit pour y parvenir. Plus tard, j'ai découvert que certains téléphones à écran auraient une situation embarrassante où une partie. du texte est couvert et la moitié du mot s'échappe, aujourd'hui j'ai enfin le temps d'optimiser le plan de mise en œuvre.
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 est un widget Flutter personnalisé utilisé pour afficher du contenu textuel long. Lorsque la longueur du texte dépasse le nombre maximum de lignes prédéfini, le texte sera réduit et un lien « développer » sera affiché. Cliquez pour développer complètement le texte, puis cliquez à nouveau pour réduire le texte.
Utilisez StatefulWidget et _ExpandableTextState pour gérer l'état développé/réduit du texte.
La variable isExpanded contrôle l'état d'affichage du texte.
Utilisez les composants TextPainter et RichText pour mesurer et restituer le texte.
Lorsque le texte dépasse le nombre maximum de lignes, en calculant et en interceptant le texte, assurez-vous que le texte s'affiche correctement à l'état réduit et ajoutez un lien « développer ».
Ajoutez la surveillance des événements de clic via TapGestureRecognizer pour implémenter les fonctions « développer » et « réduire ».
La méthode setState est utilisée pour mettre à jour l’état d’affichage du texte.
Utilisez LayoutBuilder pour obtenir les largeurs disponibles afin de s'adapter à différentes tailles d'écran et environnements de mise en page.
La méthode checkOverflow est utilisée pour déterminer si le texte dépasse le nombre maximum de lignes défini.
Fournit plusieurs paramètres configurables, tels que maxLines, textStyle, linkTextExpand, linkTextCollapse, linkTextColor, permettant aux utilisateurs de personnaliser l'apparence et le comportement du texte.
ExpandableText(
text: "这是一段很长的文本,它将被折叠起来,只有在点击'展开'链接后才会完全显示。",
maxLines: 2,
textStyle: TextStyle(fontSize: 16, color: Colors.black),
linkTextExpand: " 展开",
linkTextCollapse: " 收起",
linkTextColor: Colors.blue,
);
Assurez-vous, lorsque vous utilisez ExpandableText, de fournir une largeur de contexte suffisante pour que le point de pliage du texte soit calculé correctement.
Les paramètres de style personnalisé doivent être ajustés en fonction des besoins réels pour obtenir le meilleur effet visuel.