自定义TagEditText在部分Vivo设备上输入异常求助
解决Vivo设备上TagEditText的输入异常问题
嘿,我碰到过类似的定制EditText在部分国产ROM上的兼容性问题,尤其是Vivo的系统在文本处理上确实有一些特殊的行为。咱们来一步步拆解你的问题并修复它:
问题根源分析
你描述的输入C时丢失B的情况,主要是因为TextWatcher的循环触发和文本格式化逻辑的边界处理不当,再加上Vivo设备对文本变更的回调时机更敏感,放大了这些问题:
- 在
afterTextChanged里调用format(),而format()里的setText()又会再次触发TextWatcher,导致多次重复修改文本,最终出现内容丢失。 lastString的判断逻辑不够严谨,无法准确区分用户输入和格式化后的文本变更。- 字符串分割和截取时的边界处理有漏洞,容易出现越界或错误删除内容的情况。
具体修复方案
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
相关产品推荐
相关产品推荐

