Add highlight API for search result range

The search result highlight API is added next to the highlight APIs
becasue of following reasons.
- The search result API draws always top of the developer given
  highlight APIs.
- highlight and focused highlight color should be customizable by
  themeing.

Bug: 244502332
Test: atest android.widget.cts.TextViewSearchResultHighlightTest
Change-Id: Idb52e7080ee33a5315878e42a3d7349bc3a90627
This commit is contained in:
Seigo Nonaka
2022-12-06 19:30:01 +09:00
parent bf3823e8de
commit c99fd2f94e
4 changed files with 382 additions and 2 deletions

View File

@@ -744,6 +744,7 @@ package android {
field public static final int focusableInTouchMode = 16842971; // 0x10100db
field public static final int focusedByDefault = 16844100; // 0x1010544
field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
field public static final int focusedSearchResultHighlightColor;
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -1355,6 +1356,7 @@ package android {
field public static final int searchHintIcon = 16843988; // 0x10104d4
field public static final int searchIcon = 16843907; // 0x1010483
field public static final int searchMode = 16843221; // 0x10101d5
field public static final int searchResultHighlightColor;
field public static final int searchSettingsDescription = 16843402; // 0x101028a
field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -59275,6 +59277,7 @@ package android.widget {
method public void append(CharSequence, int, int);
method public void beginBatchEdit();
method public boolean bringPointIntoView(int);
method public boolean bringPointIntoView(@IntRange(from=0) int, boolean);
method public void clearComposingText();
method public void debug(int);
method public boolean didTouchFocusSelect();
@@ -59312,6 +59315,8 @@ package android.widget {
method public int getExtendedPaddingTop();
method public android.text.InputFilter[] getFilters();
method public int getFirstBaselineToTopHeight();
method @ColorInt public int getFocusedSearchResultHighlightColor();
method public int getFocusedSearchResultIndex();
method @Nullable public String getFontFeatureSettings();
method @Nullable public String getFontVariationSettings();
method public boolean getFreezesText();
@@ -59356,6 +59361,8 @@ package android.widget {
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public String getPrivateImeOptions();
method @ColorInt public int getSearchResultHighlightColor();
method @Nullable public int[] getSearchResultHighlights();
method public int getSelectionEnd();
method public int getSelectionStart();
method @ColorInt public int getShadowColor();
@@ -59440,6 +59447,8 @@ package android.widget {
method public void setFallbackLineSpacing(boolean);
method public void setFilters(android.text.InputFilter[]);
method public void setFirstBaselineToTopHeight(@IntRange(from=0) @Px int);
method public void setFocusedSearchResultHighlightColor(@ColorInt int);
method public void setFocusedSearchResultIndex(int);
method public void setFontFeatureSettings(@Nullable String);
method public boolean setFontVariationSettings(@Nullable String);
method protected boolean setFrame(int, int, int, int);
@@ -59487,6 +59496,8 @@ package android.widget {
method public void setPrivateImeOptions(String);
method public void setRawInputType(int);
method public void setScroller(android.widget.Scroller);
method public void setSearchResultHighlightColor(@ColorInt int);
method public void setSearchResultHighlights(@Nullable int...);
method public void setSelectAllOnFocus(boolean);
method public void setShadowLayer(float, float, float, int);
method public final void setShowSoftInputOnFocus(boolean);
@@ -59526,6 +59537,7 @@ package android.widget {
method public void setWidth(int);
field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
field public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1; // 0xffffffff
}
public enum TextView.BufferType {

View File

@@ -937,6 +937,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private List<Path> mHighlightPaths;
private List<Paint> mHighlightPaints;
private Highlights mHighlights;
private int[] mSearchResultHighlights = null;
private Paint mSearchResultHighlightPaint = null;
private Paint mFocusedSearchResultHighlightPaint = null;
private int mFocusedSearchResultHighlightColor = 0xFFFF9632;
private int mSearchResultHighlightColor = 0xFFFFFF00;
private int mFocusedSearchResultIndex = -1;
private int mGesturePreviewHighlightStart = -1;
private int mGesturePreviewHighlightEnd = -1;
private Paint mGesturePreviewHighlightPaint;
@@ -4030,6 +4037,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
private static class TextAppearanceAttributes {
int mTextColorHighlight = 0;
int mSearchResultHighlightColor = 0;
int mFocusedSearchResultHighlightColor = 0;
ColorStateList mTextColor = null;
ColorStateList mTextColorHint = null;
ColorStateList mTextColorLink = null;
@@ -4062,6 +4071,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public String toString() {
return "TextAppearanceAttributes {\n"
+ " mTextColorHighlight:" + mTextColorHighlight + "\n"
+ " mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n"
+ " mFocusedSearchResultHighlightColor: "
+ mFocusedSearchResultHighlightColor + "\n"
+ " mTextColor:" + mTextColor + "\n"
+ " mTextColorHint:" + mTextColorHint + "\n"
+ " mTextColorLink:" + mTextColorLink + "\n"
@@ -4100,6 +4112,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor,
com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor);
sAppearanceValues.put(
com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor,
com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
@@ -4174,6 +4191,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
attributes.mTextColorHighlight =
appearance.getColor(attr, attributes.mTextColorHighlight);
break;
case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor:
attributes.mSearchResultHighlightColor =
appearance.getColor(attr, attributes.mSearchResultHighlightColor);
break;
case com.android.internal.R.styleable
.TextAppearance_focusedSearchResultHighlightColor:
attributes.mFocusedSearchResultHighlightColor =
appearance.getColor(attr,
attributes.mFocusedSearchResultHighlightColor);
break;
case com.android.internal.R.styleable.TextAppearance_textColor:
attributes.mTextColor = appearance.getColorStateList(attr);
break;
@@ -4290,6 +4317,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setHighlightColor(attributes.mTextColorHighlight);
}
if (attributes.mSearchResultHighlightColor != 0) {
setSearchResultHighlightColor(attributes.mSearchResultHighlightColor);
}
if (attributes.mFocusedSearchResultHighlightColor != 0) {
setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor);
}
if (attributes.mTextSize != -1) {
mTextSizeUnit = attributes.mTextSizeUnit;
setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
@@ -6175,6 +6210,238 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mHighlights;
}
/**
* Sets the search result ranges with flatten range representation.
*
* Ranges are represented of flattened inclusive start and exclusive end integers array. The
* inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
* The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
* array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
* [1, 2, 3, 4].
*
* TextView will render the search result with the highlights with specified color in the theme.
* If there is a focused search result, it is rendered with focused color. By calling this
* method, the focused search index will be cleared.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
*
* @param ranges the flatten ranges of the search result. null for clear.
*/
public void setSearchResultHighlights(@Nullable int... ranges) {
if (ranges == null) {
mSearchResultHighlights = null;
mHighlightPathsBogus = true;
return;
}
if (ranges.length % 2 == 1) {
throw new IllegalArgumentException(
"Flatten ranges must have even numbered elements");
}
for (int j = 0; j < ranges.length / 2; ++j) {
int start = ranges[j * 2];
int end = ranges[j * 2 + 1];
if (start > end) {
throw new IllegalArgumentException(
"Reverse range found in the flatten range: " + start + ", " + end + ""
+ " at " + j + "-th range");
}
}
mHighlightPathsBogus = true;
mSearchResultHighlights = ranges;
mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE;
invalidate();
}
/**
* Gets the current search result ranges.
*
* @see #setSearchResultHighlights(int[])
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
*
* @return a flatten search result ranges. null if not available.
*/
@Nullable
public int[] getSearchResultHighlights() {
return mSearchResultHighlights;
}
/**
* A special index used for {@link #setFocusedSearchResultIndex(int)} and
* {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result.
*/
public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1;
/**
* Sets the focused search result index.
*
* The focused search result is drawn in a focused color.
* Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result.
*
* This method must be called after setting search result ranges by
* {@link #setSearchResultHighlights(int[])}.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #setSearchResultHighlights(int[])
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
*
* @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
*/
public void setFocusedSearchResultIndex(int index) {
if (mSearchResultHighlights == null) {
throw new IllegalArgumentException("Search result range must be set beforehand.");
}
if (index < -1 || index >= mSearchResultHighlights.length / 2) {
throw new IllegalArgumentException("Focused index(" + index + ") must be larger than "
+ "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")");
}
mFocusedSearchResultIndex = index;
mHighlightPathsBogus = true;
invalidate();
}
/**
* Gets the focused search result index.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #setSearchResultHighlights(int[])
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
* @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
*/
public int getFocusedSearchResultIndex() {
return mFocusedSearchResultIndex;
}
/**
* Sets the search result highlight color.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #setSearchResultHighlights(int[])
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
* @param color a search result highlight color.
*/
public void setSearchResultHighlightColor(@ColorInt int color) {
mSearchResultHighlightColor = color;
}
/**
* Gets the search result highlight color.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #setSearchResultHighlights(int[])
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
* @return a search result highlight color.
*/
@ColorInt
public int getSearchResultHighlightColor() {
return mSearchResultHighlightColor;
}
/**
* Sets focused search result highlight color.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #setSearchResultHighlights(int[])
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
* @param color a focused search result highlight color.
*/
public void setFocusedSearchResultHighlightColor(@ColorInt int color) {
mFocusedSearchResultHighlightColor = color;
}
/**
* Gets focused search result highlight color.
*
* @attr ref android.R.styleable#TextView_searchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
* @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
* @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
*
* @see #setSearchResultHighlights(int[])
* @see #getSearchResultHighlights()
* @see #setFocusedSearchResultIndex(int)
* @see #getFocusedSearchResultIndex()
* @see #setSearchResultHighlightColor(int)
* @see #getSearchResultHighlightColor()
* @see #setFocusedSearchResultHighlightColor(int)
* @see #getFocusedSearchResultHighlightColor()
* @return a focused search result highlight color.
*/
@ColorInt
public int getFocusedSearchResultHighlightColor() {
return mFocusedSearchResultHighlightColor;
}
/**
* Highlights the text range (from inclusive start offset to exclusive end offset) to show what
* will be selected by the ongoing select handwriting gesture. While the gesture preview
@@ -8335,7 +8602,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
for (int i = 0; i < mHighlights.getSize(); ++i) {
final int[] ranges = mHighlights.getRanges(i);
final Paint paint = mHighlights.getPaint(i);
final Path path;
if (mPathRecyclePool.isEmpty()) {
path = new Path();
@@ -8363,6 +8629,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
addSearchHighlightPaths();
if (hasGesturePreviewHighlight()) {
final Path path;
if (mPathRecyclePool.isEmpty()) {
@@ -8381,6 +8649,67 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mHighlightPathsBogus = false;
}
private void addSearchHighlightPaths() {
if (mSearchResultHighlights != null) {
final Path searchResultPath;
if (mPathRecyclePool.isEmpty()) {
searchResultPath = new Path();
} else {
searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
searchResultPath.reset();
}
final Path focusedSearchResultPath;
if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) {
focusedSearchResultPath = null;
} else if (mPathRecyclePool.isEmpty()) {
focusedSearchResultPath = new Path();
} else {
focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
focusedSearchResultPath.reset();
}
boolean atLeastOnePathAdded = false;
for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) {
final int start = mSearchResultHighlights[2 * j];
final int end = mSearchResultHighlights[2 * j + 1];
if (start < end) {
if (j == mFocusedSearchResultIndex) {
mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
focusedSearchResultPath.addRect(left, top, right, bottom,
Path.Direction.CW)
);
} else {
mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
searchResultPath.addRect(left, top, right, bottom,
Path.Direction.CW)
);
atLeastOnePathAdded = true;
}
}
}
if (atLeastOnePathAdded) {
if (mSearchResultHighlightPaint == null) {
mSearchResultHighlightPaint = new Paint();
}
mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor);
mSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
mHighlightPaths.add(searchResultPath);
mHighlightPaints.add(mSearchResultHighlightPaint);
}
if (focusedSearchResultPath != null) {
if (mFocusedSearchResultHighlightPaint == null) {
mFocusedSearchResultHighlightPaint = new Paint();
}
mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor);
mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
mHighlightPaths.add(focusedSearchResultPath);
mHighlightPaints.add(mFocusedSearchResultHighlightPaint);
}
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private Path getUpdatedHighlightPath() {
Path highlight = null;
@@ -10826,6 +11155,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* This has to be called after layout. Returns true if anything changed.
*/
public boolean bringPointIntoView(int offset) {
return bringPointIntoView(offset, false);
}
/**
* Move the insertion position of the given offset into visible area of the View.
*
* If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call
* {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if
* necessary.
*
* @param offset an offset of the character.
* @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)}
* in the unfocused state. False for calling it only the View has
* the focus.
* @return true if anything changed, otherwise false.
*
* @see #bringPointIntoView(int)
*/
public boolean bringPointIntoView(@IntRange(from = 0) int offset,
boolean requestRectWithoutFocus) {
if (isLayoutRequested()) {
mDeferScroll = offset;
return false;
@@ -11002,7 +11351,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
changed = true;
}
if (isFocused()) {
if (requestRectWithoutFocus && isFocused()) {
// This offsets because getInterestingRect() is in terms of viewport coordinates, but
// requestRectangleOnScreen() is in terms of content coordinates.

View File

@@ -5138,6 +5138,15 @@
<attr name="textLocale" format="string" />
<!-- Color of the text selection highlight. -->
<attr name="textColorHighlight" />
<!-- Color of search results highlight.
This color is typically used when TextView/EditText shows search result in-app text
search invoked with Ctrl+F. -->
<attr name="searchResultHighlightColor" format="color" />
<!-- Color of focused search result highlight.
This color is typically used when TextView/EditText shows search result in-app text
search invoked with Ctrl+F. -->
<attr name="focusedSearchResultHighlightColor" format="color" />
<!-- Color of the hint text. -->
<attr name="textColorHint" />
<!-- Color of the links. -->
@@ -5215,6 +5224,14 @@
<attr name="textColor" />
<!-- Color of the text selection highlight. -->
<attr name="textColorHighlight" />
<!-- Color of search results highlight.
This color is typically used when TextView/EditText shows search result in-app text
search invoked with Ctrl+F. -->
<attr name="searchResultHighlightColor" format="color" />
<!-- Color of focused search result highlight.
This color is typically used when TextView/EditText shows search result in-app text
search invoked with Ctrl+F. -->
<attr name="focusedSearchResultHighlightColor" format="color" />
<!-- Color of the hint text. -->
<attr name="textColorHint" />
<!-- Base text color, typeface, size, and style. -->

View File

@@ -126,6 +126,8 @@
<public name="keyboardLayoutType" />
<public name="allowUpdateOwnership" />
<public name="isCredential"/>
<public name="searchResultHighlightColor" />
<public name="focusedSearchResultHighlightColor" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">