Loading core/java/android/widget/Editor.java +71 −12 Original line number Diff line number Diff line Loading @@ -1851,8 +1851,7 @@ public class Editor { updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset, clamped)); if (mCursorCount == 2) { updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset, clamped)); updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset, clamped)); } } Loading Loading @@ -2151,18 +2150,57 @@ public class Editor { return mSelectionModifierCursorController; } /** * @hide */ @VisibleForTesting public Drawable[] getCursorDrawable() { return mCursorDrawable; } private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) { if (mCursorDrawable[cursorIndex] == null) mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable( mTextView.mCursorDrawableRes); final Drawable drawable = mCursorDrawable[cursorIndex]; final int left = clampCursorHorizontalPosition(drawable, horizontal); final int width = drawable.getIntrinsicWidth(); drawable.setBounds(left, top - mTempRect.top, left + width, bottom + mTempRect.bottom); } if (mTempRect == null) mTempRect = new Rect(); mCursorDrawable[cursorIndex].getPadding(mTempRect); final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth(); /** * Return clamped position for the cursor. If the cursor is within the boundaries of the view, * then it is offset with the left padding of the cursor drawable. If the cursor is at * the beginning or the end of the text then its drawable edge is aligned with left or right of * the view boundary. * * @param drawable Cursor drawable. * @param horizontal Horizontal position for the cursor. * @return The clamped horizontal position for the cursor. */ private final int clampCursorHorizontalPosition(final Drawable drawable, float horizontal) { horizontal = Math.max(0.5f, horizontal - 0.5f); final int left = (int) (horizontal) - mTempRect.left; mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width, bottom + mTempRect.bottom); if (mTempRect == null) mTempRect = new Rect(); drawable.getPadding(mTempRect); int scrollX = mTextView.getScrollX(); float horizontalDiff = horizontal - scrollX; int viewClippedWidth = mTextView.getWidth() - mTextView.getCompoundPaddingLeft() - mTextView.getCompoundPaddingRight(); final int left; if (horizontalDiff >= (viewClippedWidth - 1f)) { // at the rightmost position final int cursorWidth = drawable.getIntrinsicWidth(); left = viewClippedWidth + scrollX - (cursorWidth - mTempRect.right); } else if (Math.abs(horizontalDiff) <= 1f) { // at the leftmost position left = scrollX - mTempRect.left; } else { left = (int) horizontal - mTempRect.left; } return left; } /** Loading Loading @@ -3919,8 +3957,8 @@ public class Editor { final Layout layout = mTextView.getLayout(); if (layout != null && oldDrawable != mDrawable && isShowing()) { // Update popup window position. mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX - getHorizontalOffset() + getCursorOffset()); mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX - getHorizontalOffset() + getCursorOffset(); mPositionX += mTextView.viewportToContentHorizontalOffset(); mPositionHasChanged = true; updatePosition(mLastParentX, mLastParentY, false, false); Loading Loading @@ -4049,8 +4087,8 @@ public class Editor { final int line = layout.getLineForOffset(offset); mPrevLine = line; mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX - getHorizontalOffset() + getCursorOffset()); mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX - getHorizontalOffset() + getCursorOffset(); mPositionY = layout.getLineBottom(line); // Take TextView's padding and scroll into account. Loading @@ -4062,6 +4100,17 @@ public class Editor { } } /** * Return the clamped horizontal position for the first cursor. * * @param layout Text layout. * @param offset Character offset for the cursor. * @return The clamped horizontal position for the cursor. */ int getCursorHorizontalPosition(Layout layout, int offset) { return (int) (layout.getPrimaryHorizontal(offset) - 0.5f); } public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled) { positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled); Loading Loading @@ -4299,6 +4348,16 @@ public class Editor { return offset; } @Override int getCursorHorizontalPosition(Layout layout, int offset) { final Drawable drawable = mCursorCount > 0 ? mCursorDrawable[0] : null; if (drawable != null) { final float horizontal = layout.getPrimaryHorizontal(offset); return clampCursorHorizontalPosition(drawable, horizontal) + mTempRect.left; } return super.getCursorHorizontalPosition(layout, offset); } @Override public boolean onTouchEvent(MotionEvent ev) { final boolean result = super.onTouchEvent(ev); Loading core/java/android/widget/TextView.java +2 −8 Original line number Diff line number Diff line Loading @@ -5460,15 +5460,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return (int) Math.max(0, mShadowDy + mShadowRadius); } private int getFudgedPaddingRight() { // Add sufficient space for cursor and tone marks int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1)); } @Override protected int getRightPaddingOffset() { return -(getFudgedPaddingRight() - mPaddingRight) + return -(getCompoundPaddingRight() - mPaddingRight) + (int) Math.max(0, mShadowDx + mShadowRadius); } Loading Loading @@ -5813,7 +5807,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float clipLeft = compoundPaddingLeft + scrollX; float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; float clipRight = right - left - getFudgedPaddingRight() + scrollX; float clipRight = right - left - getCompoundPaddingRight() + scrollX; float clipBottom = bottom - top + scrollY - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); Loading core/tests/coretests/src/android/widget/EditorCursorTest.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; import android.view.ViewGroup; public class EditorCursorTest extends ActivityInstrumentationTestCase2<TextViewActivity> { private EditText mEditText; private final String RTL_STRING = "مرحبا الروبوت مرحبا الروبوت مرحبا الروبوت"; public EditorCursorTest() { super(TextViewActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); mEditText = new EditText(getActivity()); mEditText.setTextSize(30); mEditText.setSingleLine(true); mEditText.setLines(1); mEditText.setPadding(15, 15, 15, 15); ViewGroup.LayoutParams editTextLayoutParams = new ViewGroup.LayoutParams(200, ViewGroup.LayoutParams.WRAP_CONTENT); mEditText.setLayoutParams(editTextLayoutParams); final FrameLayout layout = new FrameLayout(getActivity()); ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); layout.setLayoutParams(layoutParams); layout.addView(mEditText); getActivity().runOnUiThread(new Runnable() { @Override public void run() { getActivity().setContentView(layout); mEditText.requestFocus(); } }); getInstrumentation().waitForIdleSync(); } @SmallTest public void testCursorIsInViewBoundariesWhenOnRightForLtr() throws Exception { // Asserts that when an EditText has LTR text, and cursor is at the end (right), // cursor is drawn to the right edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText("aaaaaaaaaaaaaaaaaaaaaa"); int length = mEditText.getText().length(); mEditText.setSelection(length, length); } }); getInstrumentation().waitForIdleSync(); Editor editor = mEditText.getEditorForTesting(); Drawable drawable = editor.getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); // right edge of the view including the scroll int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight() - mEditText.getCompoundPaddingLeft() + +mEditText.getScrollX(); int diff = drawableBounds.right - drawablePadding.right - maxRight; assertTrue(diff >= 0 && diff <= 1); } @SmallTest public void testCursorIsInViewBoundariesWhenOnLeftForLtr() throws Exception { // Asserts that when an EditText has LTR text, and cursor is at the beginning, // cursor is drawn to the left edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText("aaaaaaaaaaaaaaaaaaaaaa"); mEditText.setSelection(0, 0); } }); getInstrumentation().waitForIdleSync(); Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); int diff = drawableBounds.left + drawablePadding.left; assertTrue(diff >= 0 && diff <= 1); } @SmallTest public void testCursorIsInViewBoundariesWhenOnRightForRtl() throws Exception { // Asserts that when an EditText has RTL text, and cursor is at the end, // cursor is drawn to the left edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText(RTL_STRING); mEditText.setSelection(0, 0); } }); getInstrumentation().waitForIdleSync(); Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight() - mEditText.getCompoundPaddingLeft() + mEditText.getScrollX(); int diff = drawableBounds.right - drawablePadding.right - maxRight; assertTrue(diff >= 0 && diff <= 1); } @SmallTest public void testCursorIsInViewBoundariesWhenOnLeftForRtl() throws Exception { // Asserts that when an EditText has RTL text, and cursor is at the beginning, // cursor is drawn to the right edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText(RTL_STRING); int length = mEditText.getText().length(); mEditText.setSelection(length, length); } }); getInstrumentation().waitForIdleSync(); Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); int diff = drawableBounds.left - mEditText.getScrollX() + drawablePadding.left; assertTrue(diff >= 0 && diff <= 1); } } Loading
core/java/android/widget/Editor.java +71 −12 Original line number Diff line number Diff line Loading @@ -1851,8 +1851,7 @@ public class Editor { updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset, clamped)); if (mCursorCount == 2) { updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset, clamped)); updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset, clamped)); } } Loading Loading @@ -2151,18 +2150,57 @@ public class Editor { return mSelectionModifierCursorController; } /** * @hide */ @VisibleForTesting public Drawable[] getCursorDrawable() { return mCursorDrawable; } private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) { if (mCursorDrawable[cursorIndex] == null) mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable( mTextView.mCursorDrawableRes); final Drawable drawable = mCursorDrawable[cursorIndex]; final int left = clampCursorHorizontalPosition(drawable, horizontal); final int width = drawable.getIntrinsicWidth(); drawable.setBounds(left, top - mTempRect.top, left + width, bottom + mTempRect.bottom); } if (mTempRect == null) mTempRect = new Rect(); mCursorDrawable[cursorIndex].getPadding(mTempRect); final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth(); /** * Return clamped position for the cursor. If the cursor is within the boundaries of the view, * then it is offset with the left padding of the cursor drawable. If the cursor is at * the beginning or the end of the text then its drawable edge is aligned with left or right of * the view boundary. * * @param drawable Cursor drawable. * @param horizontal Horizontal position for the cursor. * @return The clamped horizontal position for the cursor. */ private final int clampCursorHorizontalPosition(final Drawable drawable, float horizontal) { horizontal = Math.max(0.5f, horizontal - 0.5f); final int left = (int) (horizontal) - mTempRect.left; mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width, bottom + mTempRect.bottom); if (mTempRect == null) mTempRect = new Rect(); drawable.getPadding(mTempRect); int scrollX = mTextView.getScrollX(); float horizontalDiff = horizontal - scrollX; int viewClippedWidth = mTextView.getWidth() - mTextView.getCompoundPaddingLeft() - mTextView.getCompoundPaddingRight(); final int left; if (horizontalDiff >= (viewClippedWidth - 1f)) { // at the rightmost position final int cursorWidth = drawable.getIntrinsicWidth(); left = viewClippedWidth + scrollX - (cursorWidth - mTempRect.right); } else if (Math.abs(horizontalDiff) <= 1f) { // at the leftmost position left = scrollX - mTempRect.left; } else { left = (int) horizontal - mTempRect.left; } return left; } /** Loading Loading @@ -3919,8 +3957,8 @@ public class Editor { final Layout layout = mTextView.getLayout(); if (layout != null && oldDrawable != mDrawable && isShowing()) { // Update popup window position. mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX - getHorizontalOffset() + getCursorOffset()); mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX - getHorizontalOffset() + getCursorOffset(); mPositionX += mTextView.viewportToContentHorizontalOffset(); mPositionHasChanged = true; updatePosition(mLastParentX, mLastParentY, false, false); Loading Loading @@ -4049,8 +4087,8 @@ public class Editor { final int line = layout.getLineForOffset(offset); mPrevLine = line; mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX - getHorizontalOffset() + getCursorOffset()); mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX - getHorizontalOffset() + getCursorOffset(); mPositionY = layout.getLineBottom(line); // Take TextView's padding and scroll into account. Loading @@ -4062,6 +4100,17 @@ public class Editor { } } /** * Return the clamped horizontal position for the first cursor. * * @param layout Text layout. * @param offset Character offset for the cursor. * @return The clamped horizontal position for the cursor. */ int getCursorHorizontalPosition(Layout layout, int offset) { return (int) (layout.getPrimaryHorizontal(offset) - 0.5f); } public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled) { positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled); Loading Loading @@ -4299,6 +4348,16 @@ public class Editor { return offset; } @Override int getCursorHorizontalPosition(Layout layout, int offset) { final Drawable drawable = mCursorCount > 0 ? mCursorDrawable[0] : null; if (drawable != null) { final float horizontal = layout.getPrimaryHorizontal(offset); return clampCursorHorizontalPosition(drawable, horizontal) + mTempRect.left; } return super.getCursorHorizontalPosition(layout, offset); } @Override public boolean onTouchEvent(MotionEvent ev) { final boolean result = super.onTouchEvent(ev); Loading
core/java/android/widget/TextView.java +2 −8 Original line number Diff line number Diff line Loading @@ -5460,15 +5460,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return (int) Math.max(0, mShadowDy + mShadowRadius); } private int getFudgedPaddingRight() { // Add sufficient space for cursor and tone marks int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1)); } @Override protected int getRightPaddingOffset() { return -(getFudgedPaddingRight() - mPaddingRight) + return -(getCompoundPaddingRight() - mPaddingRight) + (int) Math.max(0, mShadowDx + mShadowRadius); } Loading Loading @@ -5813,7 +5807,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float clipLeft = compoundPaddingLeft + scrollX; float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; float clipRight = right - left - getFudgedPaddingRight() + scrollX; float clipRight = right - left - getCompoundPaddingRight() + scrollX; float clipBottom = bottom - top + scrollY - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); Loading
core/tests/coretests/src/android/widget/EditorCursorTest.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; import android.view.ViewGroup; public class EditorCursorTest extends ActivityInstrumentationTestCase2<TextViewActivity> { private EditText mEditText; private final String RTL_STRING = "مرحبا الروبوت مرحبا الروبوت مرحبا الروبوت"; public EditorCursorTest() { super(TextViewActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); mEditText = new EditText(getActivity()); mEditText.setTextSize(30); mEditText.setSingleLine(true); mEditText.setLines(1); mEditText.setPadding(15, 15, 15, 15); ViewGroup.LayoutParams editTextLayoutParams = new ViewGroup.LayoutParams(200, ViewGroup.LayoutParams.WRAP_CONTENT); mEditText.setLayoutParams(editTextLayoutParams); final FrameLayout layout = new FrameLayout(getActivity()); ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); layout.setLayoutParams(layoutParams); layout.addView(mEditText); getActivity().runOnUiThread(new Runnable() { @Override public void run() { getActivity().setContentView(layout); mEditText.requestFocus(); } }); getInstrumentation().waitForIdleSync(); } @SmallTest public void testCursorIsInViewBoundariesWhenOnRightForLtr() throws Exception { // Asserts that when an EditText has LTR text, and cursor is at the end (right), // cursor is drawn to the right edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText("aaaaaaaaaaaaaaaaaaaaaa"); int length = mEditText.getText().length(); mEditText.setSelection(length, length); } }); getInstrumentation().waitForIdleSync(); Editor editor = mEditText.getEditorForTesting(); Drawable drawable = editor.getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); // right edge of the view including the scroll int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight() - mEditText.getCompoundPaddingLeft() + +mEditText.getScrollX(); int diff = drawableBounds.right - drawablePadding.right - maxRight; assertTrue(diff >= 0 && diff <= 1); } @SmallTest public void testCursorIsInViewBoundariesWhenOnLeftForLtr() throws Exception { // Asserts that when an EditText has LTR text, and cursor is at the beginning, // cursor is drawn to the left edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText("aaaaaaaaaaaaaaaaaaaaaa"); mEditText.setSelection(0, 0); } }); getInstrumentation().waitForIdleSync(); Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); int diff = drawableBounds.left + drawablePadding.left; assertTrue(diff >= 0 && diff <= 1); } @SmallTest public void testCursorIsInViewBoundariesWhenOnRightForRtl() throws Exception { // Asserts that when an EditText has RTL text, and cursor is at the end, // cursor is drawn to the left edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText(RTL_STRING); mEditText.setSelection(0, 0); } }); getInstrumentation().waitForIdleSync(); Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight() - mEditText.getCompoundPaddingLeft() + mEditText.getScrollX(); int diff = drawableBounds.right - drawablePadding.right - maxRight; assertTrue(diff >= 0 && diff <= 1); } @SmallTest public void testCursorIsInViewBoundariesWhenOnLeftForRtl() throws Exception { // Asserts that when an EditText has RTL text, and cursor is at the beginning, // cursor is drawn to the right edge of the view getActivity().runOnUiThread(new Runnable() { @Override public void run() { mEditText.setText(RTL_STRING); int length = mEditText.getText().length(); mEditText.setSelection(length, length); } }); getInstrumentation().waitForIdleSync(); Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0]; Rect drawableBounds = drawable.getBounds(); Rect drawablePadding = new Rect(); drawable.getPadding(drawablePadding); int diff = drawableBounds.left - mEditText.getScrollX() + drawablePadding.left; assertTrue(diff >= 0 && diff <= 1); } }