Flutter-RichText 文本更多,隐藏效果

2. RichText使用

文字后展开、隐藏功能实现

完整代码在文本末尾

 

2.1. 实现目标

  1. 当文本超过3行时,显示省略号并显示 ‘更多’ 按钮,点击更多展示完整文本;

  2. 完整文本末尾显示 ‘隐藏’ 按钮,点击隐藏,回到状态 1

  3. 多条数据互不影响

2.2. 基本实现思路:

  • 判断文本是否超过最大行maxLines,可以通过TextPainter获取小控件属性,再根据 didExceedMaxLines 属性返回的布尔值进行判断。

  • 判断完成后,因为要在三行文本内加入按钮,只能切割字符串,再使用 RichText 行内拼接的控件。

2.3. 完整流程

  1. 列表组件渲染

    因为是多条数据互不影响,则是列表渲染 ListView

    Expanded(
      child: ListView.builder(
        itemCount: data.length,
        itemBuilder: this._listViewModal,
      ),
    )

  2. 补充代码,完整基础样式

  3. 获取文本容器属性

    /**
     * @name: getTextPainter
     * @description: 获取TextPainter小控件属性
     * @param {*}
     * @return {*}
     */
    getTextPainter(BuildContext context, String text, TextStyle style,
        double maxWidth, int maxLines) {
      double textScaleFactor = MediaQuery.of(context).textScaleFactor;
      final TextPainter textPainter = TextPainter(
          locale: WidgetsBinding.instance!.window.locale,
          text: TextSpan(text: text, style: style),
          textScaleFactor: textScaleFactor,
          maxLines: maxLines,
          textDirection: TextDirection.ltr)
        ..layout(minWidth: 0, maxWidth: maxWidth);
      return textPainter;
    }

  4. 根据文本属性切割字符串

    final _textPainter = getTextPainter(
      context, item["synopsis_tc"], descStyle, textMaxWidth, maxRowNum);
    final moreText = _textPainter.didExceedMaxLines;
    if (moreText) {
      // 三行文本容器的宽高信息
      final textSize = _textPainter.size;
      // 在容器范围内,显示的文本数量
      final position = _textPainter
      .getPositionForOffset(Offset(textMaxWidth, textSize.height));
      // 为按钮预留空间-3
      final endOffset = position.offset - 3;
      // 截取默认展示的文本
      showStr = item["synopsis_tc"].substring(0, endOffset);
    }

  5. 拼接字符串,添加点击事件

    /**
    * @name: text
    * @description: 当前3行的文本内容
    * @param {*}
    * @return {*}
    */
    RichText txt = RichText(
      text: TextSpan(
        text: (moreText && !showMoreIds.contains(textId))
          ? "$showStr..."
          : '${item["synopsis_tc"]}',
        style: descStyle,
        children: [
          TextSpan(
            text: moreText ? (showMoreIds.contains(textId) ? '隐藏' : '更多') : '',
            style: txtBtnSty,
            recognizer: TapGestureRecognizer()
              ..onTap = () {
                _setShowType(textId);
              },
          )
      ]));

2.4 完整代码

/*
 * @Description:分类页面
 * @version:
 * @Date: 2022-03-14 14:29:37
 */
​
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:myapp_one/static_data/category_data.dart';
import 'package:myapp_one/utils/singleton.dart';
​
double titleSize = 18.0;
double descSize = 16.0;
double paddingOth = 10.0;
double paddingBot = 15.0;
// 获取屏幕宽高-网上一搜就有
double maxWidth = Singleton().screenWidth;
double maxHeight = Singleton().screenHeight;
int maxRowNum = 3;
​
double boxMaxWidth = maxWidth - (paddingOth * 2);
double textMaxWidth = boxMaxWidth - (paddingOth * 2);
int descRowNum = textMaxWidth ~/ descSize;
​
TextStyle descStyle = TextStyle(
  fontSize: descSize,
  color: Colors.black87,
);
TextStyle txtBtnSty = TextStyle(
  fontSize: descSize - 1,
  color: Colors.blue,
);
​
/**
 * @name: getTextPainter
 * @description: 获取TextPainter小控件属性
 * @param {*}
 * @return {*}
 */
getTextPainter(BuildContext context, String text, TextStyle style,
    double maxWidth, int maxLines) {
  double textScaleFactor = MediaQuery.of(context).textScaleFactor;
  final TextPainter textPainter = TextPainter(
      locale: WidgetsBinding.instance!.window.locale,
      text: TextSpan(text: text, style: style),
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      textDirection: TextDirection.ltr)
    ..layout(minWidth: 0, maxWidth: maxWidth);
  return textPainter;
}
​
/**
 * @name: CategoryPage
 * @description: 主体class
 * @param {*}
 * @return {*}
 */
class CategoryPage extends StatefulWidget {
  // 数据源头,可以根据字段编写数据
  final content = categoryData["content"];
  @override
  _CategoryPageState createState() => _CategoryPageState();
}
​
/**
 * @name: _CategoryPageState
 * @description: 次级主体-主要实现的部分,包括state状态改变
 * @param {*}
 * @return {*}
 */
class _CategoryPageState extends State<CategoryPage> {
  List data = [];
  String nowDate = '';
  List showMoreIds = [];
​
  _setShowType(textId) {
    setState(() {
      bool boolRes = this.showMoreIds.contains(textId);
      if (boolRes) {
        this.showMoreIds.remove(textId);
      } else {
        this.showMoreIds.add(textId);
      }
    });
  }
​
  /**
   * @name: _listViewModal
   * @description: 列表渲染
   * @param {*}
   * @return {*}
   */
  Widget _listViewModal(context, index) {
    Map item = data[index];
    String showStr = '';
    String textId = "$index-${item['episode_no']}";
​
    final _textPainter = getTextPainter(
        context, item["synopsis_tc"], descStyle, textMaxWidth, maxRowNum);
    final moreText = _textPainter.didExceedMaxLines;
    if (moreText) {
      // 三行文本容器的宽高信息
      final textSize = _textPainter.size;
      // 在容器范围内,显示的文本数量
      final position = _textPainter
          .getPositionForOffset(Offset(textMaxWidth, textSize.height));
      // 为按钮预留空间-3
      final endOffset = position.offset - 3;
      // 截取默认展示的文本
      showStr = item["synopsis_tc"].substring(0, endOffset);
    }
​
    /**
     * @name: text
     * @description: 当前3行的文本内容
     * @param {*}
     * @return {*}
     */
    RichText txt = RichText(
        text: TextSpan(
            text: (moreText && !showMoreIds.contains(textId))
                ? "$showStr..."
                : '${item["synopsis_tc"]}',
            style: descStyle,
            children: [
          TextSpan(
            text: moreText ? (showMoreIds.contains(textId) ? '隐藏' : '更多') : '',
            style: txtBtnSty,
            recognizer: TapGestureRecognizer()
              ..onTap = () {
                _setShowType(textId);
              },
          )
        ]));
    return Container(
      margin:
          EdgeInsets.fromLTRB(paddingOth, paddingOth, paddingOth, paddingBot),
      padding: EdgeInsets.all(paddingOth),
      decoration: BoxDecoration(
          border: Border.all(color: Color.fromRGBO(0, 0, 0, 0.1), width: 1),
          borderRadius: BorderRadius.circular(8)),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '${item["start_datetime"]}',
            style: TextStyle(
              fontSize: titleSize,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 5),
          Text(
            item["title_tc"],
            style: TextStyle(
              fontSize: titleSize,
              color: Colors.black,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 5),
          Stack(
            children: [
              txt,
            ],
          ),
          SizedBox(height: 5),
        ],
      ),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    // 获取数据,可以替换成自身数据
    nowDate = "2022-03-18";
    data = this.widget.content[nowDate] ?? [];
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: data.length,
              itemBuilder: this._listViewModal,
            ),
          )
        ],
      ),
    );
  }
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值