I am currently working on a multi-line Edit Text which might have placeholders within its Text. To circumvent modifications of these placeholders I added an onClickListener to the EditText widget which checks if the cursor position is within such a placeholder. In that case the placeholder should be selected, to prevent any modifications except of completely deleting it.
This works perfectly fine on my Android 2.3 device, but on Android 4.x the selection is modified after the onClick event and the cursor shows up at the beginnning of the placeholder without the selection.
Below the source of the onClickListener.
protected void textClickListener(EditText v) {
Pattern p = Pattern.compile(placeholderRegex);
Matcher matcher = p.matcher(v.getText());
int sel_start = v.getSelectionStart();
int sel_end = v.getSelectionEnd();
if (sel_start == -1) {
return;
}
while (matcher.find()) {
int pattern_start = matcher.start();
int pattern_end = pattern_start + 25;
if (pattern_start > sel_end) {
continue;
}
if (pattern_end < sel_start) {
continue;
}
v.setSelection(Math.min(sel_start, pattern_start), Math.max(sel_end, pattern_end));
return;
}
}
This code works fine, the setSelection is called with the correct values and the selection is actually set. I set a breakpoint on the Selection.setSelection method and found out that it is called from PositionListener, which is an inner class of android.widget.editor and this sets the selection length to 0. Below is the stack trace of this setSelection call:
Selection.setSelection(Spannable, int) line: 87
Editor$InsertionHandleView.updateSelection(int) line: 3271
Editor$InsertionHandleView(Editor$HandleView).positionAtCursorOffset(int, boolean) line: 3045
Editor$InsertionHandleView(Editor$HandleView).updatePosition(int, int, boolean, boolean) line: 3064
Editor$PositionListener.onPreDraw() line: 2047
ViewTreeObserver.dispatchOnPreDraw() line: 671
ViewRootImpl.performTraversals() line: 1820
ViewRootImpl.doTraversal() line: 1000
ViewRootImpl$TraversalRunnable.run() line: 4214
Choreographer$CallbackRecord.run(long) line: 725
Choreographer.doCallbacks(int, long) line: 555
Choreographer.doFrame(long, int) line: 525
Choreographer$FrameDisplayEventReceiver.run() line: 711
Handler.handleCallback(Message) line: 615
Choreographer$FrameHandler(Handler).dispatchMessage(Message) line: 92
Looper.loop() line: 137
ActivityThread.main(String[]) line: 4745
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 511
ZygoteInit$MethodAndArgsCaller.run() line: 786
ZygoteInit.main(String[]) line: 553
NativeStart.main(String[]) line: not available [native method]
Any ideas how to prevent this or how to set the selection after this PositionListener action?
Migrated From Question
Finally I found out, that you have to modify the selection in an
OnTouchListener. And to make it even more complicated, you have to modify the selection in both calls of that listener (ACTION_DOWN,ACTION_UP). If you just do it once (as I did for performance reasons) it won’t work either. So the finally working code is:The
OnClickListeneris still needed for compability reasons, because the methodgetOffsetForPositionis not available for API levels below 11 (Honeycomb). Depending on the used API, only one of both listeners has to be executed.