• Flutter-自定义可展开文本控件


    Flutter

    移动开发中,常常需要处理一些长文本显示的场景,如何优雅地展示这些文本并允许用户展开和收起是一个常见的需求。在本文中,我将分享如何使用Flutter实现一个可展开和收起的文本控件。

    效果

    我们将实现一个可展开和收起的文本控件。当文本超过指定的最大行数时,会显示省略号和“展开”按钮。点击“展开”按钮后,文本会全部显示,并且按钮变成“收起”,点击“收起”按钮后,文本会恢复到初始的折叠状态。

    需求

    1. 文本内容可以动态展开和收起。
    2. 当文本内容超过指定的最大行数时,显示“展开”按钮。
    3. 当文本内容全部显示时,显示“收起”按钮。
    4. 具有自定义文本样式的能力。

    实现思路

    1. 使用LayoutBuilder来获取文本控件的最大宽度。
    2. 使用TextPainter来计算文本的高度和是否超过最大行数。
    3. 通过判断文本是否超出最大行数来决定显示“展开”或“收起”按钮。
    4. 使用RichTextTextSpan来动态构建可点击的“展开”和“收起”按钮。

    实现代码

    以下是实现可展开文本控件的完整代码:

    import 'package:flutter/gestures.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Expandable Text View'),
            ),
            body: const Padding(
              padding: EdgeInsets.all(16.0),
              child: ExpandableTextView(
                text: '我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据,我是一个测试的数据',
                maxLines: 2,
              ),
            ),
          ),
        );
      }
    }
    
    class ExpandableText extends StatefulWidget {
      final String text;
      final int maxLines;
      final TextStyle? textStyle;
    
      const ExpandableText({
        Key? key,
        required this.text,
        required this.maxLines,
        this.textStyle,
      }) : super(key: key);
    
      
      ExpandableTextState createState() => ExpandableTextState();
    }
    
    class ExpandableTextState extends State<ExpandableText> {
      bool isExpanded = false;
    
      
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            final maxWidth = constraints.maxWidth;
            final textSpan = TextSpan(
              text: widget.text,
              style: widget.textStyle ?? const TextStyle(color: Colors.black),
            );
    
            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) {
        final expandSpan = TextSpan(
          text: " 展开",
          style: const TextStyle(color: Colors.blue),
          recognizer: TapGestureRecognizer()
            ..onTap = () {
              setState(() {
                isExpanded = !isExpanded;
              });
            },
        );
    
        final linkTextSpan = TextSpan(
          text: '...',
          style: widget.textStyle ?? const TextStyle(color: Colors.black),
          children: [expandSpan],
        );
    
        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;
        final truncatedText = widget.text.substring(0, endOffset);
    
        return RichText(
          text: TextSpan(
            text: truncatedText,
            style: widget.textStyle ?? const TextStyle(color: Colors.black),
            children: [linkTextSpan],
          ),
          maxLines: widget.maxLines,
          overflow: TextOverflow.ellipsis,
        );
      }
    
      Widget _buildExpandedText() {
        final collapseSpan = TextSpan(
          text: " 收起",
          style: const TextStyle(color: Colors.blue),
          recognizer: TapGestureRecognizer()
            ..onTap = () {
              setState(() {
                isExpanded = !isExpanded;
              });
            },
        );
    
        return RichText(
          text: TextSpan(
            text: widget.text,
            style: widget.textStyle ?? const TextStyle(color: Colors.black),
            children: [collapseSpan],
          ),
        );
      }
    }
    

    代码解析

    • ExpandableText Widget: 自定义的文本控件,接收文本内容和最大行数作为输入参数。
    • isExpanded: 控制文本是否展开的状态变量。
    • LayoutBuilder: 用于获取父容器的最大宽度,以便于后续的文本布局计算。
    • TextPainter: 用于计算文本的高度和是否超过最大行数。
    • RichText: 用于显示带有点击事件的文本(“展开”和“收起”)。

    总结

    通过以上实现,我们可以轻松地在Flutter应用中使用可展开和收起的文本控件,提升用户体验。这种实现方式不仅简洁高效,还具备良好的可维护性和扩展性。如果你有更复杂的需求,可以在此基础上进行进一步的定制和优化。

    详情见:github.com/yixiaolunhui/flutter_xy

  • 相关阅读:
    电影票房排名查询易语言代码
    学生个人单页面网页作业 学生网页设计成品 静态HTML网页单页制作 dreamweaver网页设计与制作代码 web前端期末大作业
    lower_bound 和 upper_bound
    基于JavaWeb聊天室设计与实现
    【后端】韩顺平Java学习笔记(入门篇)
    nodejs多版本管理
    Yii2安装遇到Loading composer repositories with package information
    基于Boostrap+Html的响应式Web电影网站设计
    2022年Java后端面试题,备战秋招,查缺补漏,吃透16套专题技术栈
    大小端总结
  • 原文地址:https://blog.csdn.net/u014741977/article/details/139382554