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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/670153.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM学习-详解类加载器(二)

双亲委派机制 双亲委派优势 避免类的重复加载&#xff0c;确保一个类的全局唯一性 Java类随着它的类加载器一起具备了一种带有优先级的层次关系&#xff0c;通过这种层次关系可以避免类的重复加载&#xff0c;当父类已经加载了该类&#xff0c;就没有必要子ClassLoader再加载…

Three.js——tween动画、光线投射拾取、加载.obj/.mtl外部文件、使用相机控制器

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

低代码开发平台(Low-code Development Platform)的模块组成部分

低代码开发平台&#xff08;Low-code Development Platform&#xff09;的模块组成部分主要包括以下几个方面&#xff1a; 低代码开发平台的模块组成部分可以按照包含系统、模块、菜单组织操作行为等维度进行详细阐述。以下是从这些方面对平台模块组成部分的说明&#xff1a; …

计算机系统结构之FORK和JOIN

程序语言中用FORK语句派生并行任务&#xff0c;用JOIN语句对多个并发任务汇合。 FORK语句的形式为FORK m&#xff0c;其中m为新领程开始的标号。 JOIN语句的形式为JOIN n&#xff0c;其中n为并发进程的个数。 例1&#xff1a;给定算术表达式ZEA*B*C/DF经并行编译得到如下程序…

什么是 Redis 缓存?它解决了什么问题?怎么使用它?

前言 写在前面&#xff0c;让我们从 3 个问题开始今天的文章&#xff1a;什么是 Redis 缓存&#xff1f;它解决了什么问题&#xff1f;怎么使用它&#xff1f; 在笔者近 3 年的 Java 一线开发经历中&#xff0c;尤其是一些移动端、用户量大的互联网项目&#xff0c;经常会使用…

数学建模 —— 层次分析法(2)

目录 一、层次分析法&#xff08;AHP&#xff09; 二、构造比较判断矩阵 2.1 两两比较法 三、单准则下的排序及一致检验 3.1 单准则下的排序 3.2 一致性检验 四、层次总排序 4.1 层次总排序的步骤 4.2 总排序一致性检验 一、层次分析法&#xff08;AHP&#xff09; 方…

Centos 7部署NTP

介绍 NTP是Network Time Protocol&#xff08;网络时间协议&#xff09;的简称&#xff0c;它是用来通过互联网或局域网将计算机时钟同步到世界协调时间&#xff08;UTC&#xff09;的协议。 安装 # yum安装 yum install -y ntp# 离线安装 #下载地址&#xff1a;https://mir…

Caliburn.Micro框架学习笔记——多页面处理案例

在聊这个之前&#xff0c;我们先来看一个静态类 在 Caliburn.Micro 中&#xff0c;ViewLocator 是一个用于查找和关联视图与视图模型的静态类。默认情况下&#xff0c;它根据约定&#xff08;命名约定或其他规则&#xff09;自动找到与视图模型相对应的视图。然而&#xff0c;…

【5】MySQL数据库备份-XtraBackup - 全量备份

MySQL数据库备份-XtraBackup-全量备份 前言环境版本 安装部署下载RPM 包二进制包 安装卸载 场景分析全量备份 | 恢复备份恢复综合 增量备份 | 恢复部分备份 | 恢复 前言 关于数据库备份的一些常见术语、工具等&#xff0c;可见《MySQL数据库-备份》章节&#xff0c;当前不再重…

【普通切换】【DC-based handover】【DAPS】协议栈分析

移动网络切换 移动通信中切换是保证终端业务的基本流程&#xff0c;而切换时延是终端(UE)不能与任何基站交互(传递)用户面数据包的最短时间。 在5G(NR)网络中当终端(UE)接收到切换命令时&#xff0c;将断开与源小区的连接向目标小区发起随机接入过程。在此期间终端(UE)的数据传…

深入理解linux文件系统与日志分析12

一、inode和block 文件是存储在硬盘上&#xff0c;硬盘上的最小存储单位是扇区&#xff0c;每个扇区的大小是512字节。 inode&#xff1a;存储元信息&#xff08;文件的属性--权限、创建者、创建日期等等&#xff09; block&#xff1a;块 连续的八个扇区组成一个块&#xf…

rpmbuild多进程批量编译脚本

脚本用法 使用手册 bash spec.sh --help Please ensure the directory ~/rpmbuild exists, And the script can execute in any directory.Usage: bash spec.sh [OPTION]... [*.spec]... [OPTION]...or: bash spec.sh [*.spec]... [OPTION]... [*.spec]... rpmbuild all sp…

【原型模式】详解

一.概念 原型模式是一种创建型设计模式&#xff0c;它的主要思想是通过复制现有对象来创建新对象&#xff0c;而不是通过实例化一个类来创建。在原型模式中&#xff0c;我们称被复制的对象为原型&#xff08;Prototype&#xff09;&#xff0c;新创建的对象为克隆体&#xff0…

【OpenHarmony】TypeScript 语法 ③ ( 条件语句 | if else 语句 | switch case 语句 )

文章目录 一、条件语句1、if else 语句2、switch case 语句 参考文档 : <HarmonyOS第一课>ArkTS开发语言介绍 一、条件语句 1、if else 语句 TypeScript 中的 if 语句 / if else 语句 用法 , 与 JavaScript 语言中的 if 语句 / if else 语句 语法 基本相同 ; if else 语…

.gitignore 文件

一.什么是 .gitignore 文件 在任何当前工作的 Git 仓库中&#xff0c;每个文件都是这样的&#xff1a; 追踪的&#xff08;tracked&#xff09;- 这些是 Git 所知道的所有文件或目录。这些是新添加&#xff08;用 git add 添加&#xff09;和提交&#xff08;用 git commit 提…

Ubuntu 20.04安装CMake 3.22.6版本

Ubuntu 20.04通过apt安装的cmake版本是3.16.3&#xff0c;默认安装到/usr/bin/cmake路径。 $ cmake Command cmake not found, but can be installed with:sudo snap install cmake # version 3.29.3, or sudo apt install cmake # version 3.16.3-1ubuntu1.20.04.1See sna…

chatgpt之api的调用问题

1.调用api过程中&#xff0c;出现如下报错内容 先写一个测试样例 import openaiopenai.api_key "OPEN_AI_KEY" openai.api_base"OPEN_AI_BASE_URL" # 是否需要base根据自己所在地区和key情况进行completion openai.ChatCompletion.create(model"g…

【PingPong_注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

C++ | Leetcode C++题解之第125题验证回文串

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isPalindrome(string s) {int n s.size();int left 0, right n - 1;while (left < right) {while (left < right && !isalnum(s[left])) {left;}while (left < right && !isalnu…

【scikit-learn010】sklearn算法模型清单实战及经验总结(已更新)

1.一直以来想写下基于scikit-learn训练AI算法的系列文章,作为较火的机器学习框架,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架模型算法包相关技术点及经验。 3.欢迎批评指正,欢迎互三,跪谢一键…