15

I would like to have an EditText with the background as a "normal" EditText but with the error handling of a TextInputEditText (error message appearing at the bottom, and not the "!" drawable appearing).

I got something like this :

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:setError="@{viewModel.error}">

    <android.support.design.widget.TextInputEditText

        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:background="@drawable/simple_edit_text_background"
        android:ellipsize="end"
        android:inputType="textMultiLine|textNoSuggestions"
        android:text="@={viewModel.value}"

        style="@style/MyEditTextStyle" />

</android.support.design.widget.TextInputLayout>

But it seems that when I set error on the TextInputLayout it changes the background drawable(which is, in normal TextInputEditText, the underline) to the color of the error TextView.

And so this is how my EditText looks like : enter image description here

We can see it in the code of TextInputLayout within the following method :

private void updateEditTextBackground() {
    if (mEditText == null) {
        return;
    }

    Drawable editTextBackground = mEditText.getBackground();
    if (editTextBackground == null) {
        return;
    }

    ensureBackgroundDrawableStateWorkaround();

    if (android.support.v7.widget.DrawableUtils.canSafelyMutateDrawable(editTextBackground)) {
        editTextBackground = editTextBackground.mutate();
    }

    if (mErrorShown && mErrorView != null) {
        // Set a color filter of the error color
        editTextBackground.setColorFilter(
                AppCompatDrawableManager.getPorterDuffColorFilter(
                        mErrorView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
    } else if (mCounterOverflowed && mCounterView != null) {
        // Set a color filter of the counter color
        editTextBackground.setColorFilter(
                AppCompatDrawableManager.getPorterDuffColorFilter(
                        mCounterView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
    } else {
        // Else reset the color filter and refresh the drawable state so that the
        // normal tint is used
        DrawableCompat.clearColorFilter(editTextBackground);
        mEditText.refreshDrawableState();
    }
}

The code's block that update the backgroud color is here :

if (mErrorShown && mErrorView != null) {
    // Set a color filter of the error color
    editTextBackground.setColorFilter(
            AppCompatDrawableManager.getPorterDuffColorFilter(
                    mErrorView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
}

Because this method is private I can't override it and because I still wants my error TextView's color to be red I can't see any solution so far. Any idea?

One solution could maybe to reset background color to its default value just after setError would have been called but is their any callback with a method like onError that will be fired once an error is set to a TextView/EditText?

MHogge
  • 5,408
  • 15
  • 61
  • 104

5 Answers5

40

I manage to resolve this myself by overriding TextInputLayout like this :

public class NoChangingBackgroundTextInputLayout extends TextInputLayout {
    public NoChangingBackgroundTextInputLayout(Context context) {
        super(context);
    }

    public NoChangingBackgroundTextInputLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NoChangingBackgroundTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setError(@Nullable CharSequence error) {
        ColorFilter defaultColorFilter = getBackgroundDefaultColorFilter();
        super.setError(error);
        //Reset EditText's background color to default.
        updateBackgroundColorFilter(defaultColorFilter);
    }

    @Override
    protected void drawableStateChanged() {
        ColorFilter defaultColorFilter = getBackgroundDefaultColorFilter();
        super.drawableStateChanged();
        //Reset EditText's background color to default.
        updateBackgroundColorFilter(defaultColorFilter);
    }

    private void updateBackgroundColorFilter(ColorFilter colorFilter) {
        if(getEditText() != null && getEditText().getBackground() != null)
            getEditText().getBackground().setColorFilter(colorFilter);
    }

    @Nullable
    private ColorFilter getBackgroundDefaultColorFilter() {
        ColorFilter defaultColorFilter = null;
        if(getEditText() != null && getEditText().getBackground() != null)
            defaultColorFilter = DrawableCompat.getColorFilter(getEditText().getBackground());
        return defaultColorFilter;
    }
}

So as we can see it, it reset the EditText's background to its default color after setError has been called but also in the method drawableStateChanged() because the red color filter is set when losing/getting the focus on an EditText with error too.

I'm not convinced that this is the best solution but if I don't get any better solutions, I'll mark it as resolved in meantime.

MHogge
  • 5,408
  • 15
  • 61
  • 104
  • 9
    Nice solution. Just another time we need to use a hack to overcome Android's bad design. – Stan Mots May 16 '17 at 15:08
  • @Storix that's overriding! – Shubham AgaRwal Jun 21 '17 at 07:45
  • @Shubham No, I'm not talking about overriding. I was referring to the `setError` and `drawableStateChanged` methods. Look, we need to reset the color in two different places. We wouldn't need to introduce this overhead and boilerplate code if `TextInputLayout` just had an option to configure the color for the error state. – Stan Mots Jun 21 '17 at 10:52
  • I was facing the same problem from yesterday. This helped me to resolve that red background issue. Thanks – Mahesh Gaikwad Aug 18 '18 at 11:48
  • I am having same problem, but in my case i want red error if there is an error. How to ? – Moinkhan Sep 28 '18 at 11:28
  • Can we somehow change the color of border to red on error? Any suggestions? – Ayusch Aug 10 '19 at 07:09
8

I had same problem; after searching and hit & run I found so simple way to solve this problem-

Try this easiest way -

               <android.support.design.widget.TextInputLayout
                    android:id="@+id/til_description"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    app:errorText="@{feedbackViewModel.descError}"
                    >

                    <EditText
                        style="@style/TextInputLayoutEditText"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:background="@drawable/desc_field_selector"
                        **android:paddingTop="10dp"**
                        **android:paddingBottom="7dp"**
                        android:gravity="top|left"
                        android:hint="@string/description"
                        android:inputType="textMultiLine"
                        android:lines="4"
                        android:onTextChanged="@{(text, start, before, count) -> feedbackViewModel.onDescriptionTextChanged(text)}"
                        android:scrollHorizontally="false"
                        android:scrollbarStyle="insideInset"
                        android:scrollbars="vertical"
                        android:text="@={feedbackViewModel.description}"/>

                </android.support.design.widget.TextInputLayout>

And android:background="@drawable/desc_field_selector" -

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/rectangle_blue_border_background"
          android:state_pressed="true"/>
    <item android:drawable="@drawable/rectangle_blue_border_background"
          android:state_enabled="true"
          android:state_focused="true"
          android:state_window_focused="true"/>
    <item android:drawable="@drawable/rectangle_black_border_background"/>
</selector>

Now the original shape (@drawable/rectangle_black_border_background) will be like -

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    **<item android:state_focused="false" android:top="5dp">**
        <shape>
            **<solid android:color="@android:color/transparent"/>**
            <stroke android:width="1dp" android:color="@android:color/secondary_text_light"/>
            <corners android:radius="5dp"/>
        </shape>
    </item>
</layer-list>

And the original shape (@drawable/rectangle_blue_border_background) will be like -

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    **<item android:state_focused="true" android:top="5dp">**
        <shape>
            **<solid android:color="@android:color/transparent"/>**
            <stroke android:width="@dimen/one_dip" android:color="@color/colorAccent"/>
            <corners android:radius="5dp"/>
        </shape>
    </item>
</layer-list>

Note- The ** lines are too important -

  1. paddingTop="10dp" and android:paddingBottom="7dp" -> will move the floating label up enough from the border shape otherwise it won't look well.
  2. android:top="5dp" - are important for moving floating label enough to rect shape.
  3. solid android:color="@android:color/transparent" - this solid shape color will be transparent so when error will occur you will see just a transparent color. Have a look -

**Note - ** change your padding value and top value from item and the solid color according to need (You can also remove solid color line also).

enter image description here

That's it :-) Happy coding +1

Bajrang Hudda
  • 3,028
  • 1
  • 36
  • 63
2

If you have custom background for your edittext view, then create a <shape.../> in your drawable folder & set it's <solid../> attribute color to transparent. For reference, here is the shape with name : shape_input_layout_edittext.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:innerRadius="200dp"
    android:shape="rectangle">
    <stroke
        android:width="1dp"
        android:color="#e2e2e2"></stroke>
    <solid android:color="#00000000"></solid>
    <corners android:radius="2dp"></corners>


</shape>

Now, inside your layout xml file, use it like :

<com.google.android.material.textfield.TextInputLayout
                android:id="@+id/inputLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:hintEnabled="false">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/edtText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"                    
  android:background="@drawable/shape_input_layout_edittext" ..../>
</com.google.android.material.textfield.TextInputLayout>

That's it, now whenever error will populate, edittext's background will have no colour. Happy Coding!

Abhijeet
  • 501
  • 4
  • 7
  • I was struggling to find a solution for this. But thanks, this solution is so simple and worked like a charm. – Sunita Jul 19 '21 at 19:50
0

Arranging the accepted solution with https://stackoverflow.com/a/40379564/2914140 and https://stackoverflow.com/a/44744941/2914140, I wrote another class. When an EditText has a special background (background_1) it changes to a background_2 on error. When the error disappears, it again returns to background_1. No red fill is executed.

public class YourTextInputLayout extends TextInputLayout {

    private Drawable drawable1; // Normal background.
    private Drawable drawable2; // Error background.

    public YourTextInputLayout(Context context) {
        super(context);
    }

    public YourTextInputLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public YourTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        replaceBackground();
    }

    @Override
    public void setError(@Nullable final CharSequence error) {
        super.setError(error);

        replaceBackground();
    }

    public void setDrawable1(Drawable drawable) {
        this.drawable1 = drawable;
    }

    public void setDrawable2(Drawable drawable) {
        this.drawable2 = drawable;
    }

    private void replaceBackground() {
        EditText editText = getEditText();
        if (editText != null) {
            editText.setBackground(isErrorEnabled() ? drawable2 : drawable1);
            Drawable drawable = editText.getBackground();
            if (drawable != null) {
                drawable.clearColorFilter();
            }
        }
    }
}

In your activity/fragment call after initialization in onCreate() / onCreateView():

YourTextInputLayout inputLayout = ...;
inputLayout.setDrawable1(ContextCompat.getDrawable(getContext(), R.drawable.background_1));
inputLayout.setDrawable2(ContextCompat.getDrawable(getContext(), R.drawable.background_2));

In your XML layout call it by:

<com.example.package.YourTextInputLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <EditText
        ...
CoolMind
  • 26,736
  • 15
  • 188
  • 224
0

A more easier solution I would suggest is by creating a drawable resource file (@drawable/rectangular_border) :-

<?xml version="1.0" encoding="utf-8"?>    
<shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    
        <stroke
            android:width="2dp"
            android:color="@color/colorAccent"        />    
        <corners
            android:radius="10dp"
            />
        <solid
            android:color="@android:color/transparent"
            />
    
    </shape>

And in you XML file replace the background of you TextInputEditText with the following resource file i.e -

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:setError="@{viewModel.error}">

    <android.support.design.widget.TextInputEditText

        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:background="@drawable/rectangular_border"
        android:ellipsize="end"
        android:inputType="textMultiLine|textNoSuggestions"
        android:text="@={viewModel.value}"

        style="@style/MyEditTextStyle" />

</android.support.design.widget.TextInputLayout>

In this way you will be the background color won't change completely, only the borders on errors or counter overflow. Hope this solution solves your problem :).

Ezio Blaze
  • 31
  • 8