I'd like to make an EditText whose text turns red when an "error" condition is met. I'm aware that TextInputLayout has a built-in error state, but I don't want to have text beneath the input area, and I don't want to include the support design libraries in my project (which is an sdk - so others will include it). However, my EditText always shows the error state, and I'm out of ideas as to why.
My efforts thus far:
To begin, I declare a styleable thing. Seems fine.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MySpecialEditText">
<attr name="state_error" format="boolean"/>
</declare-styleable>
</resources>
Then I create a selector in my res/colors
directory. Now, I'd really prefer to only override the color if my special attribute is set. But if I have to copy over the android:state-pressed
, etc, states and every combination thereof, I can do that (and have, with the same results, see below). This file is res/color/red_on_error_dark.xml
.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/lib/com.mypackage">
<item
custom:state_error="true"
android:color="#f00" />
<item
custom:state_error="false"
android:color="@android:color/primary_text_dark"/>
<item android:color="@android:color/primary_text_dark"/>
</selector>
And I add my new stylish thing to the XML inclusion of my custom class:
<com.mypackage.MySpecialEditText
android:id="@+id/some_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/a_nice_hint"
android:inputType="number"
android:textColor="@color/red_on_error_dark"
/>
So far, so good. Now, I create my class that extends EditText:
public class MySpecialEditText extends EditText {
private static final int[] STATE_ERROR = {R.attr.state_error};
private boolean mStateError;
//... lots of other fun functionality
// Time to set my custom status
public void setStateError(boolean stateError) {
mStateError = stateError;
invalidate();
refreshDrawableState();
}
// And now, I need to add the custom state drawable.
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (mStateError) {
mergeDrawableStates(drawableState, STATE_ERROR);
}
return drawableState;
}
}
All of this is easy enough to put together. However, when I run the code, it always draws the text as red.
Things I have verified:
- That I'm correctly setting the error/no-error states.
- That the code is going through the
onCreateDrawableState
method during the non-error times! It skips themergeDrawableStates
method with the extra attribute. (I have also added anelse
clause and returned a drawableState with no added extra spaces). Even though the attribute is boolean, and should theoretically be able to accept a value of true or false, I've added a separate attr
STATE_VALID
, and changed my selector to be as follows.
I have even gone so far as to change the selector to the exhaustive:
<item custom:state_error="true" android:state_selected="true" android:color="#f00" />
<item custom:state_error="true" android:state_focused="true" android:color="#f00" />
<item custom:state_error="true" android:state_pressed="true" android:color="#f00" />
<item custom:state_error="false" android:state_selected="true" android:color="@android:color/primary_text_dark" />
<item custom:state_error="false" android:state_focused="true" android:color="@android:color/primary_text_dark" />
<item custom:state_error="false" android:state_pressed="true" android:color="@android:color/primary_text_dark" />
And I've gone one farther and combined the unnecessary-extra-state with a bunch of android:state_blah
, so I have something like 20 different cases in my selector. (Actually, in that case, it always stays white, which isn't much better).
Now, what I'd really, really like is to declare a styleable value that other people (who use my sdk) can set a value to if they so choose. But for now I'll be happy to know why the text is always red.
Edit: One totally legit workaround is simply to call setTextColor when I reach an error state in MySpecialEditText
. This is my interim solution, but it prevents someone from using my control and adding a custom:state_error=@color/some_custom_color
, or a similar value in theme of their choosing. So long as I'm calling setTextColor by hand, the error color is always my particular shade of red.
I could go further and add a new constructor or setter for the error color, but that's not a very nice solution for a library. If you use the library and instantiate my control, it will pick up all the EditText
styles you already have, so it looks native. And if you want to customize the error color, I'd love for you to be able to do that in one place for your whole app with a nice piece of XML that you can run by a designer.
Any ideas? A pre-emptive thank you to all answerers and attempted answerers.