Text组件不支持部分文字(网络下发,非固定个数)下划线。不支持Span数组

一个示例文本,包含一些链接:http://example.com 和 https://anotherlink.com。将链接下面加下划线,并且可点击。由于是网络下发的,不确定链接的个数和位置,Text添加子组件Span,目前不支持添加数组,比如[Span(),Span(),Span()]。使用RichText又需要自己组装html代码。

HarmonyOS
2024-09-29 13:38:26
723浏览
收藏 0
回答 2
回答 2
按赞同
/
按时间
鱼弦CTO
1

在Flutter中,处理动态文本和链接的情况,可以使用​​RichText​​​和​​TextSpan​​​来实现自定义格式化和点击事件。为了避免手动组装HTML代码,我们可以编写一个函数来解析文本并自动生成​​TextSpan​​数组。

以下是一个示例代码,展示了如何实现这个功能:

  1. 首先,导入必要的包:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
  • 1.
  • 2.
  • 3.
  1. 创建一个函数,将原始文本解析为一系列​​TextSpan​​,并将链接部分设置为可点击和带下划线:
List<TextSpan> parseTextWithLinks(String text) {
  final RegExp linkRegExp = RegExp(
    r'((https?:\/\/|www\.)[^\s]+)',
    caseSensitive: false,
  );

  final List<TextSpan> spans = [];
  int start = 0;

  // 查找所有匹配的链接
  for (final Match match in linkRegExp.allMatches(text)) {
    if (match.start > start) {
      spans.add(TextSpan(text: text.substring(start, match.start)));
    }

    final String linkText = match.group(0)!;
    spans.add(
      TextSpan(
        text: linkText,
        style: const TextStyle(
          color: Colors.blue,
          decoration: TextDecoration.underline,
        ),
        recognizer: TapGestureRecognizer()
          ..onTap = () async {
            final Uri url = Uri.parse(linkText);
            if (await canLaunchUrl(url)) {
              await launchUrl(url);
            }
          },
      ),
    );
    start = match.end;
  }

  // 添加最后一段普通文本(如果有的话)
  if (start < text.length) {
    spans.add(TextSpan(text: text.substring(start)));
  }

  return spans;
}
  • 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.
  1. 在你的主应用程序中调用上述函数并将生成的​​TextSpan​​数组传递给​​RichText​​组件:
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Dynamic Links Example')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: RichTextWidget(),
        ),
      ),
    );
  }
}

class RichTextWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final String exampleText =
        '一个示例文本,包含一些链接:http://example.com 和 https://anotherlink.com。';

    return RichText(
      text: TextSpan(
        style: DefaultTextStyle.of(context).style,
        children: parseTextWithLinks(exampleText),
      ),
    );
  }
}
  • 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.

通过这种方式,你可以轻松地处理网络下发的包含不确定数量和位置链接的文本,实现动态下划线和点击功能,而无需手动组装HTML代码。

希望这能解决你的问题!如果有任何进一步的问题,随时提问。

分享
微博
QQ
微信
回复
2024-09-29 13:52:54
FengTianYa

参考demo:

1、自定义text和span类

// BaseUtil.ets  
export enum SpanType {  
  normal,  
  hyperlink,  
}  
export class CustomSpan {  
  public content: string | Resource  
  public type: SpanType  
  public event: () => void  
  constructor(content: string | Resource, type: SpanType = SpanType.normal, event: () => void = () => {  
  }) {  
    this.content = content  
    this.type = type  
    this.event = event  
  }  
  
  normal(): CustomSpan {  
    this.type = SpanType.normal  
    return this  
  }  
  
  hyperlink(event: () => void): CustomSpan {  
    this.type = SpanType.hyperlink  
    this.event = event  
    return this  
  }  
}  
  
export class CustomText {  
  public static readonly PLACEHOLDER = new RegExp('(%[1-9]{1}[0-9]{0,}\\$s)')  
  public spans: Array<CustomSpan>  
  
  constructor(spans: Array<CustomSpan> = []) {  
    this.spans = spans  
  }  
  
  append(span: CustomSpan): CustomText {  
    this.spans.push(span)  
    return this  
  }  
  
  appendNormal(text: string | Resource): CustomText {  
    this.append(new CustomSpan(text).normal())  
    return this  
  }  
  
  appendHyperlink(text: string | Resource, onClick: () => void): CustomText {  
    this.append(new CustomSpan(text).hyperlink(onClick))  
    return this  
  }  
  
  format(format: string | Resource, args: Array<CustomSpan> = []): CustomText {  
    if (!format || !args || args?.length === 0) {  
      this.appendNormal(format)  
      return this  
    }  
  
    if (typeof format === 'string') {  
      if (format.length !== 0) {  
        this.resolve(format, args)  
      }  
    } else {  
      try {  
        let value: string = getContext(this).resourceManager.getStringSync(format)  
        if (value && value.length !== 0) {  
          this.resolve(value, args)  
        }  
      } catch (e) {  
        console.warn(`CustomText getStringSync: ${JSON.stringify(format)} exception: ${e}`);  
      }  
    }  
    return this  
  }  
  
  private resolve(format: string, args: Array<CustomSpan>): CustomText {  
    let length: number = args.length  
    format.split(CustomText.PLACEHOLDER).forEach((value: string, index: number) => {  
      if (!value.match(CustomText.PLACEHOLDER)) {  
        this.appendNormal(value)  
        return  
      }  
      let argIndex: number = Number(value.replace('%', '').replace('$s', '')) - 1  
      if (argIndex >= length) {  
        return  
      }  
      let span: CustomSpan = args[argIndex++]  
      if (span.type === SpanType.normal) {  
        this.appendNormal(span.content)  
      } else if (span.type === SpanType.hyperlink) {  
        this.appendHyperlink(span.content, span.event)  
      }  
    })  
    return this  
  }  
}
  • 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.

2、页面中使用

// TextDemoPage.ets  
import { CustomSpan, CustomText, SpanType } from '../viewmodel/BaseUtil';  
  
class DeclareDemo {  
  format: string | Resource = ''  
  args: CustomSpan[] = []  
}  
  
@Entry  
@Component  
struct TextDemoPage {  
  urlRegex = /(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%$#_]*)?/g;  
  @State declare: DeclareDemo = {  
    format: '',  
    args: []  
  }  
  @State textDeclare: Array<CustomText> = []  
  
  aboutToAppear(): void {  
    let text: string = '这是一个示例文本,包含一些链接:http://example.com 和 https://anotherlink.com。'  
    let urls = text.match(this.urlRegex)  
    let newText: string = text  
    if(urls) {  
      let args: CustomSpan[] = []  
      urls.forEach((url, idx) => {  
        newText = newText.replace(url, `%${idx+1}$s`)  
        args.push(new CustomSpan(url).hyperlink(() => {  
          console.log('111111111111---url: ', url)  
        }),)  
      });  
      this.declare = {  
        format: newText,  
        args: args  
      }  
  
      this.textDeclare = [new CustomText().format(this.declare.format, this.declare.args)]  
      console.log('111 -this.textDeclare: ', JSON.stringify(this.textDeclare))  
    }  
  }  
  
  build() {  
    Row() {  
      Column() {  
        ForEach(this.textDeclare, (declare: CustomText, index) => {  
          Text() {  
            ForEach(declare.spans, (span: CustomSpan) => {  
              if (span.type == SpanType.normal) {  
                Span(span.content)  
                  .fontSize(14)  
                  .fontColor('#191919')  
              } else if (span.type == SpanType.hyperlink) {  
                Span(span.content)  
                  .fontSize(14)  
                  .fontColor('#007dff')  
                  .decoration({type: TextDecorationType.Underline, color: '#007dff'})  
                  .onClick(() => {  
                    if (span.event) {  
                      span.event()  
                    }  
                  })  
              }  
            })  
          }  
        })  
      }  
      .width('100%')  
    }  
    .height('100%')  
  }  
}
  • 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.
分享
微博
QQ
微信
回复
2024-09-29 17:50:05


相关问题
HarmonyOS TextSpan不支持align
668浏览 • 1回复 待解决
HarmonyOS Span不支持n换行
734浏览 • 1回复 待解决
web组件不支持localstorage
1489浏览 • 1回复 待解决
HarmonyOS SVG文件不支持<text>标签吗
840浏览 • 1回复 待解决
Lite Wearable 不支持 http 网络接口吗
4474浏览 • 1回复 待解决
Toggle isOn不支持$$?
788浏览 • 1回复 待解决
HarmonyOS 编码集不支持
757浏览 • 1回复 待解决
HarmonyOS 推送设备不支持
797浏览 • 1回复 待解决
HarmonyOS 部分生僻字不支持展示咨询
943浏览 • 1回复 待解决
如何给文字添加下划线?
1376浏览 • 1回复 待解决
Refresh组件不支持设置nestedScroll属性
2719浏览 • 1回复 待解决
HarmonyOS RNOH Image组件不支持apng
674浏览 • 1回复 待解决
http类不支持cancel方法
869浏览 • 1回复 待解决
HarmonyOS @State不支持HashMap吗
735浏览 • 1回复 待解决