Technologieaustausch

Die benutzerdefinierte Kapselung von Flutter implementiert das Spleißen am Ende des Texts ... erweitert/reduziert ExpandableText

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Vorwort

Vor kurzem war ich damit beschäftigt, Flutter zu schreiben, um es an Cross-Terminals anzupassen und einen gemeinsamen Codesatz für Android, iOS und HarmonyOS zu vervollständigen, um die Arbeitskosten für die Wartung zu senken. Bei dem Projekt stieß ich auf die Notwendigkeit, dasselbe wie beim vorherigen zu implementieren Android nativ, wenn ein Text die maximale Zeilenanzahl überschreitet, am Ende abschneiden und erweitern/reduzieren. Anfangs war ich damit nicht vertraut, also habe ich zuerst Stack verwendet, um maxLines und Überlauf dynamisch zu steuern, und eine Maske und Text in die untere rechte Ecke eingefügt, um dies zu erreichen. Später stellte ich fest, dass es bei einigen Bildschirmtelefonen zu einer peinlichen Situation kommen kann Nachdem der Text abgedeckt ist und die Hälfte des Wortes durchgesickert ist, habe ich heute endlich Zeit, den Umsetzungsplan zu optimieren.

Code implementieren

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],
      ),
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

Funktionsübersicht

ExpandableText ist ein benutzerdefiniertes Flutter-Widget, das zum Anzeigen langer Textinhalte verwendet wird. Wenn die Textlänge die voreingestellte maximale Zeilenanzahl überschreitet, wird der Text reduziert und ein „Erweitern“-Link angezeigt. Klicken Sie, um den Text vollständig zu erweitern, und klicken Sie erneut, um den Text zu reduzieren.

Wichtige Umsetzungspunkte

1. Statusverwaltung:

Verwenden Sie StatefulWidget und _ExpandableTextState, um den erweiterten/reduzierten Status von Text zu verwalten.
Die Variable isExpanded steuert den Anzeigestatus des Textes.

2. Textverarbeitung:

Verwenden Sie die Komponenten TextPainter und RichText, um Text zu messen und darzustellen.
Wenn der Text die maximale Zeilenanzahl überschreitet, stellen Sie durch Berechnen und Abfangen des Texts sicher, dass der Text im minimierten Zustand korrekt angezeigt wird, und fügen Sie einen Link zum Erweitern hinzu.

3. Interaktionslogik:

Fügen Sie die Klickereignisüberwachung über TapGestureRecognizer hinzu, um die Funktionen „Erweitern“ und „Reduzieren“ zu implementieren.
Die setState-Methode wird verwendet, um den Anzeigestatus von Text zu aktualisieren.

4. Responsives Layout:

Verwenden Sie LayoutBuilder, um verfügbare Breiten für unterschiedliche Bildschirmgrößen und Layoutumgebungen zu erhalten.
Mit der Methode checkOverflow wird ermittelt, ob der Text die festgelegte maximale Zeilenanzahl überschreitet.

5. Stilanpassung:

Bietet mehrere konfigurierbare Parameter wie maxLines, textStyle, linkTextExpand, linkTextCollapse und linkTextColor, mit denen Benutzer das Erscheinungsbild und Verhalten von Text anpassen können.

6. Anwendung

ExpandableText(
  text: "这是一段很长的文本,它将被折叠起来,只有在点击'展开'链接后才会完全显示。",
  maxLines: 2,
  textStyle: TextStyle(fontSize: 16, color: Colors.black),
  linkTextExpand: " 展开",
  linkTextCollapse: " 收起",
  linkTextColor: Colors.blue,
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Vorsichtsmaßnahmen

Achten Sie bei der Verwendung von ExpandableText darauf, dass Sie eine ausreichende Kontextbreite bereitstellen, damit der Faltpunkt des Textes korrekt berechnet wird.
Benutzerdefinierte Stilparameter sollten entsprechend den tatsächlichen Anforderungen angepasst werden, um den besten visuellen Effekt zu erzielen.