Xamarin自定义Label中仅哈希标签与提及用户子串可点击需求实现
Hey there! The issue with your current renderers is that you're using MatchOptions.All (Android) and UIDataDetectorType.All (iOS), which automatically links every possible type of content like phone numbers, URLs, etc. To limit this only to hashtags (#hashtag) and mentions (@username), we need to handle the clickable spans manually instead of relying on the system's automatic detection.
Let's adjust both renderers step by step:
Android Renderer Adjustment
Replace your existing BodyLabelAndroid code with this version. We'll use SpannableString to identify and make only hashtags/mentions clickable, then wire up the click events to your desired methods:
[assembly: ExportRenderer(typeof(BodyLabel), typeof(BodyLabelAndroid))] namespace SocialNetwork.Droid.Renderers { public class BodyLabelAndroid : LabelRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Label> e) { base.OnElementChanged(e); var view = (BodyLabel)Element; if (view == null) return; TextView textView = new TextView(Forms.Context) { LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent), TextColor = view.TextColor.ToAndroid(), TextSize = ComplexUnitType.Dip, Text = view.Text, AutoLinkMask = MatchOptions.None // Disable all automatic linking }; textView.SetTextSize(ComplexUnitType.Dip, (float)view.FontSize); // Create a SpannableString to apply clickable spans var spannable = new SpannableString(view.Text); // Regex pattern to match hashtags (# followed by letters/numbers) var hashtagPattern = new Regex(@"\#\w+"); foreach (Match match in hashtagPattern.Matches(view.Text)) { spannable.SetSpan(new ClickableSpanImplementation(() => { // Call your OpenHashtag method here ((BodyLabel)Element).OpenHashtag(match.Value); }), match.Index, match.Index + match.Length, SpanTypes.ExclusiveExclusive); } // Regex pattern to match mentions (@ followed by letters/numbers) var mentionPattern = new Regex(@"\@\w+"); foreach (Match match in mentionPattern.Matches(view.Text)) { spannable.SetSpan(new ClickableSpanImplementation(() => { // Call your ViewProfile method here ((BodyLabel)Element).ViewProfile(match.Value); }), match.Index, match.Index + match.Length, SpanTypes.ExclusiveExclusive); } textView.MovementMethod = LinkMovementMethod.Instance; textView.TextFormatted = spannable; SetNativeControl(textView); } // Custom ClickableSpan to handle the click action private class ClickableSpanImplementation : ClickableSpan { private readonly Action _onClick; public ClickableSpanImplementation(Action onClick) { _onClick = onClick; } public override void OnClick(View widget) { _onClick?.Invoke(); } // Optional: Customize the link appearance (e.g., color, underline) public override void UpdateDrawState(TextPaint ds) { base.UpdateDrawState(ds); ds.Color = Android.Graphics.Color.Blue; // Set your desired link color ds.UnderlineText = true; // Remove this if you don't want underlines } } } }
iOS Renderer Adjustment
For iOS, we'll replace the UITextView with a custom setup using NSAttributedString and a tap gesture recognizer to detect clicks on hashtags/mentions:
[assembly: ExportRenderer(typeof(BodyLabel), typeof(BodyLabeliOS))] namespace SocialNetwork.iOS.Renderers { public class BodyLabeliOS : ViewRenderer<BodyLabel, UITextView> { protected override void OnElementChanged(ElementChangedEventArgs<BodyLabel> e) { base.OnElementChanged(e); if (Element == null) return; var textView = new UITextView(new CGRect(0, 0, Element.Width, Element.Height)) { Text = Element.Text, Font = UIFont.SystemFontOfSize((float)Element.FontSize), Editable = false, DataDetectorTypes = UIDataDetectorType.None, // Disable automatic detection BackgroundColor = UIColor.Clear }; // Add tap gesture recognizer to detect clicks var tapGesture = new UITapGestureRecognizer(HandleTap); textView.AddGestureRecognizer(tapGesture); // Optional: Style hashtags/mentions to look like links var attributedText = new NSMutableAttributedString(Element.Text); var hashtagPattern = new NSRegularExpression(@"#\w+", NSRegularExpressionOptions.CaseInsensitive, out _); hashtagPattern.EnumerateMatches(Element.Text, NSMatchingOptions.ReportProgress, new NSRange(0, Element.Text.Length), (match, _, __) => { if (match != null) { attributedText.AddAttribute(UIStringAttributeKey.ForegroundColor, UIColor.Blue, match.Range); attributedText.AddAttribute(UIStringAttributeKey.UnderlineStyle, NSNumber.FromInt32((int)NSUnderlineStyle.Single), match.Range); } }); var mentionPattern = new NSRegularExpression(@"@\w+", NSRegularExpressionOptions.CaseInsensitive, out _); mentionPattern.EnumerateMatches(Element.Text, NSMatchingOptions.ReportProgress, new NSRange(0, Element.Text.Length), (match, _, __) => { if (match != null) { attributedText.AddAttribute(UIStringAttributeKey.ForegroundColor, UIColor.Blue, match.Range); attributedText.AddAttribute(UIStringAttributeKey.UnderlineStyle, NSNumber.FromInt32((int)NSUnderlineStyle.Single), match.Range); } }); textView.AttributedText = attributedText; SetNativeControl(textView); } private void HandleTap(UITapGestureRecognizer gesture) { if (gesture.State != UIGestureRecognizerState.Ended) return; var textView = (UITextView)gesture.View; var location = gesture.LocationInView(textView); var position = textView.GetPosition(textView.BeginningOfDocument, location); if (position == null) return; var range = textView.GetWordRange(position, UITextGranularity.Word); var clickedText = textView.Text.Substring((int)range.Location, (int)range.Length); // Check if the clicked text is a hashtag or mention if (clickedText.StartsWith("#")) { Element.OpenHashtag(clickedText); } else if (clickedText.StartsWith("@")) { Element.ViewProfile(clickedText); } } } }
Key Notes:
- We disabled the system's automatic link detection (
MatchOptions.Noneon Android,UIDataDetectorType.Noneon iOS) to take full control. - Used regex patterns to identify hashtags (
#\w+) and mentions (@\w+) - you can adjust these patterns if you need to support special characters in your hashtags/mentions. - Added custom styling for clickable text (blue color, underline) to make them visually distinct - feel free to tweak this to match your app's design.
- Make sure your
BodyLabelcustom control has theOpenHashtag(string)andViewProfile(string)methods defined so the renderers can call them.
内容的提问来源于stack exchange,提问作者Dan

