@@ -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