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

在Xamarin MVVMCross中为iOS/Android通过MvxValueConverter绑定富文本点击命令

iOS 平台实现方案

1. 替换显示控件为 UITextView

UILabel默认不支持富文本点击交互,将TableViewCell中的文本显示控件替换为UITextView,并配置为类Label样式:

_textView = new UITextView();
_textView.Editable = false;       // 禁止编辑
_textView.Selectable = true;      // 允许选择(支持链接点击)
_textView.ScrollEnabled = false;  // 禁止滚动,保持Label外观
_textView.BackgroundColor = UIColor.Clear; // 透明背景

2. 扩展富文本转换器,添加链接标识

修改你的FreeShippingConverter,为「Perks Members」添加自定义链接标识(用自定义URL scheme区分),同时设置样式:

public class FreeShippingConverter : MvxValueConverter<string, NSAttributedString>
{
    protected override NSAttributedString Convert(string value, Type targetType, object parameter, CultureInfo culture)
    {
        var mutableAttrStr = new NSMutableAttributedString(value);
        var targetRange = mutableAttrStr.MutableString.LocalizedStandardRangeOfString("Perks Members");
        
        if (targetRange.Location != NSNotFound)
        {
            // 添加自定义链接标识
            mutableAttrStr.AddAttribute(NSAttributedStringKey.Link, new NSUrl("perks://members"), targetRange);
            // 设置链接样式
            mutableAttrStr.AddAttribute(NSAttributedStringKey.ForegroundColor, UIColor.SystemBlueColor, targetRange);
            mutableAttrStr.AddAttribute(NSAttributedStringKey.UnderlineStyle, NSNumber.FromInt32((int)NSUnderlineStyle.Single), targetRange);
        }
        
        return mutableAttrStr;
    }
}

3. 处理链接点击,绑定ViewModel命令

为UITextView设置自定义Delegate,拦截链接点击事件并触发ViewModel中的Command:

public class CustomTableViewCell : MvxTableViewCell
{
    public ICommand PerksMembersTappedCommand { get; set; }
    private UITextView _textView;

    public CustomTableViewCell(IntPtr handle) : base(handle)
    {
        // 初始化_textView(省略布局约束)
        _textView.Delegate = new PerksLinkTextViewDelegate(this);
        
        this.DelayBind(() =>
        {
            var bindingSet = this.CreateBindingSet<CustomTableViewCell, YourCellViewModel>();
            // 绑定富文本
            bindingSet.Bind(_textView).For(v => v.AttributedText)
                      .To(vm => vm.ShippingInfoText)
                      .WithConversion<FreeShippingConverter>();
            // 绑定点击命令
            bindingSet.Bind(this).For(v => v.PerksMembersTappedCommand)
                      .To(vm => vm.PerksMembersTappedCommand);
            bindingSet.Apply();
        });
    }

    private class PerksLinkTextViewDelegate : UITextViewDelegate
    {
        private readonly CustomTableViewCell _cell;

        public PerksLinkTextViewDelegate(CustomTableViewCell cell) => _cell = cell;

        public override bool ShouldInteractWith(UITextView textView, NSUrl url, NSRange characterRange)
        {
            if (url.AbsoluteString.Equals("perks://members", StringComparison.OrdinalIgnoreCase))
            {
                _cell.PerksMembersTappedCommand?.Execute(null);
                return false; // 阻止系统默认跳转行为
            }
            return true;
        }
    }
}

Android 平台实现方案

1. 创建可绑定的ClickableSpan

自定义ClickableSpan,持有ViewModel的Command引用,点击时触发命令:

public class CommandClickableSpan : ClickableSpan
{
    private readonly ICommand _command;
    private readonly Color _linkColor;

    public CommandClickableSpan(ICommand command, Color linkColor)
    {
        _command = command;
        _linkColor = linkColor;
    }

    public override void OnClick(View widget)
    {
        _command?.Execute(null);
    }

    public override void UpdateDrawState(TextPaint ds)
    {
        base.UpdateDrawState(ds);
        ds.Color = _linkColor;
        ds.UnderlineText = true; // 显示下划线标识链接
    }
}

2. 自定义绑定(推荐方案)

创建自定义绑定,直接将TextView的链接点击与ViewModel命令关联,无需修改原有转换器:

第一步:实现自定义绑定类

public class TextViewPerksLinkBinding : MvxAndroidTargetBinding
{
    private readonly TextView _textView;
    private ICommand _tappedCommand;

    public TextViewPerksLinkBinding(TextView textView) : base(textView)
    {
        _textView = textView;
        // 启用链接交互
        _textView.MovementMethod = LinkMovementMethod.Instance;
        // 移除点击高亮背景
        _textView.HighlightColor = Color.Transparent;
    }

    public override Type TargetType => typeof(ICommand);

    protected override void SetValueImpl(object target, object value)
    {
        _tappedCommand = value as ICommand;
        RefreshLinkSpan();
    }

    // 监听文本变化,自动更新链接Span
    public override void SubscribeToEvents()
    {
        _textView.TextChanged += TextView_TextChanged;
    }

    private void TextView_TextChanged(object sender, Android.Text.TextChangedEventArgs e)
    {
        RefreshLinkSpan();
    }

    private void RefreshLinkSpan()
    {
        var text = _textView.Text;
        if (string.IsNullOrEmpty(text)) return;

        var spannable = new SpannableString(text);
        var startIndex = text.IndexOf("Perks Members", StringComparison.OrdinalIgnoreCase);
        
        if (startIndex != -1)
        {
            var endIndex = startIndex + "Perks Members".Length;
            // 移除旧的Span,避免重复添加
            var oldSpans = spannable.GetSpans(0, spannable.Length, Java.Lang.Class.FromType(typeof(ClickableSpan)));
            foreach (var span in oldSpans)
            {
                spannable.RemoveSpan(span);
            }
            // 添加新的可点击Span
            spannable.SetSpan(new CommandClickableSpan(_tappedCommand, Color.ParseColor("#007AFF")), 
                              startIndex, endIndex, SpanTypes.ExclusiveExclusive);
            _textView.TextFormatted = spannable;
        }
    }

    protected override void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            _textView.TextChanged -= TextView_TextChanged;
        }
        base.Dispose(isDisposing);
    }

    public override MvxBindingMode DefaultMode => MvxBindingMode.OneWay;
}

第二步:注册自定义绑定

在Setup类中添加绑定注册:

protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
    base.FillTargetFactories(registry);
    registry.RegisterCustomBindingFactory<TextView>("PerksMembersCommand", 
                                                    textView => new TextViewPerksLinkBinding(textView));
}

第三步:布局中绑定

在RecyclerView的Item布局axml中,同时绑定文本和命令:

<TextView
    android:id="@+id/tv_shipping_info"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    local:MvxBind="Text ShippingInfoText; PerksMembersCommand PerksMembersTappedCommand" />

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 06:16:13