日志
java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0
at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1330)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:684)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:676)
at android.text.Selection.setSelection(Selection.java:94)
at android.text.Selection.setSelection(Selection.java:78)
at android.text.Selection.setSelection(Selection.java:153)
at android.widget.TextView.onDragEvent(TextView.java:12932)
at android.view.View.callDragEventHandler(View.java:26953)
at android.view.View.dispatchDragEvent(View.java:26941)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:1839)
at android.view.ViewRootImpl.handleDragEvent(ViewRootImpl.java:7430)
at android.view.ViewRootImpl.access$1200(ViewRootImpl.java:203)
at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:5248)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:7879)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
前提条件
界面中有两个EditText,且都有内容,两个EditText都设置了OnFocusChangeListener,且在获取到焦点的时候,去改变EditText的typeface(特殊业务逻辑)。
复现路径
长按其中一个EditText,选中一段文字,再长按选中的文字,拖动文字到另外一个EditText,此时就会崩溃。
崩溃原因
看一下崩溃最后的地方的代码
// android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1330)
private void checkRange(final String operation, int start, int end) {
if (end < start) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " has end before start");
}
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " ends beyond length " + len);
}
if (start < 0 || end < 0) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " starts before 0");
}
}
应该是传入的start或者end小于0了,那么往上翻一下start和end是哪来的
// android.text.Selection.setSelection(Selection.java:153)
public static final void setSelection(Spannable text, int index) {
setSelection(text, index, index);
}
这里的start和end都传入的index,再往上看看index哪来的
// android.widget.TextView.onDragEvent(TextView.java:12932)
public boolean onDragEvent(DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return mEditor != null && mEditor.hasInsertionController();
case DragEvent.ACTION_DRAG_ENTERED:
TextView.this.requestFocus();
return true;
case DragEvent.ACTION_DRAG_LOCATION:
if (mText instanceof Spannable) {
// 这里的offset就是传入的index
final int offset = getOffsetForPosition(event.getX(), event.getY());
Selection.setSelection(mSpannable, offset);
}
return true;
case DragEvent.ACTION_DROP:
if (mEditor != null) mEditor.onDrop(event);
return true;
case DragEvent.ACTION_DRAG_ENDED:
case DragEvent.ACTION_DRAG_EXITED:
default:
return true;
}
}
再看看getOffsetForPosition方法
// android.widget.TextView.getOffsetForPosition
public int getOffsetForPosition(float x, float y) {
if (getLayout() == null) return -1;
final int line = getLineAtCoordinate(y);
final int offset = getOffsetAtCoordinate(line, x);
return offset;
}
这里判断了一下layout是否为空,为空则返回-1,那应该就是layout为空了,看下设置typeface的方法
// android.widget.TextView.setTypeface
public void setTypeface(@Nullable Typeface tf) {
if (mTextPaint.getTypeface() != tf) {
mTextPaint.setTypeface(tf);
if (mLayout != null) {
nullLayouts();
requestLayout();
invalidate();
}
}
}
这里貌似设置typeface后把layout置空了,看下nullLayouts方法
// android.widget.TextView.nullLayouts
public void nullLayouts() {
if (mLayout instanceof BoringLayout && mSavedLayout == null) {
mSavedLayout = (BoringLayout) mLayout;
}
if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
mSavedHintLayout = (BoringLayout) mHintLayout;
}
// 将layout置空了
mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
mBoring = mHintBoring = null;
// Since it depends on the value of mLayout
if (mEditor != null) mEditor.prepareCursorControllers();
}
最后真相大白了,其实就是在EditText获取到焦点的时候设置了typeface,然后TextView此时将Layout置空了,导致最后setSelection拿到的index==-1,抛出了异常。
解决方法
只需要去掉EditText获取到焦点时设置字体的逻辑就行了。