@@ -4060,9 +4060,17 @@ private class SelectionStartHandleView extends HandleView {
40604060 private float mPrevX ;
40614061 // Indicates if the handle has moved a boundary between LTR and RTL text.
40624062 private boolean mLanguageDirectionChanged = false ;
4063+ // Distance from edge of horizontally scrolling text view
4064+ // to use to switch to character mode.
4065+ private final float mTextViewEdgeSlop ;
4066+ // Used to save text view location.
4067+ private final int [] mTextViewLocation = new int [2 ];
40634068
40644069 public SelectionStartHandleView (Drawable drawableLtr , Drawable drawableRtl ) {
40654070 super (drawableLtr , drawableRtl );
4071+ ViewConfiguration viewConfiguration = ViewConfiguration .get (
4072+ mTextView .getContext ());
4073+ mTextViewEdgeSlop = viewConfiguration .getScaledTouchSlop () * 4 ;
40664074 }
40674075
40684076 @ Override
@@ -4100,7 +4108,7 @@ public void updatePosition(float x, float y) {
41004108 if (layout == null ) {
41014109 // HandleView will deal appropriately in positionAtCursorOffset when
41024110 // layout is null.
4103- positionAtCursorOffset (mTextView .getOffsetForPosition (x , y ), false );
4111+ positionAndAdjustForCrossingHandles (mTextView .getOffsetForPosition (x , y ));
41044112 return ;
41054113 }
41064114
@@ -4142,12 +4150,12 @@ public void updatePosition(float x, float y) {
41424150 // to the current position.
41434151 mLanguageDirectionChanged = true ;
41444152 mTouchWordDelta = 0.0f ;
4145- positionAtCursorOffset (offset , false );
4153+ positionAndAdjustForCrossingHandles (offset );
41464154 return ;
41474155 } else if (mLanguageDirectionChanged && !isLvlBoundary ) {
41484156 // We've just moved past the boundary so update the position. After this we can
41494157 // figure out if the user is expanding or shrinking to go by word or character.
4150- positionAtCursorOffset (offset , false );
4158+ positionAndAdjustForCrossingHandles (offset );
41514159 mTouchWordDelta = 0.0f ;
41524160 mLanguageDirectionChanged = false ;
41534161 return ;
@@ -4160,6 +4168,21 @@ public void updatePosition(float x, float y) {
41604168 }
41614169 }
41624170
4171+ if (mTextView .getHorizontallyScrolling ()) {
4172+ if (positionNearEdgeOfScrollingView (x , atRtl )
4173+ && (mTextView .getScrollX () != 0 )
4174+ && ((isExpanding && offset < selectionStart ) || !isExpanding )) {
4175+ // If we're expanding ensure that the offset is smaller than the
4176+ // selection start, if the handle snapped to the word, the finger position
4177+ // may be out of sync and we don't want the selection to jump back.
4178+ mTouchWordDelta = 0.0f ;
4179+ final int nextOffset = atRtl ? layout .getOffsetToRightOf (mPreviousOffset )
4180+ : layout .getOffsetToLeftOf (mPreviousOffset );
4181+ positionAndAdjustForCrossingHandles (nextOffset );
4182+ return ;
4183+ }
4184+ }
4185+
41634186 if (isExpanding ) {
41644187 // User is increasing the selection.
41654188 if (!mInWord || currLine < mPrevLine ) {
@@ -4215,17 +4238,22 @@ public void updatePosition(float x, float y) {
42154238 }
42164239
42174240 if (positionCursor ) {
4218- // Handles can not cross and selection is at least one character.
4219- if (offset >= selectionEnd ) {
4220- offset = getNextCursorOffset (selectionEnd , false );
4221- mTouchWordDelta = 0.0f ;
4222- }
42234241 mPreviousLineTouched = currLine ;
4224- positionAtCursorOffset (offset , false );
4242+ positionAndAdjustForCrossingHandles (offset );
42254243 }
42264244 mPrevX = x ;
42274245 }
42284246
4247+ private void positionAndAdjustForCrossingHandles (int offset ) {
4248+ final int selectionEnd = mTextView .getSelectionEnd ();
4249+ if (offset >= selectionEnd ) {
4250+ // Handles can not cross and selection is at least one character.
4251+ offset = getNextCursorOffset (selectionEnd , false );
4252+ mTouchWordDelta = 0.0f ;
4253+ }
4254+ positionAtCursorOffset (offset , false );
4255+ }
4256+
42294257 @ Override
42304258 protected void positionAtCursorOffset (int offset , boolean parentScrolled ) {
42314259 super .positionAtCursorOffset (offset , parentScrolled );
@@ -4243,6 +4271,20 @@ public boolean onTouchEvent(MotionEvent event) {
42434271 }
42444272 return superResult ;
42454273 }
4274+
4275+ private boolean positionNearEdgeOfScrollingView (float x , boolean atRtl ) {
4276+ mTextView .getLocationOnScreen (mTextViewLocation );
4277+ boolean nearEdge ;
4278+ if (atRtl ) {
4279+ int rightEdge = mTextViewLocation [0 ] + mTextView .getWidth ()
4280+ - mTextView .getPaddingRight ();
4281+ nearEdge = x > rightEdge - mTextViewEdgeSlop ;
4282+ } else {
4283+ int leftEdge = mTextViewLocation [0 ] + mTextView .getPaddingLeft ();
4284+ nearEdge = x < leftEdge + mTextViewEdgeSlop ;
4285+ }
4286+ return nearEdge ;
4287+ }
42464288 }
42474289
42484290 private class SelectionEndHandleView extends HandleView {
@@ -4254,9 +4296,17 @@ private class SelectionEndHandleView extends HandleView {
42544296 private float mPrevX ;
42554297 // Indicates if the handle has moved a boundary between LTR and RTL text.
42564298 private boolean mLanguageDirectionChanged = false ;
4299+ // Distance from edge of horizontally scrolling text view
4300+ // to use to switch to character mode.
4301+ private final float mTextViewEdgeSlop ;
4302+ // Used to save the text view location.
4303+ private final int [] mTextViewLocation = new int [2 ];
42574304
42584305 public SelectionEndHandleView (Drawable drawableLtr , Drawable drawableRtl ) {
42594306 super (drawableLtr , drawableRtl );
4307+ ViewConfiguration viewConfiguration = ViewConfiguration .get (
4308+ mTextView .getContext ());
4309+ mTextViewEdgeSlop = viewConfiguration .getScaledTouchSlop () * 4 ;
42604310 }
42614311
42624312 @ Override
@@ -4294,7 +4344,7 @@ public void updatePosition(float x, float y) {
42944344 if (layout == null ) {
42954345 // HandleView will deal appropriately in positionAtCursorOffset when
42964346 // layout is null.
4297- positionAtCursorOffset (mTextView .getOffsetForPosition (x , y ), false );
4347+ positionAndAdjustForCrossingHandles (mTextView .getOffsetForPosition (x , y ));
42984348 return ;
42994349 }
43004350
@@ -4336,12 +4386,12 @@ public void updatePosition(float x, float y) {
43364386 // to the current position.
43374387 mLanguageDirectionChanged = true ;
43384388 mTouchWordDelta = 0.0f ;
4339- positionAtCursorOffset (offset , false );
4389+ positionAndAdjustForCrossingHandles (offset );
43404390 return ;
43414391 } else if (mLanguageDirectionChanged && !isLvlBoundary ) {
43424392 // We've just moved past the boundary so update the position. After this we can
43434393 // figure out if the user is expanding or shrinking to go by word or character.
4344- positionAtCursorOffset (offset , false );
4394+ positionAndAdjustForCrossingHandles (offset );
43454395 mTouchWordDelta = 0.0f ;
43464396 mLanguageDirectionChanged = false ;
43474397 return ;
@@ -4354,6 +4404,21 @@ public void updatePosition(float x, float y) {
43544404 }
43554405 }
43564406
4407+ if (mTextView .getHorizontallyScrolling ()) {
4408+ if (positionNearEdgeOfScrollingView (x , atRtl )
4409+ && mTextView .canScrollHorizontally (atRtl ? -1 : 1 )
4410+ && ((isExpanding && offset > selectionEnd ) || !isExpanding )) {
4411+ // If we're expanding ensure that the offset is actually greater than the
4412+ // selection end, if the handle snapped to the word, the finger position
4413+ // may be out of sync and we don't want the selection to jump back.
4414+ mTouchWordDelta = 0.0f ;
4415+ final int nextOffset = atRtl ? layout .getOffsetToLeftOf (mPreviousOffset )
4416+ : layout .getOffsetToRightOf (mPreviousOffset );
4417+ positionAndAdjustForCrossingHandles (nextOffset );
4418+ return ;
4419+ }
4420+ }
4421+
43574422 if (isExpanding ) {
43584423 // User is increasing the selection.
43594424 if (!mInWord || currLine > mPrevLine ) {
@@ -4409,17 +4474,22 @@ public void updatePosition(float x, float y) {
44094474 }
44104475
44114476 if (positionCursor ) {
4412- // Handles can not cross and selection is at least one character.
4413- if (offset <= selectionStart ) {
4414- offset = getNextCursorOffset (selectionStart , true );
4415- mTouchWordDelta = 0.0f ;
4416- }
44174477 mPreviousLineTouched = currLine ;
4418- positionAtCursorOffset (offset , false );
4478+ positionAndAdjustForCrossingHandles (offset );
44194479 }
44204480 mPrevX = x ;
44214481 }
44224482
4483+ private void positionAndAdjustForCrossingHandles (int offset ) {
4484+ final int selectionStart = mTextView .getSelectionStart ();
4485+ if (offset <= selectionStart ) {
4486+ // Handles can not cross and selection is at least one character.
4487+ offset = getNextCursorOffset (selectionStart , true );
4488+ mTouchWordDelta = 0.0f ;
4489+ }
4490+ positionAtCursorOffset (offset , false );
4491+ }
4492+
44234493 @ Override
44244494 protected void positionAtCursorOffset (int offset , boolean parentScrolled ) {
44254495 super .positionAtCursorOffset (offset , parentScrolled );
@@ -4437,6 +4507,20 @@ public boolean onTouchEvent(MotionEvent event) {
44374507 }
44384508 return superResult ;
44394509 }
4510+
4511+ private boolean positionNearEdgeOfScrollingView (float x , boolean atRtl ) {
4512+ mTextView .getLocationOnScreen (mTextViewLocation );
4513+ boolean nearEdge ;
4514+ if (atRtl ) {
4515+ int leftEdge = mTextViewLocation [0 ] + mTextView .getPaddingLeft ();
4516+ nearEdge = x < leftEdge + mTextViewEdgeSlop ;
4517+ } else {
4518+ int rightEdge = mTextViewLocation [0 ] + mTextView .getWidth ()
4519+ - mTextView .getPaddingRight ();
4520+ nearEdge = x > rightEdge - mTextViewEdgeSlop ;
4521+ }
4522+ return nearEdge ;
4523+ }
44404524 }
44414525
44424526 private int getCurrentLineAdjustedForSlop (Layout layout , int prevLine , float y ) {
0 commit comments