Loading core/java/android/widget/Editor.java +36 −21 Original line number Diff line number Diff line Loading @@ -405,6 +405,13 @@ public class Editor { // The actual zoom value may changes based on this initial zoom value. private float mInitialZoom = 1f; // For calculating the line change slops while moving cursor/selection. // The slop max/min value include line height and the slop on the upper/lower line. private static final int LINE_CHANGE_SLOP_MAX_DP = 45; private static final int LINE_CHANGE_SLOP_MIN_DP = 12; private int mLineChangeSlopMax; private int mLineChangeSlopMin; Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. Loading @@ -430,6 +437,14 @@ public class Editor { logCursor("Editor", "New magnifier is %s.", mNewMagnifierEnabled ? "enabled" : "disabled"); } mLineChangeSlopMax = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MAX_DP, mTextView.getContext().getResources().getDisplayMetrics()); mLineChangeSlopMin = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MIN_DP, mTextView.getContext().getResources().getDisplayMetrics()); } @VisibleForTesting Loading Loading @@ -6018,7 +6033,14 @@ public class Editor { } } private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { @VisibleForTesting public void setLineChangeSlopMinMaxForTesting(final int min, final int max) { mLineChangeSlopMin = min; mLineChangeSlopMax = max; } @VisibleForTesting public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { final int trueLine = mTextView.getLineAtCoordinate(y); if (layout == null || prevLine > layout.getLineCount() || layout.getLineCount() <= 0 || prevLine < 0) { Loading @@ -6031,28 +6053,21 @@ public class Editor { return trueLine; } final int lineHeight = layout.getLineBottom(prevLine) - layout.getLineTop(prevLine); int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS * (layout.getLineBottom(trueLine) - layout.getLineTop(trueLine))); slop = Math.max(mLineChangeSlopMin, Math.min(mLineChangeSlopMax, lineHeight + slop)) - lineHeight; slop = Math.max(0, slop); final float verticalOffset = mTextView.viewportToContentVerticalOffset(); final int lineCount = layout.getLineCount(); final float slop = mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS; final float firstLineTop = layout.getLineTop(0) + verticalOffset; final float prevLineTop = layout.getLineTop(prevLine) + verticalOffset; final float yTopBound = Math.max(prevLineTop - slop, firstLineTop + slop); final float lastLineBottom = layout.getLineBottom(lineCount - 1) + verticalOffset; final float prevLineBottom = layout.getLineBottom(prevLine) + verticalOffset; final float yBottomBound = Math.min(prevLineBottom + slop, lastLineBottom - slop); // Determine if we've moved lines based on y position and previous line. int currLine; if (y <= yTopBound) { currLine = Math.max(prevLine - 1, 0); } else if (y >= yBottomBound) { currLine = Math.min(prevLine + 1, lineCount - 1); } else { currLine = prevLine; if (trueLine > prevLine && y >= layout.getLineBottom(prevLine) + slop + verticalOffset) { return trueLine; } if (trueLine < prevLine && y <= layout.getLineTop(prevLine) - slop + verticalOffset) { return trueLine; } return currLine; return prevLine; } /** Loading core/tests/coretests/src/android/widget/EditorCursorDragTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.widget; import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.dragOnText; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; Loading @@ -37,6 +38,9 @@ import android.app.Activity; import android.app.Instrumentation; import android.graphics.Rect; import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; import android.text.style.AbsoluteSizeSpan; import android.util.ArraySet; import android.util.Log; import android.view.InputDevice; Loading Loading @@ -488,6 +492,38 @@ public class EditorCursorDragTest { simulateDrag(tv, events, true); } @Test public void testLineChangeSlop() throws Throwable { TextView tv = mActivity.findViewById(R.id.textview); Spannable s = new SpannableString("a\nb\nc"); s.setSpan(new AbsoluteSizeSpan(10), 2, 4, SPAN_INCLUSIVE_EXCLUSIVE); s.setSpan(new AbsoluteSizeSpan(32), 4, 5, SPAN_INCLUSIVE_EXCLUSIVE); mInstrumentation.runOnMainSync(() -> tv.setText(s)); Layout layout = tv.getLayout(); Editor editor = tv.getEditorForTesting(); final float verticalOffset = tv.getExtendedPaddingTop(); editor.setLineChangeSlopMinMaxForTesting(30, 65); // Hit top part of upper line, jump to upper line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 5 + verticalOffset)) .isEqualTo(0); // Hit bottom part of upper line, stay at current line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 40 + verticalOffset)) .isEqualTo(1); // Hit current line, stay at current line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 70 + verticalOffset)) .isEqualTo(1); // Hit top part of lower line, stay at current line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 85 + verticalOffset)) .isEqualTo(1); // Hit bottom part of lower line, jump to lower line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 110 + verticalOffset)) .isEqualTo(2); // Hit lower line of lower line, jump to target line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 0, 110 + verticalOffset)) .isEqualTo(2); } @Test public void testCursorDrag_snapDistance() throws Throwable { String text = "line1: This is the 1st line: A\n" Loading Loading
core/java/android/widget/Editor.java +36 −21 Original line number Diff line number Diff line Loading @@ -405,6 +405,13 @@ public class Editor { // The actual zoom value may changes based on this initial zoom value. private float mInitialZoom = 1f; // For calculating the line change slops while moving cursor/selection. // The slop max/min value include line height and the slop on the upper/lower line. private static final int LINE_CHANGE_SLOP_MAX_DP = 45; private static final int LINE_CHANGE_SLOP_MIN_DP = 12; private int mLineChangeSlopMax; private int mLineChangeSlopMin; Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. Loading @@ -430,6 +437,14 @@ public class Editor { logCursor("Editor", "New magnifier is %s.", mNewMagnifierEnabled ? "enabled" : "disabled"); } mLineChangeSlopMax = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MAX_DP, mTextView.getContext().getResources().getDisplayMetrics()); mLineChangeSlopMin = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MIN_DP, mTextView.getContext().getResources().getDisplayMetrics()); } @VisibleForTesting Loading Loading @@ -6018,7 +6033,14 @@ public class Editor { } } private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { @VisibleForTesting public void setLineChangeSlopMinMaxForTesting(final int min, final int max) { mLineChangeSlopMin = min; mLineChangeSlopMax = max; } @VisibleForTesting public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { final int trueLine = mTextView.getLineAtCoordinate(y); if (layout == null || prevLine > layout.getLineCount() || layout.getLineCount() <= 0 || prevLine < 0) { Loading @@ -6031,28 +6053,21 @@ public class Editor { return trueLine; } final int lineHeight = layout.getLineBottom(prevLine) - layout.getLineTop(prevLine); int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS * (layout.getLineBottom(trueLine) - layout.getLineTop(trueLine))); slop = Math.max(mLineChangeSlopMin, Math.min(mLineChangeSlopMax, lineHeight + slop)) - lineHeight; slop = Math.max(0, slop); final float verticalOffset = mTextView.viewportToContentVerticalOffset(); final int lineCount = layout.getLineCount(); final float slop = mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS; final float firstLineTop = layout.getLineTop(0) + verticalOffset; final float prevLineTop = layout.getLineTop(prevLine) + verticalOffset; final float yTopBound = Math.max(prevLineTop - slop, firstLineTop + slop); final float lastLineBottom = layout.getLineBottom(lineCount - 1) + verticalOffset; final float prevLineBottom = layout.getLineBottom(prevLine) + verticalOffset; final float yBottomBound = Math.min(prevLineBottom + slop, lastLineBottom - slop); // Determine if we've moved lines based on y position and previous line. int currLine; if (y <= yTopBound) { currLine = Math.max(prevLine - 1, 0); } else if (y >= yBottomBound) { currLine = Math.min(prevLine + 1, lineCount - 1); } else { currLine = prevLine; if (trueLine > prevLine && y >= layout.getLineBottom(prevLine) + slop + verticalOffset) { return trueLine; } if (trueLine < prevLine && y <= layout.getLineTop(prevLine) - slop + verticalOffset) { return trueLine; } return currLine; return prevLine; } /** Loading
core/tests/coretests/src/android/widget/EditorCursorDragTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.widget; import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.dragOnText; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; Loading @@ -37,6 +38,9 @@ import android.app.Activity; import android.app.Instrumentation; import android.graphics.Rect; import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; import android.text.style.AbsoluteSizeSpan; import android.util.ArraySet; import android.util.Log; import android.view.InputDevice; Loading Loading @@ -488,6 +492,38 @@ public class EditorCursorDragTest { simulateDrag(tv, events, true); } @Test public void testLineChangeSlop() throws Throwable { TextView tv = mActivity.findViewById(R.id.textview); Spannable s = new SpannableString("a\nb\nc"); s.setSpan(new AbsoluteSizeSpan(10), 2, 4, SPAN_INCLUSIVE_EXCLUSIVE); s.setSpan(new AbsoluteSizeSpan(32), 4, 5, SPAN_INCLUSIVE_EXCLUSIVE); mInstrumentation.runOnMainSync(() -> tv.setText(s)); Layout layout = tv.getLayout(); Editor editor = tv.getEditorForTesting(); final float verticalOffset = tv.getExtendedPaddingTop(); editor.setLineChangeSlopMinMaxForTesting(30, 65); // Hit top part of upper line, jump to upper line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 5 + verticalOffset)) .isEqualTo(0); // Hit bottom part of upper line, stay at current line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 40 + verticalOffset)) .isEqualTo(1); // Hit current line, stay at current line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 70 + verticalOffset)) .isEqualTo(1); // Hit top part of lower line, stay at current line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 85 + verticalOffset)) .isEqualTo(1); // Hit bottom part of lower line, jump to lower line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 110 + verticalOffset)) .isEqualTo(2); // Hit lower line of lower line, jump to target line. assertThat(editor.getCurrentLineAdjustedForSlop(layout, 0, 110 + verticalOffset)) .isEqualTo(2); } @Test public void testCursorDrag_snapDistance() throws Throwable { String text = "line1: This is the 1st line: A\n" Loading