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

自定义TagEditText在部分Vivo设备上输入异常求助

解决Vivo设备上TagEditText的输入异常问题

嘿,我碰到过类似的定制EditText在部分国产ROM上的兼容性问题,尤其是Vivo的系统在文本处理上确实有一些特殊的行为。咱们来一步步拆解你的问题并修复它:

问题根源分析

你描述的输入C时丢失B的情况,主要是因为TextWatcher的循环触发和文本格式化逻辑的边界处理不当,再加上Vivo设备对文本变更的回调时机更敏感,放大了这些问题:

  1. 在afterTextChanged里调用format(),而format()里的setText()又会再次触发TextWatcher,导致多次重复修改文本,最终出现内容丢失。
  2. lastString的判断逻辑不够严谨,无法准确区分用户输入和格式化后的文本变更。
  3. 字符串分割和截取时的边界处理有漏洞,容易出现越界或错误删除内容的情况。

具体修复方案

1. 防止TextWatcher循环触发

添加一个标志位,并且在格式化前临时移除监听,避免setText()再次触发回调:

// 在TagEditText类中添加一个标志位
private boolean isFormatting = false;

@Override
public void afterTextChanged(Editable s) {
    Log.i("CharSequence2", s.toString());
    String thisString = s.toString();
    
    // 正在格式化时直接返回,避免循环触发
    if (isFormatting) {
        return;
    }
    
    if (thisString.length() > 0 && !thisString.equals(lastString)) {
        isFormatting = true;
        format();
        isFormatting = false;
    }
}

private void format() {
    // 先移除监听,避免setText触发循环修改
    removeTextChangedListener(textWatcher);
    
    SpannableStringBuilder sb = new SpannableStringBuilder();
    String fullString = getText().toString();
    String[] strings = fullString.split(separator);
    boolean endsWithSeparator = fullString.endsWith(separator);
    
    for (int i = 0; i < strings.length; i++) {
        String string = strings[i];
        // 跳过空字符串,避免生成无效的空Tag
        if (string.isEmpty()) {
            continue;
        }
        sb.append(string);
        
        // 只有非最后一个元素,或者最后一个元素且文本以分隔符结尾时,才添加Tag样式
        if ((i < strings.length - 1) || endsWithSeparator) {
            BitmapDrawable bd = (BitmapDrawable) convertViewToDrawable(createTokenView(string));
            bd.setBounds(0, 0, bd.getIntrinsicWidth(), bd.getIntrinsicHeight());
            int startIdx = sb.length() - string.length();
            int endIdx = sb.length();
            
            // 设置Tag的图片Span
            sb.setSpan(new ImageSpan(bd), startIdx, endIdx, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            // 设置点击Span,覆盖整个Tag范围
            MyClickableSpan myClickableSpan = new MyClickableSpan(startIdx, endIdx);
            sb.setSpan(myClickableSpan, startIdx, endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            
            // 添加分隔符(避免重复添加)
            if ((i < strings.length - 1) || endsWithSeparator) {
                sb.append(separator);
            }
        }
    }
    
    lastString = sb.toString();
    setText(sb);
    setSelection(sb.length());
    
    // 重新添加监听,恢复正常输入监听
    addTextChangedListener(textWatcher);
}

2. 修复Tag删除的边界问题

你的MyClickableSpan里的字符串截取容易出现越界,调整为更安全的处理方式:

private class MyClickableSpan extends ClickableSpan {
    int startIdx;
    int endIdx;

    public MyClickableSpan(int startIdx, int endIdx) {
        super();
        this.startIdx = startIdx;
        this.endIdx = endIdx;
    }

    @Override
    public void onClick(View widget) {
        String s = getText().toString();
        // 安全截取前后字符串,避免越界
        String s1 = startIdx > 0 ? s.substring(0, startIdx) : "";
        String s2 = endIdx < s.length() ? s.substring(endIdx) : "";
        
        // 处理前后字符串都包含分隔符的情况,避免出现连续空格
        if (!s1.isEmpty() && !s2.isEmpty() && s1.endsWith(separator) && s2.startsWith(separator)) {
            s2 = s2.substring(1);
        }
        
        TagEditText.this.setText(s1 + s2);
        // 确保光标始终在文本末尾
        setSelection(getText().length());
    }
}

3. 优化View转Drawable的性能(可选但推荐)

你的convertViewToDrawable方法存在重复创建Bitmap的问题,调整为更高效的方式,同时避免内存泄漏:

public Object convertViewToDrawable(View view) {
    view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    
    Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);
    
    // 不需要使用DrawingCache,直接绘制更可靠
    return new BitmapDrawable(getContext().getResources(), bitmap);
}

为什么这些修改能解决Vivo设备的问题?

  • Vivo的系统输入法对文本变更的回调时机和次数与原生Android有差异,移除监听再添加可以彻底避免循环触发导致的文本错乱。
  • 标志位isFormatting确保格式化过程中不会响应新的文本变更请求,避免重复修改。
  • 更严谨的边界处理解决了字符串截取和分割时的漏洞,防止意外删除内容。
  • 优化后的View转Drawable逻辑减少了Bitmap的创建开销,在Vivo这类对性能敏感的设备上更稳定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:10:06