Skip to content

Commit 42390aa

Browse files
Allow text selection handles to scroll horizontally
When selecting text and expanding the selection, the handles snap to the end (or start) of words. The handles don't snap until the user has moved halfway through the word. In horizontally scrolling text views, where some of the text is cut off, the user cannot be halfway through the word, this causes the selection to get stuck unless the user scrolls the view and then continues selecting. This CL does two things: 1) Checks if the user is close to the edge of the view when the view can scroll horizontally, and places the cursor at next offset if available. 2) Moves the code to check if handles are crossing into own method this should be done each time the cursor is placed and avoids the need to duplicate the check throughout updatePosition code. Bug: 22657879 Change-Id: Ic14cb0994cd202a897bf6532f3832bb93ed49bfb
1 parent e95af4a commit 42390aa

1 file changed

Lines changed: 102 additions & 18 deletions

File tree

core/java/android/widget/Editor.java

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4059,9 +4059,17 @@ private class SelectionStartHandleView extends HandleView {
40594059
private float mPrevX;
40604060
// Indicates if the handle has moved a boundary between LTR and RTL text.
40614061
private boolean mLanguageDirectionChanged = false;
4062+
// Distance from edge of horizontally scrolling text view
4063+
// to use to switch to character mode.
4064+
private final float mTextViewEdgeSlop;
4065+
// Used to save text view location.
4066+
private final int[] mTextViewLocation = new int[2];
40624067

40634068
public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
40644069
super(drawableLtr, drawableRtl);
4070+
ViewConfiguration viewConfiguration = ViewConfiguration.get(
4071+
mTextView.getContext());
4072+
mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
40654073
}
40664074

40674075
@Override
@@ -4099,7 +4107,7 @@ public void updatePosition(float x, float y) {
40994107
if (layout == null) {
41004108
// HandleView will deal appropriately in positionAtCursorOffset when
41014109
// layout is null.
4102-
positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
4110+
positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
41034111
return;
41044112
}
41054113

@@ -4141,12 +4149,12 @@ public void updatePosition(float x, float y) {
41414149
// to the current position.
41424150
mLanguageDirectionChanged = true;
41434151
mTouchWordDelta = 0.0f;
4144-
positionAtCursorOffset(offset, false);
4152+
positionAndAdjustForCrossingHandles(offset);
41454153
return;
41464154
} else if (mLanguageDirectionChanged && !isLvlBoundary) {
41474155
// We've just moved past the boundary so update the position. After this we can
41484156
// figure out if the user is expanding or shrinking to go by word or character.
4149-
positionAtCursorOffset(offset, false);
4157+
positionAndAdjustForCrossingHandles(offset);
41504158
mTouchWordDelta = 0.0f;
41514159
mLanguageDirectionChanged = false;
41524160
return;
@@ -4159,6 +4167,21 @@ public void updatePosition(float x, float y) {
41594167
}
41604168
}
41614169

4170+
if (mTextView.getHorizontallyScrolling()) {
4171+
if (positionNearEdgeOfScrollingView(x, atRtl)
4172+
&& (mTextView.getScrollX() != 0)
4173+
&& ((isExpanding && offset < selectionStart) || !isExpanding)) {
4174+
// If we're expanding ensure that the offset is smaller than the
4175+
// selection start, if the handle snapped to the word, the finger position
4176+
// may be out of sync and we don't want the selection to jump back.
4177+
mTouchWordDelta = 0.0f;
4178+
final int nextOffset = atRtl ? layout.getOffsetToRightOf(mPreviousOffset)
4179+
: layout.getOffsetToLeftOf(mPreviousOffset);
4180+
positionAndAdjustForCrossingHandles(nextOffset);
4181+
return;
4182+
}
4183+
}
4184+
41624185
if (isExpanding) {
41634186
// User is increasing the selection.
41644187
if (!mInWord || currLine < mPrevLine) {
@@ -4214,17 +4237,22 @@ public void updatePosition(float x, float y) {
42144237
}
42154238

42164239
if (positionCursor) {
4217-
// Handles can not cross and selection is at least one character.
4218-
if (offset >= selectionEnd) {
4219-
offset = getNextCursorOffset(selectionEnd, false);
4220-
mTouchWordDelta = 0.0f;
4221-
}
42224240
mPreviousLineTouched = currLine;
4223-
positionAtCursorOffset(offset, false);
4241+
positionAndAdjustForCrossingHandles(offset);
42244242
}
42254243
mPrevX = x;
42264244
}
42274245

4246+
private void positionAndAdjustForCrossingHandles(int offset) {
4247+
final int selectionEnd = mTextView.getSelectionEnd();
4248+
if (offset >= selectionEnd) {
4249+
// Handles can not cross and selection is at least one character.
4250+
offset = getNextCursorOffset(selectionEnd, false);
4251+
mTouchWordDelta = 0.0f;
4252+
}
4253+
positionAtCursorOffset(offset, false);
4254+
}
4255+
42284256
@Override
42294257
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
42304258
super.positionAtCursorOffset(offset, parentScrolled);
@@ -4242,6 +4270,20 @@ public boolean onTouchEvent(MotionEvent event) {
42424270
}
42434271
return superResult;
42444272
}
4273+
4274+
private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
4275+
mTextView.getLocationOnScreen(mTextViewLocation);
4276+
boolean nearEdge;
4277+
if (atRtl) {
4278+
int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
4279+
- mTextView.getPaddingRight();
4280+
nearEdge = x > rightEdge - mTextViewEdgeSlop;
4281+
} else {
4282+
int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
4283+
nearEdge = x < leftEdge + mTextViewEdgeSlop;
4284+
}
4285+
return nearEdge;
4286+
}
42454287
}
42464288

42474289
private class SelectionEndHandleView extends HandleView {
@@ -4253,9 +4295,17 @@ private class SelectionEndHandleView extends HandleView {
42534295
private float mPrevX;
42544296
// Indicates if the handle has moved a boundary between LTR and RTL text.
42554297
private boolean mLanguageDirectionChanged = false;
4298+
// Distance from edge of horizontally scrolling text view
4299+
// to use to switch to character mode.
4300+
private final float mTextViewEdgeSlop;
4301+
// Used to save the text view location.
4302+
private final int[] mTextViewLocation = new int[2];
42564303

42574304
public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
42584305
super(drawableLtr, drawableRtl);
4306+
ViewConfiguration viewConfiguration = ViewConfiguration.get(
4307+
mTextView.getContext());
4308+
mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
42594309
}
42604310

42614311
@Override
@@ -4293,7 +4343,7 @@ public void updatePosition(float x, float y) {
42934343
if (layout == null) {
42944344
// HandleView will deal appropriately in positionAtCursorOffset when
42954345
// layout is null.
4296-
positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
4346+
positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
42974347
return;
42984348
}
42994349

@@ -4335,12 +4385,12 @@ public void updatePosition(float x, float y) {
43354385
// to the current position.
43364386
mLanguageDirectionChanged = true;
43374387
mTouchWordDelta = 0.0f;
4338-
positionAtCursorOffset(offset, false);
4388+
positionAndAdjustForCrossingHandles(offset);
43394389
return;
43404390
} else if (mLanguageDirectionChanged && !isLvlBoundary) {
43414391
// We've just moved past the boundary so update the position. After this we can
43424392
// figure out if the user is expanding or shrinking to go by word or character.
4343-
positionAtCursorOffset(offset, false);
4393+
positionAndAdjustForCrossingHandles(offset);
43444394
mTouchWordDelta = 0.0f;
43454395
mLanguageDirectionChanged = false;
43464396
return;
@@ -4353,6 +4403,21 @@ public void updatePosition(float x, float y) {
43534403
}
43544404
}
43554405

4406+
if (mTextView.getHorizontallyScrolling()) {
4407+
if (positionNearEdgeOfScrollingView(x, atRtl)
4408+
&& mTextView.canScrollHorizontally(atRtl ? -1 : 1)
4409+
&& ((isExpanding && offset > selectionEnd) || !isExpanding)) {
4410+
// If we're expanding ensure that the offset is actually greater than the
4411+
// selection end, if the handle snapped to the word, the finger position
4412+
// may be out of sync and we don't want the selection to jump back.
4413+
mTouchWordDelta = 0.0f;
4414+
final int nextOffset = atRtl ? layout.getOffsetToLeftOf(mPreviousOffset)
4415+
: layout.getOffsetToRightOf(mPreviousOffset);
4416+
positionAndAdjustForCrossingHandles(nextOffset);
4417+
return;
4418+
}
4419+
}
4420+
43564421
if (isExpanding) {
43574422
// User is increasing the selection.
43584423
if (!mInWord || currLine > mPrevLine) {
@@ -4408,17 +4473,22 @@ public void updatePosition(float x, float y) {
44084473
}
44094474

44104475
if (positionCursor) {
4411-
// Handles can not cross and selection is at least one character.
4412-
if (offset <= selectionStart) {
4413-
offset = getNextCursorOffset(selectionStart, true);
4414-
mTouchWordDelta = 0.0f;
4415-
}
44164476
mPreviousLineTouched = currLine;
4417-
positionAtCursorOffset(offset, false);
4477+
positionAndAdjustForCrossingHandles(offset);
44184478
}
44194479
mPrevX = x;
44204480
}
44214481

4482+
private void positionAndAdjustForCrossingHandles(int offset) {
4483+
final int selectionStart = mTextView.getSelectionStart();
4484+
if (offset <= selectionStart) {
4485+
// Handles can not cross and selection is at least one character.
4486+
offset = getNextCursorOffset(selectionStart, true);
4487+
mTouchWordDelta = 0.0f;
4488+
}
4489+
positionAtCursorOffset(offset, false);
4490+
}
4491+
44224492
@Override
44234493
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
44244494
super.positionAtCursorOffset(offset, parentScrolled);
@@ -4436,6 +4506,20 @@ public boolean onTouchEvent(MotionEvent event) {
44364506
}
44374507
return superResult;
44384508
}
4509+
4510+
private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
4511+
mTextView.getLocationOnScreen(mTextViewLocation);
4512+
boolean nearEdge;
4513+
if (atRtl) {
4514+
int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
4515+
nearEdge = x < leftEdge + mTextViewEdgeSlop;
4516+
} else {
4517+
int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
4518+
- mTextView.getPaddingRight();
4519+
nearEdge = x > rightEdge - mTextViewEdgeSlop;
4520+
}
4521+
return nearEdge;
4522+
}
44394523
}
44404524

44414525
private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {

0 commit comments

Comments
 (0)