La répartition : texte flottant
Pourquoi cette série ?
Bien qu'il y ait beaucoup de contenu sur l'utilisation et les personnalisations des widgets Flutter, il y a relativement peu de contenu sur la façon dont le widget atteint réellement la fonctionnalité pour laquelle il est conçu. Même un widget assez simple a beaucoup de simplification sous le capot, même en excluant son objectif principal. Cette série décompose divers Widgets à leur niveau fondamental qui descend généralement au niveau de la toile.
Comprendre la fonction du texte
Text(
"Hello World!",
style: TextStyle(
fontSize: 24.0,
),
)
Mais voici le hic : le Text
Widget n'est pas responsable du rendu du texte, il est responsable de l'intégration des aspects de l'application Flutter dans le texte affiché. C'est la raison pour laquelle le Text
Widget ne s'attend pas à ce qu'un TextStyle
ou d'autres paramètres soient fournis. Il hérite du style de texte par défaut de BuildContext
et construit le texte en conséquence. Au fur et à mesure que vous approfondissez la façon dont le Widget peint le texte à l'écran, vous trouverez de plus en plus de composants qui sont de plus en plus purs - ce qui signifie qu'ils n'héritent d'aucune information du contexte Flutter et ont plutôt besoin que toutes les informations leur soient transmises .
Voici le code simplifié pour la Text
méthode de construction Widget. Comme vous pouvez le voir, la plupart de la méthode incorpore des styles existants, ajoute une zone de souris et ajoute de la sémantique. Le reste de la méthode délègue le rendu à un RichText
Widget.
// The build() method of the Text Widget
Widget build(BuildContext context) {
// Set the effective text style
final defaultTextStyle = DefaultTextStyle.of(context);
TextStyle? effectiveTextStyle = style;
if (style == null || style!.inherit) {
effectiveTextStyle = defaultTextStyle.style.merge(style);
}
if (MediaQuery.boldTextOverride(context)) {
effectiveTextStyle = effectiveTextStyle!.merge(const TextStyle(fontWeight: FontWeight.bold));
}
final registrar = SelectionContainer.maybeOf(context);
// Parameters passed down to RichText
Widget result = RichText(...);
// Create a mouse region
if (registrar != null) {
result = MouseRegion(...);
}
// Set any semantics for the text
if (semanticsLabel != null) {
result = Semantics(...);
}
return result;
}
Sous le capot : RichText
Le RichText
widget est utilisé pour afficher du texte formaté avec des styles variés dans un seul paragraphe ou une seule phrase. Il permet aux développeurs de créer des mises en page de texte complexes avec différentes polices, tailles, couleurs et styles.
Le RichText
Widget accepte un TextSpan
objet pour définir le texte et les styles qui sont affichés à l'écran. La TextSpan
classe permet aux développeurs de définir diverses options de formatage du texte telles que la famille de polices, la taille, l'épaisseur, le style, la couleur et la couleur d'arrière-plan. Il permet également des décorations de texte telles que le soulignement et le barré. En outre, TextSpan
peut également contenir des objets enfants TextSpan
qui autorisent le texte imbriqué avec différents styles et mises en forme pour chaque étendue.
Voici un exemple d'utilisation du RichText
Widget pour afficher du texte simple avec différents styles :
RichText(
text: TextSpan(
text: 'Hello ',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text: 'World',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.normal,
color: Colors.blue,
),
),
TextSpan(
text: '!',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
],
),
),
Voici une version simplifiée de la createRenderObject()
méthode de RichText
:
@override
RenderParagraph createRenderObject(BuildContext context) {
return RenderParagraph(text,
textAlign: textAlign,
// Defaults to inherited directionality
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
// Defaults to inherited locale
locale: locale ?? Localizations.maybeLocaleOf(context),
registrar: selectionRegistrar,
selectionColor: selectionColor,
);
}
Plonger plus profondément : RenderParagraph
La RenderParagraph
classe est RenderObject
responsable du rendu d'un paragraphe de texte à l'écran. Il gère la mise en page du texte, les sauts de ligne et les fonctionnalités de sélection de texte. Il est utilisé comme composant de rendu derrière Text
et TextField
Widget dans Flutter. RenderParagraph
fournit des fonctionnalités de sélection de texte, permettant à l'utilisateur de sélectionner une plage de texte avec une souris ou un geste tactile.
RenderParagraph
se concentre sur le rendu du texte, ce qui signifie qu'il n'hérite d'aucune information Flutter et nécessite que tous les paramètres soient définis - soit par les utilisateurs, soit par défaut. Puisqu'il s'agit d'un RenderObject
, il traite de la mise en page du texte. Cependant, alors que d'habitude RenderObjects
ne traiterait que de la taille de la boîte, le RenderParagraph
doit également gérer la disposition interne du texte à afficher - en tenant compte de la hauteur de ligne, de la direction du texte, de l'habillage, etc.
Il existe différentes méthodes qui traitent de cette procédure de mise en page. Voici quelques méthodes qui traitent du dimensionnement de RenderBox
:
@override
double computeMinIntrinsicWidth(double height) {...}
@override
double computeMaxIntrinsicWidth(double height) {...}
@override
double computeMinIntrinsicHeight(double width) {...}
@override
double computeMaxIntrinsicHeight(double width) {...}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {...}
void _computeChildrenWidthWithMaxIntrinsics(double height) {...}
void _computeChildrenWidthWithMinIntrinsics(double height) {...}
void _computeChildrenHeightWithMinIntrinsics(double width) {...}
Voici une version simplifiée de la méthode :
@override
void performLayout() {
// Calculate size of placeholders
final BoxConstraints constraints = this.constraints;
_placeholderDimensions = _layoutChildren(constraints);
_layoutTextWithConstraints(constraints);
_setParentData();
final Size textSize = _textPainter.size;
final bool textDidExceedMaxLines = _textPainter.didExceedMaxLines;
size = constraints.constrain(textSize);
// Check if text overflows any boundary
final bool didOverflowHeight = size.height < textSize.height || textDidExceedMaxLines;
final bool didOverflowWidth = size.width < textSize.width;
final bool hasVisualOverflow = didOverflowWidth || didOverflowHeight;
if (hasVisualOverflow) {
switch (_overflow) {
case TextOverflow.visible:
_needsClipping = false;
_overflowShader = null;
break;
case TextOverflow.clip:
case TextOverflow.ellipsis:
_needsClipping = true;
_overflowShader = null;
break;
case TextOverflow.fade:
_needsClipping = true;
final fadeSizePainter = TextPainter(...)..layout();
if (didOverflowWidth) {
double fadeEnd, fadeStart;
switch (textDirection) {
... // Set the direction of fade
}
_overflowShader = ui.Gradient.linear(...);
} else {
final double fadeEnd = size.height;
final double fadeStart = fadeEnd - fadeSizePainter.height / 2.0;
_overflowShader = ui.Gradient.linear(...);
}
break;
}
} else {
... // No overflow
}
}
Cependant, cela ne répond toujours pas à ce qui peint réellement le texte à l'écran.
Jusqu'à présent, nous avons :
1) Rassemblez tout ce qui est nécessaire pour le texte : styles, direction, sémantique, sélections, etc.
2) Création d'étendues de texte basées sur les données.
3) Mise en page effectuée pour le texte et ajout de considérations pour le débordement.
Étant donné que les applications Flutter ne sont que des peintures géantes sur une toile, le dernier élément est la TextPainter
classe qui peint le texte pour nous.
Socle rocheux : Canvas et TextPainter
Pour peindre le texte à l'écran, il y a quelques étapes à suivre.
Tout d'abord, nous passons l'arbre des TextSpans
créé par les Widgets avant au TextPainter
. Le RenderParagraph
crée également une instance de TextPainter
à l'intérieur.
Ensuite, nous appelons la layout()
méthode à l'intérieur de la TextPainter
classe qui dimensionne la hauteur et la largeur du texte, et efface également tout cache caret (puisque la classe est également utilisée pour le TextField
Widget).
Voici le code simplifié de la layout()
méthode :
void layout({ double minWidth = 0.0, double maxWidth = double.infinity }) {
if (_paragraph != null && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth) {
return;
}
if (_rebuildParagraphForPaint || _paragraph == null) {
_createParagraph();
}
_lastMinWidth = minWidth;
_lastMaxWidth = maxWidth;
// A change in layout invalidates the cached caret and line metrics as well.
_lineMetricsCache = null;
_previousCaretPosition = null;
_previousCaretPrototype = null;
_layoutParagraph(minWidth, maxWidth);
_inlinePlaceholderBoxes = _paragraph!.getBoxesForPlaceholders();
}
void paint(Canvas canvas, Offset offset) {
final double? minWidth = _lastMinWidth;
final double? maxWidth = _lastMaxWidth;
if (_rebuildParagraphForPaint) {
_createParagraph();
_layoutParagraph(minWidth, maxWidth);
}
canvas.drawParagraph(_paragraph!, offset);
}
Conclusion
Il est très facile de dire " Ce n'est qu'un widget de texte ", mais il y a une personnalisation et une complexité incroyables cachées derrière même le plus simple des widgets. Le rendu de texte en particulier a toujours été une chose complexe à gérer et j'espère que cet article vous aidera à apprécier la complexité qui vous est cachée pour vous permettre de créer vos meilleures idées avec le moindre effort.
Voilà pour cet article ! J'espère que vous l'avez apprécié, et assurez-vous de me suivre pour plus d'articles Flutter et commentez pour tout commentaire que vous pourriez avoir sur cet article.