Skip to content

Commit

Permalink
[TextInputLayout] Added API to set cursor colors for API 28+.
Browse files Browse the repository at this point in the history
This is useful if the cursor color should be set in runtime, where setting ?attr/colorControlActivated for the edit text is not possible. Using the added APIs will take precedence over the value of ?attr/colorControlActivated.

This API is limited to APIs 28+ due to the framework getTextCursorDrawable() method being 28+.

Resolves #3255
Resolves #3311

PiperOrigin-RevId: 521448134
  • Loading branch information
leticiarossi authored and paulfthomas committed Apr 10, 2023
1 parent a0d0b53 commit c598ccd
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 26 deletions.
28 changes: 16 additions & 12 deletions docs/components/TextField.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,6 @@ indicator, optional helper/error text and optional leading/trailing icons.
7. Helper/error/counter text
8. Prefix/suffix/placeholder (not shown)

**Note:** All the attributes in the tables below should be set on the
`TextInputLayout`, with the exception of the input text attributes, which should
be set on the `TextInputEditText`.

#### Container attributes

Element | Attribute | Related method(s) | Default value
Expand Down Expand Up @@ -489,7 +485,7 @@ Element | Attribute | Related method(s)
**Note:** The `android:hint` should always be set on the `TextInputLayout`
instead of on the `EditText` in order to avoid unintended behaviors.

#### Input text attributes
#### Input text attributes (set on the `TextInputEditText`)

Element | Attribute | Related method(s) | Default value
------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------ | -------------
Expand All @@ -499,7 +495,13 @@ Element | Attribute
**Cursor color** | N/A (color comes from the theme attr `?attr/colorControlActivated`) | N/A | `?attr/colorPrimary`
**Text highlight color** | N/A (color comes from the theme attr `?android:attr/textColorHighlight`) | N/A | [`@color/m3_highlighted_text`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/color/res/color/m3_highlighted_text.xml)

**Note:** The input text attributes should be set on the `TextInputEditText`.
#### Input text attributes (set on the `TextInputLayout`)

Element | Attribute | Related method(s) | Default value
------------------------ |------------------------------------------|-----------------------------------------------------------------------| -------------
**Cursor color** | `app:cursorColor` on API levels 28+ | `setCursorColor`<br/>`getCursorColor`<br/>on API levels 28+ | `@null` (uses `?attr/colorControlActivated` by default)
**Cursor error color** | `app:cursorErrorColor` on API levels 28+ | `setCursorErrorColor`<br/>`getCursorErrorColor`<br/>on API levels 28+ | `?attr/colorError` on API levels 28+, `?attr/colorControlActivated` otherwise


#### Trailing icon attributes

Expand Down Expand Up @@ -636,10 +638,6 @@ helper/error text and optional leading/trailing icons.
6. Helper/error/counter text
7. Prefix/suffix/placeholder (not shown)

**Note:** All the attributes in the tables below should be set on the
`TextInputLayout`, with the exception of the input text attributes, which should
be set on the `TextInputEditText`.

#### Container attributes

Element | Attribute | Related method(s) | Default value
Expand Down Expand Up @@ -672,7 +670,7 @@ Element | Attribute | Related method(s)
**Note:** The `android:hint` should always be set on the `TextInputLayout`
instead of on the `EditText` in order to avoid unintended behaviors.

#### Input text attributes
#### Input text attributes (set on the `TextInputEditText`)

Element | Attribute | Related method(s) | Default value
------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------ | -------------
Expand All @@ -682,7 +680,13 @@ Element | Attribute
**Cursor color** | N/A (color comes from the theme attr `?attr/colorControlActivated`) | N/A | `?attr/colorPrimary`
**Text highlight color** | N/A (color comes from the theme attr `?android:attr/textColorHighlight`) | N/A | [`@color/m3_highlighted_text`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/color/res/color/m3_highlighted_text.xml)

**Note:** The input text attributes should be set on the `TextInputEditText`.
#### Input text attributes (set on the `TextInputLayout`)

Element | Attribute | Related method(s) | Default value
------------------------ |------------------------------------------|-----------------------------------------------------------------------| -------------
**Cursor color** | `app:cursorColor` on API levels 28+ | `setCursorColor`<br/>`getCursorColor`<br/>on API levels 28+ | `@null` (uses `?attr/colorControlActivated` by default)
**Cursor error color** | `app:cursorErrorColor` on API levels 28+ | `setCursorErrorColor`<br/>`getCursorErrorColor`<br/>on API levels 28+ | `?attr/colorError` on API levels 28+, `?attr/colorControlActivated` otherwise


#### Trailing icon attributes

Expand Down
115 changes: 101 additions & 14 deletions lib/java/com/google/android/material/textfield/TextInputLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
Expand Down Expand Up @@ -258,6 +259,9 @@ public interface LengthCounter {
@Nullable private ColorStateList counterTextColor;
@Nullable private ColorStateList counterOverflowTextColor;

@Nullable private ColorStateList cursorColor;
@Nullable private ColorStateList cursorErrorColor;

private boolean hintEnabled;
private CharSequence hint;

Expand Down Expand Up @@ -610,6 +614,9 @@ public TextInputLayout(@NonNull Context context, @Nullable AttributeSet attrs, i
setHintTextAppearance(a.getResourceId(R.styleable.TextInputLayout_hintTextAppearance, 0));
}

cursorColor = a.getColorStateList(R.styleable.TextInputLayout_cursorColor);
cursorErrorColor = a.getColorStateList(R.styleable.TextInputLayout_cursorErrorColor);

final int errorTextAppearance =
a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0);
final CharSequence errorContentDescription =
Expand Down Expand Up @@ -1542,6 +1549,10 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {}
this.isProvidingHint = true;
}

if (VERSION.SDK_INT >= VERSION_CODES.Q) {
updateCursorColor();
}

if (counterView != null) {
updateCounter(this.editText.getText());
}
Expand Down Expand Up @@ -2530,6 +2541,80 @@ public int getPlaceholderTextAppearance() {
return placeholderTextAppearance;
}

/**
* Sets the cursor color. Using this method will take precedence over using the
* value of {@code ?attr/colorControlActivated}.
*
* <p>Note: This method only has effect on API levels 28+. On lower API levels
* {@code ?attr/colorControlActivated} will be used for the cursor color.
*
* @param cursorColor the cursor color to be set
* @see #getCursorColor
* @see #setCursorErrorColor
* @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorColor
*/
@RequiresApi(VERSION_CODES.Q)
public void setCursorColor(@Nullable ColorStateList cursorColor) {
if (this.cursorColor != cursorColor) {
this.cursorColor = cursorColor;
updateCursorColor();
}
}

/**
* Returns the cursor color. It will return the value of {@code app:cursorColor} if set, or
* <code>null</code> otherwise.
*
* <p>Note: This value only has effect on API levels 28+. On lower API levels
* {@code ?attr/colorControlActivated} will be used for the cursor color.
*
* @see #setCursorColor
* @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorColor
*/
@Nullable
@RequiresApi(VERSION_CODES.Q)
public ColorStateList getCursorColor() {
return cursorColor;
}

/**
* Sets the cursor color when an error is being displayed. If null, the cursor doesn't change its
* color when the text field is in an error state.
*
* <p>Note: This method only has effect on API levels 28+. On lower API levels
* {@code ?attr/colorControlActivated} will be used for the cursor color.
*
* @param cursorErrorColor the error color to use for the cursor
* @see #getCursorErrorColor
* @see #setCursorColor
* @see #setError(CharSequence)
* @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorErrorColor
*/
@RequiresApi(VERSION_CODES.Q)
public void setCursorErrorColor(@Nullable ColorStateList cursorErrorColor) {
if (this.cursorErrorColor != cursorErrorColor) {
this.cursorErrorColor = cursorErrorColor;
if (isOnError()) {
updateCursorColor();
}
}
}

/**
* Returns the cursor error color.
*
* <p>Note: This value only has effect on API levels 28+. On lower API levels
* {@code ?attr/colorControlActivated} will be used for the cursor color.
*
* @see #setCursorErrorColor
* @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorErrorColor
*/
@Nullable
@RequiresApi(VERSION_CODES.Q)
public ColorStateList getCursorErrorColor() {
return cursorErrorColor;
}

/**
* Sets prefix text that will be displayed in the input area when the hint is collapsed before
* text is entered. If the {@code prefix} is {@code null}, any previous prefix text will be hidden
Expand Down Expand Up @@ -4232,7 +4317,6 @@ void updateTextInputBoxState() {

final boolean hasFocus = isFocused() || (editText != null && editText.hasFocus());
final boolean isHovered = isHovered() || (editText != null && editText.isHovered());
final boolean isOnError = shouldShowError() || (counterView != null && counterOverflowed);

// Update the text box's stroke color based on the current state.
if (!isEnabled()) {
Expand All @@ -4258,7 +4342,7 @@ void updateTextInputBoxState() {
}

if (VERSION.SDK_INT >= VERSION_CODES.Q) {
updateCursorColor(isOnError);
updateCursorColor();
}

endLayout.onTextInputBoxStateUpdated();
Expand Down Expand Up @@ -4298,6 +4382,10 @@ void updateTextInputBoxState() {
applyBoxAttributes();
}

private boolean isOnError() {
return shouldShowError() || (counterView != null && counterOverflowed);
}

private void updateStrokeErrorColor(boolean hasFocus, boolean isHovered) {
int defaultStrokeErrorColor = strokeErrorColor.getDefaultColor();
int hoveredStrokeErrorColor =
Expand All @@ -4317,23 +4405,22 @@ private void updateStrokeErrorColor(boolean hasFocus, boolean isHovered) {
}
}

@TargetApi(VERSION_CODES.Q)
private void updateCursorColor(boolean isOnError) {
ColorStateList cursorColor =
MaterialColors.getColorStateListOrNull(getContext(), R.attr.colorControlActivated);
if (editText == null || editText.getTextCursorDrawable() == null || cursorColor == null) {
// If there's no cursor or if its color is null, return.
@RequiresApi(VERSION_CODES.Q)
private void updateCursorColor() {
ColorStateList color = cursorColor != null
? cursorColor
: MaterialColors.getColorStateListOrNull(getContext(), R.attr.colorControlActivated);

if (editText == null || editText.getTextCursorDrawable() == null) {
// If there's no cursor, return.
return;
}

Drawable cursorDrawable = editText.getTextCursorDrawable();
if (isOnError) {
// Use the stroke error color for the cursor error color, or the box stroke color if
// strokeErrorColor is null.
cursorColor =
strokeErrorColor != null ? strokeErrorColor : ColorStateList.valueOf(boxStrokeColor);
if (isOnError() && cursorErrorColor != null) {
color = cursorErrorColor;
}
DrawableCompat.setTintList(cursorDrawable, cursorColor);
DrawableCompat.setTintList(cursorDrawable, color);
}

private void expandHint(boolean animate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
<public name="endIconTintMode" type="attr"/>
<public name="endIconMinSize" type="attr"/>
<public name="endIconScaleType" type="attr" />
<public name="cursorColor" type="attr"/>
<public name="cursorErrorColor" type="attr"/>
<!-- Deprecated. Use endIconMode instead. -->
<public name="passwordToggleEnabled" type="attr"/>
<!--Deprecated. Use endIconDrawable instead. -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@
<enum name="screen" value="15"/>
</attr>

<!-- Color for the cursor. If set, this takes precedence over
?attr/colorControlActivated set for the edit text.
Note: Only has effect on API levels 28+. -->
<attr name="cursorColor" format="color"/>
<!-- Error color for the cursor.
If null, the cursor color won't change on error.
Note: Only has effect on API levels 28+. -->
<attr name="cursorErrorColor" format="color"/>

<!-- Whether the layout is laid out as if the character counter will be displayed. -->
<attr name="counterEnabled" format="boolean"/>
<!-- The max length to display in the character counter. -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
<item name="boxStrokeErrorColor">@color/mtrl_error</item>
<item name="boxStrokeWidth">@dimen/mtrl_textinput_box_stroke_width_default</item>
<item name="boxStrokeWidthFocused">@dimen/mtrl_textinput_box_stroke_width_focused</item>
<!-- By default the cursor color is controlled via ?attr/colorControlActivated. -->
<item name="cursorColor">@null</item>
<item name="cursorErrorColor">@color/mtrl_error</item>

<item name="counterTextAppearance">?attr/textAppearanceCaption</item>
<item name="counterOverflowTextAppearance">?attr/textAppearanceCaption</item>
Expand Down Expand Up @@ -356,6 +359,9 @@
<item name="placeholderTextAppearance">@macro/m3_comp_outlined_text_field_input_text_type</item>
<item name="prefixTextAppearance">?attr/textAppearanceTitleMedium</item>
<item name="suffixTextAppearance">?attr/textAppearanceTitleMedium</item>
<!-- By default the cursor color is controlled via ?attr/colorControlActivated. -->
<item name="cursorColor">@null</item>
<item name="cursorErrorColor">@macro/m3_comp_outlined_text_field_error_outline_color</item>
<item name="shapeAppearance">@macro/m3_comp_outlined_text_field_container_shape</item>
<item name="shapeAppearanceOverlay">@null</item>
</style>
Expand Down Expand Up @@ -407,6 +413,9 @@
<item name="placeholderTextAppearance">@macro/m3_comp_filled_text_field_input_text_type</item>
<item name="prefixTextAppearance">?attr/textAppearanceTitleMedium</item>
<item name="suffixTextAppearance">?attr/textAppearanceTitleMedium</item>
<!-- By default the cursor color is controlled via ?attr/colorControlActivated. -->
<item name="cursorColor">@null</item>
<item name="cursorErrorColor">@macro/m3_comp_filled_text_field_error_active_indicator_color</item>
<item name="shapeAppearance">@macro/m3_comp_filled_text_field_container_shape</item>
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Material3.Corner.Top</item>
</style>
Expand Down

0 comments on commit c598ccd

Please sign in to comment.