Berbagi teknologi

Enkapsulasi khusus Flutter mengimplementasikan penyambungan di akhir teks... diperluas/diciutkan ExpandableText

2024-07-12

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

Kata pengantar

Baru-baru ini, saya sibuk menulis Flutter untuk beradaptasi dengan lintas terminal, menyelesaikan serangkaian kode umum untuk Android, iOS, dan HarmonyOS untuk mengurangi biaya pemeliharaan tenaga kerja. Dalam proyek tersebut, saya menghadapi kebutuhan untuk mengimplementasikan hal yang sama seperti sebelumnya Asli Android ketika sepotong teks melebihi jumlah baris maksimum, potong di bagian akhir dan sambung perluas/ciutkan. Saya tidak terbiasa dengan hal itu pada awalnya, jadi saya pertama kali menggunakan Stack untuk mengontrol maxLines dan overflow secara dinamis, dan meletakkan topeng dan teks di sudut kanan bawah untuk mencapainya. Kemudian saya menemukan bahwa beberapa ponsel layar akan mengalami situasi yang memalukan di mana bagian dari teks tertutup dan separuh kata bocor, hari ini saya akhirnya punya waktu untuk mengoptimalkan rencana implementasi.

Menerapkan kode

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

Tinjauan Fungsional

ExpandableText adalah widget Flutter khusus yang digunakan untuk menampilkan konten teks panjang. Ketika panjang teks melebihi jumlah baris maksimum yang telah ditetapkan, teks akan diciutkan dan tautan "perluas" akan ditampilkan. Klik untuk memperluas teks sepenuhnya, dan klik lagi untuk menciutkan teks.

Poin-poin implementasi utama

1. Manajemen status:

Gunakan StatefulWidget dan _ExpandableTextState untuk mengelola status teks yang diperluas/diciutkan.
Variabel isExpanded mengontrol status tampilan teks.

2. Pemrosesan teks:

Gunakan komponen TextPainter dan RichText untuk mengukur dan merender teks.
Ketika teks melebihi jumlah baris maksimum, dengan menghitung dan mencegat teks, pastikan teks ditampilkan dengan benar dalam keadaan diciutkan, dan tambahkan tautan "perluas".

3. Logika interaksi:

Tambahkan pemantauan peristiwa klik melalui TapGestureRecognizer untuk mengimplementasikan fungsi "perluas" dan "ciutkan".
Metode setState digunakan untuk memperbarui status tampilan teks.

4. Tata letak responsif:

Gunakan LayoutBuilder untuk mendapatkan lebar yang tersedia guna mengakomodasi berbagai ukuran layar dan lingkungan tata letak.
Metode checkOverflow digunakan untuk menentukan apakah teks melebihi jumlah baris maksimum yang ditetapkan.

5. Kustomisasi gaya:

Menyediakan beberapa parameter yang dapat dikonfigurasi, seperti maxLines, textStyle, linkTextExpand, linkTextCollapse, linkTextColor, memungkinkan pengguna untuk menyesuaikan tampilan dan perilaku teks.

6. Cara menggunakan

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

Tindakan pencegahan

Pastikan saat menggunakan ExpandableText Anda menyediakan lebar konteks yang cukup sehingga titik lipatan teks dihitung dengan benar.
Parameter gaya khusus harus disesuaikan dengan kebutuhan aktual untuk mencapai efek visual terbaik.