I have a custom edit text named MyEditText, I want to add the auto closing bracket feature, so when I type "[", I close that bracket automatically and put the cursor between the brackets '[|]'. This is my code:
MyEditText:
public class MyEditText extends AppCompatEditText {
public static final String CURSOR = "\u2622";
private final TextWatcher bracketWatcher = new TextWatcher() {
private int start;
private int count;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
this.start = start;
this.count = count;
}
@Override
public void afterTextChanged(Editable editable) {
//remove cursor character and put real cursor instead: [☢] -> [|]
if (editable.length() > start && count > 1) {
CharSequence newText = editable.subSequence(start, start + count);
int i = newText.toString().indexOf(CURSOR);
if (i > -1) {
editable.delete(start + i, start + i + 1);
setSelection(start);
}
}
}
};
private void init() {
setFilters(new InputFilter[]{inputFilter});
addTextChangedListener(bracketWatcher);
}
private CharSequence addBracket(CharSequence source, int start) {
if (source.charAt(start) == '[') {
return "[" + CURSOR + "]";
}
return source;
}
private final InputFilter inputFilter = (source, start, end, dest, dstart, dend) -> {
try {
if (end - start == 1 && start < source.length()) {
return addBracket(source, start);
}
} catch (Exception e) {
e.printStackTrace();
}
return source;
};
public MyEditText(Context context) {
super(context);
init();
}
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyEditText editorView = findViewById(R.id.my_edit_text);
String text = "line 1\n" +
"line 2\n" +
"line 3";
editorView.setText(text);
}
}
main_activity.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.abdo.stackoverflowquestionioob.MyEditText
android:id="@+id/my_edit_text"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="start|top"
android:hint="Write text here"
android:layout_margin="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
In most cases, this code works! , but has a serious bug, which occurs only in a special case: When the app has just opened, I put the cursor at the beginning of the edit text (position = 0) and then click the bracket "[" (image 1).
Note that if I put the cursor in another position (position != 0) and move it to position 0 (image 2), this error will not occur!, it ONLY occurs and ONLY in the case I mentioned above.
Stack trace:
java.lang.IndexOutOfBoundsException: charAt: 22 >= length 22
at android.text.SpannableStringBuilder.charAt(SpannableStringBuilder.java:124)
at android.text.TextLine.handleText(TextLine.java:883)
at android.text.TextLine.handleRun(TextLine.java:1125)
at android.text.TextLine.drawRun(TextLine.java:491)
at android.text.TextLine.draw(TextLine.java:286)
at android.text.Layout.drawText(Layout.java:588)
at android.widget.Editor.drawHardwareAcceleratedInner(Editor.java:2167)
at android.widget.Editor.drawHardwareAccelerated(Editor.java:2086)
at android.widget.Editor.onDraw(Editor.java:2026)
at android.widget.TextView.onDraw(TextView.java:8754)
at android.view.View.draw(View.java:23191)
at android.view.View.updateDisplayListIfDirty(View.java:22066)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22021)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22021)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22021)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22021)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22021)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22021)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:588)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:594)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:667)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:4264)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4048)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3321)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2201)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9000)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:996)
at android.view.Choreographer.doCallbacks(Choreographer.java:794)
at android.view.Choreographer.doFrame(Choreographer.java:729)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:981)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7948)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1075)
If I remove "bracketWatcher" from the code, the exception will not occur, so I think this part of the code is causing the problem:
@Override
public void afterTextChanged(Editable editable) {
//remove cursor character and put real cursor instead: [☢] -> [|]
if (editable.length() > start && count > 1) {
CharSequence newText = editable.subSequence(start, start + count);
int i = newText.toString().indexOf(CURSOR);
if (i > -1) {
editable.delete(start + i, start + i + 1);
setSelection(start);
}
}
}
I spent a lot of time figuring out how to fix this problem. I hope that someone could help me.