Android 6.0+ 上关于可编辑问题的多个 ForegroundColorSpan

Multiple ForegroundColorSpan on Editable issue on Android 6.0+

如果我在 Editable 上设置多个跨度,则使用最后一个 ForegroundColorSpan。在我看来,这是预期的行为。在 Android 6.0+ 上,最新设置的跨度不优先。


下面是一个完整的工作示例,突出显示 EditText 中的数字,然后是字符串:

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Create the EditText view
    EditText editText = new EditText(this);
    editText.setLayoutParams(new ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    editText.setBackground(null);
    editText.setGravity(Gravity.TOP);

    // Patterns to find numbers and strings
    final Pattern[] patterns = {
        Pattern.compile("\b(\d*[.]?\d+)\b"), // numbers
        Pattern.compile("\".*?\"|'.*?'"), // strings
    };

    // Colors for numbers and strings
    final int[] colors = {
        Color.RED,
        Color.BLUE
    };

    // Add a TextWatcher to highlight numbers and strings
    editText.addTextChangedListener(new TextWatcher() {

      @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {

      }

      @Override public void onTextChanged(CharSequence s, int start, int before, int count) {

      }

      @Override public void afterTextChanged(Editable editable) {

        // remove all spans before highlighting new text
        ForegroundColorSpan[] spans = editable.getSpans(0, editable.length(), ForegroundColorSpan.class);
        for (ForegroundColorSpan span : spans) {
          editable.removeSpan(span);
        }

        // first, highlight numbers, next strings
        for (int i = 0; i < patterns.length; i++) {
          Pattern p = patterns[i];
          Matcher m = p.matcher(editable);
          while (m.find()) {
            editable.setSpan(new ForegroundColorSpan(colors[i]), m.start(), m.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            // print match
            System.out.println(editable.subSequence(m.start(), m.end()));
          }
        }

        // PROBLEM:
        // On Android 6.0+ numbers are highlighted within the string.
        // This is not expected behavior. It worked prior to Android 6.0
      }
    });

    // set some text for testing purposes
    editText.setText("foo \"bar\" baz \"quz 16\"");

    setContentView(editText);
  }

}

如果我启动这个 Activity 并输入文本

foo "bar" baz "qux 16"

我希望 "bar" 和 "qux 16" 都是蓝色的。然而,在 Android 6.0+ 16 上是红色的。


截图如下:

Android 5.1.1(预期行为)

Android6.0.1

请注意 16 在第二个屏幕截图中是如何突出显示的。


问题:

为什么第一个颜色跨度覆盖 Android 6.0 上的最后一个颜色跨度,我该如何解决这个问题?

如果我在设置新 ForegroundColorSpan 之前删除所有跨度,那么我会得到所需的行为。不幸的是,如果 EditText 有很多文本,这会增加很多工作量。

我做了什么修复:

@Override public void afterTextChanged(Editable editable) {
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
    // remove old spans before highlighting
    // this does not work on Android 6.0+
    ForegroundColorSpan[] spans = editable.getSpans(0, editable.length(), ForegroundColorSpan.class);
    for (ForegroundColorSpan span : spans) {
      editable.removeSpan(span);
    }
  }

  for (int i = 0; i < patterns.length; i++) {
    Pattern p = patterns[i];
    Matcher m = p.matcher(editable);
    while (m.find()) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Remove any spans in the given range.
        ForegroundColorSpan[] oldspans = editable.getSpans(m.start(), m.end(), ForegroundColorSpan.class);
        for (ForegroundColorSpan span : oldspans) {
          editable.removeSpan(span);
        }
      }
      editable.setSpan(new ForegroundColorSpan(colors[i]), m.start(), m.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
  }
}

我不确定 Android 6.0+ 中发生了什么变化,如果有人有任何见解,我们将不胜感激。