You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中生成动态富文本:实现提及与话题标签的可点击样式

Flutter评论组件:提及与话题标签的可点击渲染实现

要实现评论内容中提及(@开头)和话题标签(#开头)的可点击+异色显示,核心思路是把原评论字符串拆分成普通文本、提及项、话题标签三种类型的片段,用TextSpan分别定义样式和点击行为,最后通过RichText组件组合渲染。

具体实现代码

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class CommentWidget extends StatelessWidget {
  final String commentMessage;
  final List<String> mentionList;
  final List<String> hashtagList;

  const CommentWidget({
    super.key,
    required this.commentMessage,
    required this.mentionList,
    required this.hashtagList,
  });

  // 拆分评论内容为带样式的TextSpan列表
  List<TextSpan> _buildTextSpans() {
    final List<_SpanItem> spanItems = [];

    // 添加提及项,标记类型为mention
    for (final mention in mentionList) {
      final startIndex = commentMessage.indexOf(mention);
      if (startIndex != -1) {
        spanItems.add(_SpanItem(
          text: mention,
          start: startIndex,
          type: _SpanType.mention,
        ));
      }
    }

    // 添加话题标签,标记类型为hashtag
    for (final hashtag in hashtagList) {
      final startIndex = commentMessage.indexOf(hashtag);
      if (startIndex != -1) {
        spanItems.add(_SpanItem(
          text: hashtag,
          start: startIndex,
          type: _SpanType.hashtag,
        ));
      }
    }

    // 按出现顺序排序,避免拆分混乱
    spanItems.sort((a, b) => a.start.compareTo(b.start));

    final List<TextSpan> textSpans = [];
    int currentIndex = 0;

    for (final item in spanItems) {
      // 添加前面的普通文本
      if (item.start > currentIndex) {
        textSpans.add(TextSpan(
          text: commentMessage.substring(currentIndex, item.start),
          style: const TextStyle(color: Colors.black87),
        ));
      }

      // 添加带样式和点击事件的提及/话题标签
      TextStyle style;
      TapGestureRecognizer? recognizer;

      if (item.type == _SpanType.mention) {
        style = const TextStyle(color: Colors.blue, decoration: TextDecoration.underline);
        recognizer = TapGestureRecognizer()
          ..onTap = () {
            // 处理提及点击事件,比如跳转到用户主页
            print('点击了提及:${item.text}');
          };
      } else {
        style = const TextStyle(color: Colors.orange, decoration: TextDecoration.underline);
        recognizer = TapGestureRecognizer()
          ..onTap = () {
            // 处理话题标签点击事件,比如搜索话题
            print('点击了话题:${item.text}');
          };
      }

      textSpans.add(TextSpan(
        text: item.text,
        style: style,
        recognizer: recognizer,
      ));

      currentIndex = item.start + item.text.length;
    }

    // 添加最后一段普通文本
    if (currentIndex < commentMessage.length) {
      textSpans.add(TextSpan(
        text: commentMessage.substring(currentIndex),
        style: const TextStyle(color: Colors.black87),
      ));
    }

    return textSpans;
  }

  @override
  Widget build(BuildContext context) {
    return RichText(
      text: TextSpan(
        children: _buildTextSpans(),
        style: DefaultTextStyle.of(context).style,
      ),
    );
  }
}

// 辅助类:标记每个特殊片段的信息
enum _SpanType { mention, hashtag }

class _SpanItem {
  final String text;
  final int start;
  final _SpanType type;

  _SpanItem({required this.text, required this.start, required this.type});
}

使用示例

// 在页面中调用CommentWidget
CommentWidget(
  commentMessage: 'Hi @Brad Pitt are u so cute #socute @Jackie Chan',
  mentionList: ['@Brad Pitt', '@Jackie Chan'],
  hashtagList: ['#socute'],
)

关键说明

  • 用_SpanItem类统一管理特殊片段的文本、起始位置和类型,确保拆分顺序正确
  • 通过TapGestureRecognizer给提及和话题标签添加点击事件,可根据需求替换成跳转页面等逻辑
  • 样式可自定义:比如调整颜色、是否显示下划线,匹配你的App设计规范

内容的提问来源于stack exchange,提问作者VolkanUstekidag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 02:25:21