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

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

HarmonyOS
2024-09-29 13:38:26
浏览
收藏 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. 创建一个函数,将原始文本解析为一系列​​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. 在你的主应用程序中调用上述函数并将生成的​​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),
      ),
    );
  }
}

通过这种方式,你可以轻松地处理网络下发的包含不确定数量和位置链接的文本,实现动态下划线和点击功能,而无需手动组装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  
  }  
}

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%')  
  }  
}
分享
微博
QQ
微信
回复
2024-09-29 17:50:05
相关问题
web组件不支持localstorage
800浏览 • 1回复 待解决
Lite Wearable 不支持 http 网络接口吗
3509浏览 • 1回复 待解决
Refresh组件不支持设置nestedScroll属性
1930浏览 • 1回复 待解决
如何给文字添加下划线?
610浏览 • 1回复 待解决
HarmonyOS 部分生僻字不支持展示咨询
327浏览 • 1回复 待解决
http类不支持cancel方法
174浏览 • 1回复 待解决
Image组件不支持读入沙盒内的图片
866浏览 • 1回复 待解决
Image组件不支持svg字符串显示
296浏览 • 1回复 待解决
HarmonyOS ets不支持匿名类吗?
206浏览 • 2回复 待解决
HarmonyOS Object不支持 ... 展开符吗?
251浏览 • 1回复 待解决
HarmonyOS 不支持通过索引访问字段
286浏览 • 1回复 待解决
RelativeContainer容器不支持自动宽高
474浏览 • 1回复 待解决
API 9 是否不支持 npm 了?
2738浏览 • 1回复 待解决
4.0release不支持热重载?
2549浏览 • 1回复 待解决
@BuilderParam 不支持普通class的变量
783浏览 • 1回复 待解决
鸿蒙NEXT版本,支不支持APK?
2840浏览 • 1回复 待解决