@@ -349,6 +349,7 @@ public interface OnEndIconChangedListener {
349349 private boolean hasEndIconTintMode ;
350350 private Drawable endIconDummyDrawable ;
351351 private Drawable originalEditTextEndDrawable ;
352+ private final CheckableImageButton errorIconView ;
352353
353354 private ColorStateList defaultHintTextColor ;
354355 private ColorStateList focusedTextColor ;
@@ -522,6 +523,32 @@ public TextInputLayout(Context context, @Nullable AttributeSet attrs, int defSty
522523 final int errorTextAppearance =
523524 a .getResourceId (R .styleable .TextInputLayout_errorTextAppearance , 0 );
524525 final boolean errorEnabled = a .getBoolean (R .styleable .TextInputLayout_errorEnabled , false );
526+ // Initialize error icon view.
527+ errorIconView =
528+ (CheckableImageButton )
529+ LayoutInflater .from (getContext ())
530+ .inflate (R .layout .design_text_input_end_icon , inputFrame , false );
531+ inputFrame .addView (errorIconView );
532+ errorIconView .setVisibility (GONE );
533+ if (a .hasValue (R .styleable .TextInputLayout_errorIconDrawable )) {
534+ setErrorIconDrawable (a .getDrawable (R .styleable .TextInputLayout_errorIconDrawable ));
535+ }
536+ if (a .hasValue (R .styleable .TextInputLayout_errorIconTint )) {
537+ setErrorIconTintList (
538+ MaterialResources .getColorStateList (
539+ context , a , R .styleable .TextInputLayout_errorIconTint ));
540+ }
541+ if (a .hasValue (R .styleable .TextInputLayout_errorIconTintMode )) {
542+ setErrorIconTintMode (
543+ ViewUtils .parseTintMode (
544+ a .getInt (R .styleable .TextInputLayout_errorIconTintMode , -1 ), null ));
545+ }
546+ errorIconView .setContentDescription (
547+ getResources ().getText (R .string .error_icon_content_description ));
548+ ViewCompat
549+ .setImportantForAccessibility (errorIconView , ViewCompat .IMPORTANT_FOR_ACCESSIBILITY_NO );
550+ errorIconView .setClickable (false );
551+ errorIconView .setFocusable (false );
525552
526553 final int helperTextTextAppearance =
527554 a .getResourceId (R .styleable .TextInputLayout_helperTextTextAppearance , 0 );
@@ -1051,6 +1078,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {}
10511078
10521079 startIconView .bringToFront ();
10531080 endIconView .bringToFront ();
1081+ errorIconView .bringToFront ();
10541082 dispatchOnEditTextAttached ();
10551083
10561084 // Update the label visibility with no animation, but force a state change
@@ -1422,6 +1450,75 @@ public void setError(@Nullable final CharSequence errorText) {
14221450 }
14231451 }
14241452
1453+ /**
1454+ * Set the drawable to use for the error icon.
1455+ *
1456+ * @param resId resource id of the drawable to set, or 0 to clear the icon
1457+ * @attr ref com.google.android.material.R.styleable#TextInputLayout_errorIconDrawable
1458+ */
1459+ public void setErrorIconDrawable (@ DrawableRes int resId ) {
1460+ setErrorIconDrawable (resId != 0 ? AppCompatResources .getDrawable (getContext (), resId ) : null );
1461+ }
1462+
1463+ /**
1464+ * Set the drawable to use for the error icon.
1465+ *
1466+ * @param errorIconDrawable Drawable to set, may be null to clear the icon
1467+ * @attr ref com.google.android.material.R.styleable#TextInputLayout_errorIconDrawable
1468+ */
1469+ public void setErrorIconDrawable (@ Nullable Drawable errorIconDrawable ) {
1470+ errorIconView .setImageDrawable (errorIconDrawable );
1471+ setErrorIconVisible (errorIconDrawable != null );
1472+ }
1473+
1474+ /**
1475+ * Returns the drawable currently used for the error icon.
1476+ *
1477+ * @see #setErrorIconDrawable(Drawable)
1478+ * @attr ref com.google.android.material.R.styleable#TextInputLayout_errorIconDrawable
1479+ */
1480+ @ Nullable
1481+ public Drawable getErrorIconDrawable () {
1482+ return errorIconView .getDrawable ();
1483+ }
1484+
1485+ /**
1486+ * Applies a tint to the error icon drawable.
1487+ *
1488+ * @param errorIconTintList the tint to apply, may be null to clear tint
1489+ * @attr ref com.google.android.material.R.styleable#TextInputLayout_errorIconTint
1490+ */
1491+ public void setErrorIconTintList (@ Nullable ColorStateList errorIconTintList ) {
1492+ Drawable icon = errorIconView .getDrawable ();
1493+ if (icon != null ) {
1494+ icon = DrawableCompat .wrap (icon ).mutate ();
1495+ DrawableCompat .setTintList (icon , errorIconTintList );
1496+ }
1497+
1498+ if (errorIconView .getDrawable () != icon ) {
1499+ errorIconView .setImageDrawable (icon );
1500+ }
1501+ }
1502+
1503+ /**
1504+ * Specifies the blending mode used to apply tint to the end icon drawable. The default mode is
1505+ * {@link PorterDuff.Mode#SRC_IN}.
1506+ *
1507+ * @param errorIconTintMode the blending mode used to apply the tint, may be null to clear tint
1508+ * @attr ref com.google.android.material.R.styleable#TextInputLayout_errorIconTintMode
1509+ */
1510+ public void setErrorIconTintMode (@ Nullable PorterDuff .Mode errorIconTintMode ) {
1511+ Drawable icon = errorIconView .getDrawable ();
1512+ if (icon != null ) {
1513+ icon = DrawableCompat .wrap (icon ).mutate ();
1514+ DrawableCompat .setTintMode (icon , errorIconTintMode );
1515+ }
1516+
1517+ if (errorIconView .getDrawable () != icon ) {
1518+ errorIconView .setImageDrawable (icon );
1519+ }
1520+ }
1521+
14251522 /**
14261523 * Whether the character counter functionality is enabled or not in this layout.
14271524 *
@@ -2270,7 +2367,7 @@ public void setEndIconVisible(boolean visible) {
22702367 * @see #setEndIconVisible(boolean)
22712368 */
22722369 public boolean isEndIconVisible () {
2273- return endIconView .getVisibility () == View .VISIBLE ;
2370+ return endIconView .getParent () != null && endIconView . getVisibility () == View .VISIBLE ;
22742371 }
22752372
22762373 /**
@@ -2758,15 +2855,16 @@ private boolean updateIconDummyDrawables() {
27582855 updatedIcon = true ;
27592856 }
27602857
2761- // Update end icon drawable if needed.
2762- if (hasEndIcon () && isEndIconVisible () && endIconView .getMeasuredWidth () > 0 ) {
2858+ // Update end icon or error icon drawable if needed.
2859+ CheckableImageButton iconView = getEndIconToUpdateDummyDrawable ();
2860+ if (iconView != null && iconView .getMeasuredWidth () > 0 ) {
27632861 if (endIconDummyDrawable == null ) {
27642862 endIconDummyDrawable = new ColorDrawable ();
27652863 int right =
2766- endIconView .getMeasuredWidth ()
2864+ iconView .getMeasuredWidth ()
27672865 - editText .getPaddingRight ()
27682866 + MarginLayoutParamsCompat .getMarginStart (
2769- ((MarginLayoutParams ) endIconView .getLayoutParams ()));
2867+ ((MarginLayoutParams ) iconView .getLayoutParams ()));
27702868 endIconDummyDrawable .setBounds (0 , 0 , right , 1 );
27712869 }
27722870 final Drawable [] compounds = TextViewCompat .getCompoundDrawablesRelative (editText );
@@ -2791,6 +2889,17 @@ private boolean updateIconDummyDrawables() {
27912889 return updatedIcon ;
27922890 }
27932891
2892+ @ Nullable
2893+ private CheckableImageButton getEndIconToUpdateDummyDrawable () {
2894+ if (errorIconView .getVisibility () == VISIBLE ) {
2895+ return errorIconView ;
2896+ } else if (hasEndIcon () && isEndIconVisible ()) {
2897+ return endIconView ;
2898+ } else {
2899+ return null ;
2900+ }
2901+ }
2902+
27942903 private void applyIconTint (
27952904 CheckableImageButton iconView ,
27962905 boolean hasIconTintList ,
@@ -2987,6 +3096,8 @@ void updateTextInputBoxState() {
29873096 tintEndIconOnError (
29883097 indicatorViewController .errorShouldBeShown ()
29893098 && getEndIconDelegate ().shouldTintIconOnError ());
3099+ setErrorIconVisible (
3100+ getErrorIconDrawable () != null && indicatorViewController .errorShouldBeShown ());
29903101
29913102 // Update the text box's stroke width based on the current state.
29923103 if ((isHovered || hasFocus ) && isEnabled ()) {
@@ -3009,6 +3120,25 @@ void updateTextInputBoxState() {
30093120 applyBoxAttributes ();
30103121 }
30113122
3123+ private void setErrorIconVisible (boolean errorIconVisible ) {
3124+ int newErrorIconVisibility = errorIconVisible ? VISIBLE : GONE ;
3125+ if (errorIconView .getVisibility () == newErrorIconVisibility ) {
3126+ return ;
3127+ }
3128+
3129+ errorIconView .setVisibility (newErrorIconVisibility );
3130+
3131+ if (errorIconVisible ) {
3132+ inputFrame .removeView (endIconView );
3133+ } else if (endIconView .getParent () == null ) {
3134+ inputFrame .addView (endIconView );
3135+ }
3136+
3137+ if (!hasEndIcon ()) {
3138+ updateIconDummyDrawables ();
3139+ }
3140+ }
3141+
30123142 private void expandHint (boolean animate ) {
30133143 if (animator != null && animator .isRunning ()) {
30143144 animator .cancel ();
0 commit comments