将自定义 Editable 与 EditText 结合使用会使编辑时出现故障
Using custom Editable with EditText makes it glitchy while editing
注意: 我创建了一个 GitHub 存储库,其中包含此错误的重现 here. Feel free to clone and try the app out yourself to see the bug. The relevant code is here:注释部分保留在注释中它有效好吧,取消注释,你就会遇到这个错误。
我正在为 Android 构建源代码编辑器应用程序。我有一个包含 SpannableStringBuilder
的自定义 Editable
类型(此后将称为 SSB)。这是它的代码:
package com.bluejay.myapplication;
import android.text.Editable;
import android.text.InputFilter;
import android.text.SpannableStringBuilder;
public class ColoredText implements Editable {
private final SpannableStringBuilder builder;
public ColoredText(String rawText) {
assert rawText != null;
this.builder = new SpannableStringBuilder(rawText);
}
@Override
public Editable replace(int st, int en, CharSequence source, int start, int end) {
this.builder.replace(st, en, source, start, end);
return this;
}
@Override
public Editable replace(int st, int en, CharSequence text) {
this.builder.replace(st, en, text);
return this;
}
@Override
public Editable insert(int where, CharSequence text, int start, int end) {
this.builder.insert(where, text, start, end);
return this;
}
@Override
public Editable insert(int where, CharSequence text) {
this.builder.insert(where, text);
return this;
}
@Override
public Editable delete(int st, int en) {
this.builder.delete(st, en);
return this;
}
@Override
public Editable append(CharSequence text) {
this.builder.append(text);
return this;
}
@Override
public Editable append(CharSequence text, int start, int end) {
this.builder.append(text, start, end);
return this;
}
@Override
public Editable append(char text) {
this.builder.append(text);
return this;
}
@Override
public void clear() {
this.builder.clear();
}
@Override
public void clearSpans() {
this.builder.clearSpans();
}
@Override
public void setFilters(InputFilter[] filters) {
this.builder.setFilters(filters);
}
@Override
public InputFilter[] getFilters() {
return this.builder.getFilters();
}
@Override
public void getChars(int start, int end, char[] dest, int destoff) {
this.builder.getChars(start, end, dest, destoff);
}
@Override
public void setSpan(Object what, int start, int end, int flags) {
this.builder.setSpan(what, start, end, flags);
}
@Override
public void removeSpan(Object what) {
this.builder.removeSpan(what);
}
@Override
public <T> T[] getSpans(int start, int end, Class<T> type) {
return this.builder.getSpans(start, end, type);
}
@Override
public int getSpanStart(Object tag) {
return this.builder.getSpanStart(tag);
}
@Override
public int getSpanEnd(Object tag) {
return this.builder.getSpanEnd(tag);
}
@Override
public int getSpanFlags(Object tag) {
return this.builder.getSpanFlags(tag);
}
@Override
public int nextSpanTransition(int start, int limit, Class type) {
return this.builder.nextSpanTransition(start, limit, type);
}
@Override
public int length() {
return this.builder.length();
}
@Override
public char charAt(int index) {
return this.builder.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return this.builder.subSequence(start, end);
}
}
如您所见,此类型是 SSB 的简单包装器。 new ColoredText(str)
从 str
及其所有方法调用(append
、delete
等除外,其中 return this
而不是SSB) 只需转发给 SSB。
现在,当我有一个 EditText
时,我尝试将 ColoredText
设置为 EditText
的基础文本,就像这样
EditText editText = (EditText) findViewById(R.id.editText);
// By default, setText() will attempt to copy the passed CharSequence into a new SSB.
// See https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L4396
// and https://github.com/android/platform_frameworks_base/blob/master/core/java/android/text/Editable.java#L143
// I want to prevent this and have the ColoredText instead of an SSB be the EditText's
// underlying text, that is, I want the mText member to be of type ColoredText.
editText.setEditableFactory(new Editable.Factory() {
@Override
public Editable newEditable(CharSequence source) {
return (Editable) source; // source is ColoredText
}
});
ColoredText text = new ColoredText("Hello world!\nHello world again!");
editText.setText(text, TextView.BufferType.EDITABLE);
EditText
在编辑时会出现很多问题。在上面的示例中,点击带有 Hello world!
的第一行的任意位置并开始输入随机字符。第二行将受到影响,并且不知何故(即使您没有触摸换行键或箭头键)光标最终会溢出到第二行。并且您键入的某些字符可能不会显示,即使光标会移动。
现在,如果您注释掉 setEditableFactory
部分,那么文本会在 setText()
期间被复制到 SSB 中,然后您再次 运行 应用程序,您将看到没有故障。
如果您保留 setEditableFactory
部分不变,但将 text
的变量初始化替换为
,它甚至可以工作
SpannableStringBuilder text = new SpannableStringBuilder("Hello world!\nHello world again!");
很明显,虽然 setText()
说它会接受任何 Editable
,但它在处理 SSB 以外的任何东西时效果不佳。为什么会发生这种情况,我该如何解决?谢谢
通过挖掘SpannableStringBuilder
的源代码,我发现它不仅完成了接口Editable
等定义的职责,而且还通过调用[=12=报告了跨度变化] 通过 this
。 DynamicLayout
(EditText
的真正主力)通过检查传入引用与其成员(这是我们实际的 ColoredSpan
实例)是否相等来响应 onSpanChanged()
。显然它们是不同的,我怀疑这是一个问题。
其实SpannableStringBuilder
不只是Editable
,而是更多。如果您需要自定义 Editable
子类化 SpannableStringBuilder
可能有效。
注意: 我创建了一个 GitHub 存储库,其中包含此错误的重现 here. Feel free to clone and try the app out yourself to see the bug. The relevant code is here:注释部分保留在注释中它有效好吧,取消注释,你就会遇到这个错误。
我正在为 Android 构建源代码编辑器应用程序。我有一个包含 SpannableStringBuilder
的自定义 Editable
类型(此后将称为 SSB)。这是它的代码:
package com.bluejay.myapplication;
import android.text.Editable;
import android.text.InputFilter;
import android.text.SpannableStringBuilder;
public class ColoredText implements Editable {
private final SpannableStringBuilder builder;
public ColoredText(String rawText) {
assert rawText != null;
this.builder = new SpannableStringBuilder(rawText);
}
@Override
public Editable replace(int st, int en, CharSequence source, int start, int end) {
this.builder.replace(st, en, source, start, end);
return this;
}
@Override
public Editable replace(int st, int en, CharSequence text) {
this.builder.replace(st, en, text);
return this;
}
@Override
public Editable insert(int where, CharSequence text, int start, int end) {
this.builder.insert(where, text, start, end);
return this;
}
@Override
public Editable insert(int where, CharSequence text) {
this.builder.insert(where, text);
return this;
}
@Override
public Editable delete(int st, int en) {
this.builder.delete(st, en);
return this;
}
@Override
public Editable append(CharSequence text) {
this.builder.append(text);
return this;
}
@Override
public Editable append(CharSequence text, int start, int end) {
this.builder.append(text, start, end);
return this;
}
@Override
public Editable append(char text) {
this.builder.append(text);
return this;
}
@Override
public void clear() {
this.builder.clear();
}
@Override
public void clearSpans() {
this.builder.clearSpans();
}
@Override
public void setFilters(InputFilter[] filters) {
this.builder.setFilters(filters);
}
@Override
public InputFilter[] getFilters() {
return this.builder.getFilters();
}
@Override
public void getChars(int start, int end, char[] dest, int destoff) {
this.builder.getChars(start, end, dest, destoff);
}
@Override
public void setSpan(Object what, int start, int end, int flags) {
this.builder.setSpan(what, start, end, flags);
}
@Override
public void removeSpan(Object what) {
this.builder.removeSpan(what);
}
@Override
public <T> T[] getSpans(int start, int end, Class<T> type) {
return this.builder.getSpans(start, end, type);
}
@Override
public int getSpanStart(Object tag) {
return this.builder.getSpanStart(tag);
}
@Override
public int getSpanEnd(Object tag) {
return this.builder.getSpanEnd(tag);
}
@Override
public int getSpanFlags(Object tag) {
return this.builder.getSpanFlags(tag);
}
@Override
public int nextSpanTransition(int start, int limit, Class type) {
return this.builder.nextSpanTransition(start, limit, type);
}
@Override
public int length() {
return this.builder.length();
}
@Override
public char charAt(int index) {
return this.builder.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return this.builder.subSequence(start, end);
}
}
如您所见,此类型是 SSB 的简单包装器。 new ColoredText(str)
从 str
及其所有方法调用(append
、delete
等除外,其中 return this
而不是SSB) 只需转发给 SSB。
现在,当我有一个 EditText
时,我尝试将 ColoredText
设置为 EditText
的基础文本,就像这样
EditText editText = (EditText) findViewById(R.id.editText);
// By default, setText() will attempt to copy the passed CharSequence into a new SSB.
// See https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L4396
// and https://github.com/android/platform_frameworks_base/blob/master/core/java/android/text/Editable.java#L143
// I want to prevent this and have the ColoredText instead of an SSB be the EditText's
// underlying text, that is, I want the mText member to be of type ColoredText.
editText.setEditableFactory(new Editable.Factory() {
@Override
public Editable newEditable(CharSequence source) {
return (Editable) source; // source is ColoredText
}
});
ColoredText text = new ColoredText("Hello world!\nHello world again!");
editText.setText(text, TextView.BufferType.EDITABLE);
EditText
在编辑时会出现很多问题。在上面的示例中,点击带有 Hello world!
的第一行的任意位置并开始输入随机字符。第二行将受到影响,并且不知何故(即使您没有触摸换行键或箭头键)光标最终会溢出到第二行。并且您键入的某些字符可能不会显示,即使光标会移动。
现在,如果您注释掉 setEditableFactory
部分,那么文本会在 setText()
期间被复制到 SSB 中,然后您再次 运行 应用程序,您将看到没有故障。
如果您保留 setEditableFactory
部分不变,但将 text
的变量初始化替换为
SpannableStringBuilder text = new SpannableStringBuilder("Hello world!\nHello world again!");
很明显,虽然 setText()
说它会接受任何 Editable
,但它在处理 SSB 以外的任何东西时效果不佳。为什么会发生这种情况,我该如何解决?谢谢
通过挖掘SpannableStringBuilder
的源代码,我发现它不仅完成了接口Editable
等定义的职责,而且还通过调用[=12=报告了跨度变化] 通过 this
。 DynamicLayout
(EditText
的真正主力)通过检查传入引用与其成员(这是我们实际的 ColoredSpan
实例)是否相等来响应 onSpanChanged()
。显然它们是不同的,我怀疑这是一个问题。
其实SpannableStringBuilder
不只是Editable
,而是更多。如果您需要自定义 Editable
子类化 SpannableStringBuilder
可能有效。