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

MAUI中Entry控件处理手机号格式化时粘贴+91开头号码触发IndexOutOfBoundsException异常问题排查

MAUI中Entry控件处理手机号格式化时粘贴+91开头号码触发IndexOutOfBoundsException异常问题排查

嘿,我之前也碰到过MAUI Entry做手机号格式化时,粘贴特定前缀号码崩溃的情况,先看看你遇到的异常:

Pending exception java.lang.IndexOutOfBoundsException: setSpan (15 ... 15) ends beyond length 12
(Throwable with no stack trace)
JNI DETECTED ERROR IN APPLICATION: JNI CallObjectMethodA called with pending exception java.lang.IndexOutOfBoundsException: setSpan (15 ... 15) ends beyond length 12
(Throwable with no stack trace)
in call to CallObjectMethodA
from void crc6477f0d89a9cfd64b1.EntryRendererBase_1.n_onTextChanged(java.lang.CharSequence, int, int, int)

这个问题本质是Android端EditText的内部文本处理冲突,咱们一步步来分析解决:

问题根源

当你在TextChanged事件里直接修改Entry的Text属性时,系统此时还在处理原文本的Span(用于光标定位、文本选中标记等)。尤其是粘贴操作会一次性替换大量文本,原文本的索引位置和新文本的长度不匹配,导致系统在更新Span时使用了旧的索引值,超出了新文本的有效长度范围,最终抛出这个越界异常。

可行解决方案

方案1:延迟执行格式化,避开同步冲突

不要在TextChanged事件里直接修改文本,而是用Dispatcher延迟执行格式化逻辑,让系统先完成当前的文本更新流程:

private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
    var entry = sender as Entry;
    if (entry == null) return;

    // 避免重复触发,先判断新旧文本是否一致
    if (e.NewTextValue == e.OldTextValue) return;

    // 用Dispatcher延迟执行,让系统先处理完内部文本更新
    Dispatcher.Dispatch(async () =>
    {
        await Task.Delay(10); // 短延迟确保系统完成处理
        var formattedText = FormatPhoneNumber(entry.Text);
        // 再次判断,防止循环触发TextChanged
        if (entry.Text != formattedText)
        {
            entry.Text = formattedText;
            // 将光标移到文本末尾,提升用户体验
            entry.CursorPosition = formattedText.Length;
        }
    });
}

// 你的手机号格式化方法示例
private string FormatPhoneNumber(string rawText)
{
    // 先过滤非数字和+的无效字符
    var cleanText = new string(rawText.Where(c => char.IsDigit(c) || c == '+').ToArray());
    if (string.IsNullOrEmpty(cleanText)) return "";

    if (cleanText.StartsWith("+91"))
    {
        // +91 12345 12345格式处理
        if (cleanText.Length > 3)
        {
            var part1 = cleanText.Substring(0, 3);
            var part2 = cleanText.Length > 8 ? cleanText.Substring(3, 5) : cleanText.Substring(3);
            var part3 = cleanText.Length > 8 ? cleanText.Substring(8) : "";
            return $"{part1} {part2} {part3}".Trim();
        }
        return cleanText;
    }
    else if (cleanText.StartsWith("+1"))
    {
        // +1 123 123 1234格式处理
        if (cleanText.Length > 2)
        {
            var part1 = cleanText.Substring(0, 2);
            var part2 = cleanText.Length > 5 ? cleanText.Substring(2, 3) : cleanText.Substring(2);
            var part3 = cleanText.Length > 8 ? cleanText.Substring(5, 3) : "";
            var part4 = cleanText.Length > 8 ? cleanText.Substring(8) : "";
            return $"{part1} {part2} {part3} {part4}".Trim();
        }
        return cleanText;
    }
    else
    {
        // 123 123 1234格式处理
        if (cleanText.Length > 3)
        {
            var part1 = cleanText.Substring(0, 3);
            var part2 = cleanText.Length > 6 ? cleanText.Substring(3, 3) : cleanText.Substring(3);
            var part3 = cleanText.Length > 6 ? cleanText.Substring(6) : "";
            return $"{part1} {part2} {part3}".Trim();
        }
        return cleanText;
    }
}

方案2:自定义Android Renderer,底层控制文本更新

如果方案1的延迟仍有问题,可以直接自定义Android端的Renderer,绕过MAUI的TextChanged事件,在EditText的TextWatcher里直接处理,从底层避免冲突:

[assembly: ExportRenderer(typeof(Entry), typeof(CustomPhoneEntryRenderer))]
namespace YourAppNamespace.Platforms.Android
{
    public class CustomPhoneEntryRenderer : EntryRenderer
    {
        private TextWatcher _textWatcher;

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

            if (Control != null)
            {
                // 移除默认的TextWatcher,避免冲突
                if (_textWatcher != null)
                {
                    Control.RemoveTextChangedListener(_textWatcher);
                }

                _textWatcher = new PhoneTextWatcher(Element);
                Control.AddTextChangedListener(_textWatcher);
                // 禁止系统文本自动纠正,避免干扰格式化
                Control.InputType = Android.Text.InputTypes.ClassPhone | Android.Text.InputTypes.TextFlagNoSuggestions;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (Control != null && _textWatcher != null)
            {
                Control.RemoveTextChangedListener(_textWatcher);
            }
            base.Dispose(disposing);
        }

        private class PhoneTextWatcher : Java.Lang.Object, ITextWatcher
        {
            private readonly Entry _entry;
            private bool _isUpdating;

            public PhoneTextWatcher(Entry entry)
            {
                _entry = entry;
            }

            public void AfterTextChanged(IEditable s)
            {
                if (_isUpdating) return;

                _isUpdating = true;
                var rawText = s.ToString();
                var formattedText = FormatPhoneNumber(rawText);
                // 直接修改Editable,避免触发额外的TextChanged事件
                s.Replace(0, s.Length(), formattedText);
                _isUpdating = false;
            }

            public void BeforeTextChanged(ICharSequence s, int start, int count, int after)
            {
                // 无需处理
            }

            public void OnTextChanged(ICharSequence s, int start, int before, int count)
            {
                // 无需处理
            }

            // 复用上面的FormatPhoneNumber方法,此处省略重复代码
            private string FormatPhoneNumber(string rawText)
            {
                var cleanText = new string(rawText.Where(c => char.IsDigit(c) || c == '+').ToArray());
                if (string.IsNullOrEmpty(cleanText)) return "";

                if (cleanText.StartsWith("+91"))
                {
                    if (cleanText.Length > 3)
                    {
                        var part1 = cleanText.Substring(0, 3);
                        var part2 = cleanText.Length > 8 ? cleanText.Substring(3, 5) : cleanText.Substring(3);
                        var part3 = cleanText.Length > 8 ? cleanText.Substring(8) : "";
                        return $"{part1} {part2} {part3}".Trim();
                    }
                    return cleanText;
                }
                // 其他格式处理逻辑...
                return cleanText;
            }
        }
    }
}

关键注意点

  • 避免循环触发:一定要判断格式化后的文本和当前文本是否一致,否则会无限触发TextChanged事件
  • 光标位置优化:修改文本后记得将光标移到末尾,否则用户输入体验会很差
  • 过滤无效字符:格式化前先过滤掉非数字和+的字符,避免无效文本导致的索引计算错误

备注:内容来源于stack exchange,提问作者Matthew Pans

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 07:49:30