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

如何在MAUI中创建带手势事件的自定义Span渲染器(仅安卓和iOS)

实现.NET 7 MAUI带手势的自定义Span渲染器(仅安卓/iOS)

第一步:创建跨平台自定义Span控件

在共享项目中定义带点击事件的自定义Span,作为跨平台抽象层:

using Microsoft.Maui.Controls;

namespace YourAppNamespace
{
    public class TapGestureSpan : Span
    {
        public event EventHandler Tapped;

        public void OnTapped()
        {
            Tapped?.Invoke(this, EventArgs.Empty);
        }
    }
}

第二步:安卓平台渲染器实现

在安卓项目的Platforms/Android目录下创建渲染器,通过原生ClickableSpan处理触摸逻辑:

using Android.Text;
using Android.Views;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using YourAppNamespace;
using YourAppNamespace.Platforms.Android;

[assembly: ExportRenderer(typeof(TapGestureSpan), typeof(TapGestureSpanRenderer))]
namespace YourAppNamespace.Platforms.Android
{
    public class TapGestureSpanRenderer : SpanRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Span> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement is TapGestureSpan tapSpan && Control is ISpannable spannable)
            {
                var clickableSpan = new CustomClickableSpan(tapSpan);
                spannable.SetSpan(clickableSpan, 0, tapSpan.Text.Length, SpanTypes.ExclusiveExclusive);
            }
        }

        private class CustomClickableSpan : Android.Text.Style.ClickableSpan
        {
            private readonly TapGestureSpan _tapSpan;

            public CustomClickableSpan(TapGestureSpan tapSpan)
            {
                _tapSpan = tapSpan;
            }

            public override void OnClick(View widget)
            {
                _tapSpan.OnTapped();
            }

            public override void UpdateDrawState(TextPaint ds)
            {
                base.UpdateDrawState(ds);
                // 可自定义样式,比如取消默认下划线
                ds.UnderlineText = false;
            }
        }
    }
}

第三步:iOS平台渲染器实现

在iOS项目的Platforms/iOS目录下创建渲染器,利用UITapGestureRecognizer识别点击位置:

using Foundation;
using Microsoft.Maui.Controls.Compatibility.Platform.iOS;
using UIKit;
using YourAppNamespace;
using YourAppNamespace.Platforms.iOS;

[assembly: ExportRenderer(typeof(TapGestureSpan), typeof(TapGestureSpanRenderer))]
namespace YourAppNamespace.Platforms.iOS
{
    public class TapGestureSpanRenderer : SpanRenderer
    {
        private UITapGestureRecognizer _tapGesture;

        protected override void OnElementChanged(ElementChangedEventArgs<Span> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement is TapGestureSpan tapSpan && Control is NSMutableAttributedString attributedString)
            {
                var range = new NSRange(0, attributedString.Length);
                attributedString.AddAttribute(UIStringAttributeKey.Link, new NSString("tap://"), range);

                if (_tapGesture == null)
                {
                    _tapGesture = new UITapGestureRecognizer(HandleTap);
                    ContainerView.AddGestureRecognizer(_tapGesture);
                }
            }
            else if (e.OldElement != null)
            {
                // 清理资源
                if (_tapGesture != null)
                {
                    ContainerView.RemoveGestureRecognizer(_tapGesture);
                    _tapGesture.Dispose();
                    _tapGesture = null;
                }
            }
        }

        private void HandleTap(UITapGestureRecognizer gesture)
        {
            if (Element is TapGestureSpan tapSpan && ContainerView is UITextView textView)
            {
                var location = gesture.LocationInView(textView);
                var position = textView.GetPosition(textView.BeginningOfDocument, location);
                if (position != null)
                {
                    var range = textView.GetCharacterRange(position);
                    if (range.Location >= 0 && range.Location < tapSpan.Text.Length)
                    {
                        tapSpan.OnTapped();
                    }
                }
            }
        }
    }
}

第四步:在页面中使用自定义Span

在XAML中引用自定义Span并绑定点击事件:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:YourAppNamespace"
             x:Class="YourAppNamespace.MainPage">

    <ScrollView>
        <VerticalStackLayout Padding="10">
            <Label FontSize="18">
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text="这是一段普通文本," />
                        <local:TapGestureSpan Text="点击此处触发事件" 
                                             Tapped="TapGestureSpan_Tapped" />
                        <Span Text=",继续浏览内容。" />
                    </FormattedString>
                </Label.FormattedText>
            </Label>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

后台代码处理点击事件:

namespace YourAppNamespace
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void TapGestureSpan_Tapped(object sender, EventArgs e)
        {
            DisplayAlert("提示", "自定义Span被点击!", "确定");
        }
    }
}

关键注意事项

  • 安卓端需确保包含自定义Span的Label父容器设置InputTransparent="False",否则手势无法响应。
  • iOS端可通过添加UIStringAttributeKey.ForegroundColor等属性,自定义点击Span的文字样式。
  • 非安卓/iOS平台上,该Span会自动降级为普通Span,不具备手势功能。

内容的提问来源于stack exchange,提问作者Mehmet Sandıklı

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:25:03