Fix: CursorAnchorInfo EditorBoundsInfo includes non-visible region
Bug: 277625776 Test: atest EditTextCursorAnchorInfoTest Change-Id: I1c45951d32b0ef394ba1857846c056d7f5f6da97
This commit is contained in:
@@ -13811,13 +13811,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set {@code rect} to the text content's non-clipped area in the view's
|
||||
* coordinates.
|
||||
* Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
|
||||
* This method obtains the view's visible rectangle whereas the method
|
||||
* {@link #getContentVisibleRect} returns the text layout's visible rectangle.
|
||||
*
|
||||
* @return true if at least part of the text content is visible; false if the text content is
|
||||
* completely clipped or translated out of the visible area.
|
||||
*/
|
||||
private boolean getContentVisibleRect(Rect rect) {
|
||||
private boolean getViewVisibleRect(Rect rect) {
|
||||
if (!getLocalVisibleRect(rect)) {
|
||||
return false;
|
||||
}
|
||||
@@ -13826,6 +13827,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
// view's coordinates. So we need to offset it with the negative scrolled amount to convert
|
||||
// it to view's coordinate.
|
||||
rect.offset(-getScrollX(), -getScrollY());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set {@code rect} to the text content's non-clipped area in the view's
|
||||
* coordinates.
|
||||
*
|
||||
* @return true if at least part of the text content is visible; false if the text content is
|
||||
* completely clipped or translated out of the visible area.
|
||||
*/
|
||||
private boolean getContentVisibleRect(Rect rect) {
|
||||
if (!getViewVisibleRect(rect)) {
|
||||
return false;
|
||||
}
|
||||
// Clip the view's visible rect with the text layout's visible rect.
|
||||
return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
|
||||
getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
|
||||
@@ -13955,14 +13970,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
builder.setMatrix(viewToScreenMatrix);
|
||||
|
||||
if (includeEditorBounds) {
|
||||
final RectF editorBounds = new RectF();
|
||||
editorBounds.set(0 /* left */, 0 /* top */,
|
||||
getWidth(), getHeight());
|
||||
final RectF handwritingBounds = new RectF(
|
||||
-getHandwritingBoundsOffsetLeft(),
|
||||
-getHandwritingBoundsOffsetTop(),
|
||||
getWidth() + getHandwritingBoundsOffsetRight(),
|
||||
getHeight() + getHandwritingBoundsOffsetBottom());
|
||||
if (mTempRect == null) {
|
||||
mTempRect = new Rect();
|
||||
}
|
||||
final Rect bounds = mTempRect;
|
||||
final RectF editorBounds;
|
||||
final RectF handwritingBounds;
|
||||
if (getViewVisibleRect(bounds)) {
|
||||
editorBounds = new RectF(bounds);
|
||||
handwritingBounds = new RectF(editorBounds);
|
||||
handwritingBounds.top -= getHandwritingBoundsOffsetTop();
|
||||
handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
|
||||
handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
|
||||
handwritingBounds.right += getHandwritingBoundsOffsetRight();
|
||||
} else {
|
||||
// The editor is not visible at all, return empty rectangles. We still need to
|
||||
// return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
|
||||
editorBounds = new RectF();
|
||||
handwritingBounds = new RectF();
|
||||
}
|
||||
EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
|
||||
EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
|
||||
.setHandwritingBounds(handwritingBounds).build();
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorBoundsInfo;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
@@ -54,8 +55,15 @@ public class EditTextCursorAnchorInfoTest {
|
||||
private static final int[] sLocationOnScreen = new int[2];
|
||||
private static Typeface sTypeface;
|
||||
private static final float TEXT_SIZE = 1f;
|
||||
// The line height of the test font font is 1.2 * textSize.
|
||||
// The line height of the test font is 1.2 * textSize.
|
||||
private static final int LINE_HEIGHT = 12;
|
||||
private static final int HW_BOUNDS_OFFSET_LEFT = 10;
|
||||
private static final int HW_BOUNDS_OFFSET_TOP = 20;
|
||||
private static final int HW_BOUNDS_OFFSET_RIGHT = 30;
|
||||
private static final int HW_BOUNDS_OFFSET_BOTTOM = 40;
|
||||
|
||||
|
||||
// Default text has 5 lines of text. The needed width is 50px and the needed height is 60px.
|
||||
private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
|
||||
private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
|
||||
new RectF(0f, 0f, 10f, LINE_HEIGHT),
|
||||
@@ -130,6 +138,55 @@ public class EditTextCursorAnchorInfoTest {
|
||||
assertThat(actualMatrix).isEqualTo(expectedMatrix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditorBoundsInfo_allVisible() {
|
||||
// The needed width and height of the DEFAULT_TEXT are 50 px and 60 px respectfully.
|
||||
int width = 100;
|
||||
int height = 200;
|
||||
setupEditText(DEFAULT_TEXT, width, height);
|
||||
CursorAnchorInfo cursorAnchorInfo =
|
||||
mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
|
||||
EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
|
||||
assertThat(editorBoundsInfo).isNotNull();
|
||||
assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, width, height));
|
||||
assertThat(editorBoundsInfo.getHandwritingBounds())
|
||||
.isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, -HW_BOUNDS_OFFSET_TOP,
|
||||
width + HW_BOUNDS_OFFSET_RIGHT, height + HW_BOUNDS_OFFSET_BOTTOM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditorBoundsInfo_scrolled() {
|
||||
// The height of the editor will be 60 px.
|
||||
int width = 100;
|
||||
int visibleTop = 10;
|
||||
int visibleBottom = 30;
|
||||
setupVerticalClippedEditText(width, visibleTop, visibleBottom);
|
||||
CursorAnchorInfo cursorAnchorInfo =
|
||||
mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
|
||||
EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
|
||||
assertThat(editorBoundsInfo).isNotNull();
|
||||
assertThat(editorBoundsInfo.getEditorBounds())
|
||||
.isEqualTo(new RectF(0, visibleTop, width, visibleBottom));
|
||||
assertThat(editorBoundsInfo.getHandwritingBounds())
|
||||
.isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, visibleTop - HW_BOUNDS_OFFSET_TOP,
|
||||
width + HW_BOUNDS_OFFSET_RIGHT, visibleBottom + HW_BOUNDS_OFFSET_BOTTOM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditorBoundsInfo_invisible() {
|
||||
// The height of the editor will be 60px. Scroll it to 70px will make it invisible.
|
||||
int width = 100;
|
||||
int visibleTop = 70;
|
||||
int visibleBottom = 70;
|
||||
setupVerticalClippedEditText(width, visibleTop, visibleBottom);
|
||||
CursorAnchorInfo cursorAnchorInfo =
|
||||
mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
|
||||
EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
|
||||
assertThat(editorBoundsInfo).isNotNull();
|
||||
assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, 0, 0));
|
||||
assertThat(editorBoundsInfo.getHandwritingBounds()).isEqualTo(new RectF(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVisibleLineBounds_allVisible() {
|
||||
setupEditText(DEFAULT_TEXT, /* height= */ 100);
|
||||
@@ -465,32 +522,26 @@ public class EditTextCursorAnchorInfoTest {
|
||||
}
|
||||
|
||||
private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
|
||||
setupVerticalClippedEditText(1000, visibleTop, visibleBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create an EditText in a vertical ScrollView so that its visible bounds
|
||||
* is Rect(0, visibleTop, width, visibleBottom) in the EditText's coordinates. Both ScrollView
|
||||
* and EditText's width is set to the given width.
|
||||
*/
|
||||
private void setupVerticalClippedEditText(int width, int visibleTop, int visibleBottom) {
|
||||
ScrollView scrollView = new ScrollView(mActivity);
|
||||
mEditText = new EditText(mActivity);
|
||||
mEditText.setTypeface(sTypeface);
|
||||
mEditText.setText(DEFAULT_TEXT);
|
||||
mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
|
||||
|
||||
mEditText.setPadding(0, 0, 0, 0);
|
||||
mEditText.setCompoundDrawables(null, null, null, null);
|
||||
mEditText.setCompoundDrawablePadding(0);
|
||||
|
||||
mEditText.scrollTo(0, 0);
|
||||
mEditText.setLineSpacing(0f, 1f);
|
||||
|
||||
// Place the text layout top to the view's top.
|
||||
mEditText.setGravity(Gravity.TOP);
|
||||
int width = 1000;
|
||||
int height = visibleBottom - visibleTop;
|
||||
createEditText();
|
||||
int scrollViewHeight = visibleBottom - visibleTop;
|
||||
|
||||
scrollView.addView(mEditText, new FrameLayout.LayoutParams(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
|
||||
scrollView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
|
||||
scrollView.layout(0, 0, width, height);
|
||||
|
||||
View.MeasureSpec.makeMeasureSpec(scrollViewHeight, View.MeasureSpec.EXACTLY));
|
||||
scrollView.layout(0, 0, width, scrollViewHeight);
|
||||
scrollView.scrollTo(0, visibleTop);
|
||||
}
|
||||
|
||||
@@ -499,6 +550,11 @@ public class EditTextCursorAnchorInfoTest {
|
||||
measureEditText(height);
|
||||
}
|
||||
|
||||
private void setupEditText(CharSequence text, int width, int height) {
|
||||
createEditText(text);
|
||||
measureEditText(width, height);
|
||||
}
|
||||
|
||||
private void setupEditText(CharSequence text, int height, float lineSpacing,
|
||||
float lineMultiplier) {
|
||||
createEditText(text);
|
||||
@@ -537,6 +593,8 @@ public class EditTextCursorAnchorInfoTest {
|
||||
mEditText.setTypeface(sTypeface);
|
||||
mEditText.setText(text);
|
||||
mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
|
||||
mEditText.setHandwritingBoundsOffsets(HW_BOUNDS_OFFSET_LEFT, HW_BOUNDS_OFFSET_TOP,
|
||||
HW_BOUNDS_OFFSET_RIGHT, HW_BOUNDS_OFFSET_BOTTOM);
|
||||
|
||||
mEditText.setPadding(0, 0, 0, 0);
|
||||
mEditText.setCompoundDrawables(null, null, null, null);
|
||||
|
||||
Reference in New Issue
Block a user