-
Notifications
You must be signed in to change notification settings - Fork 55
Custom states
Sometimes it's useful to have additional state attributes supported by a view. The best example is an invalid state attribute. It could be used to signalize a wrong pin in EditText
, unchecked privacy policy checkbox, etc.
Here's an example of a state - two state attributes grouped in an array:
int[] state = new int[] {
-android.R.attr.state_enabled,
android.R.attr.state_selected
};
If a state uses only one attribute, it still has to be in an array.
State attributes can express three situations. Positive attributes mean that the state attribute is there (active). Negative (with the minus sign) attributes mean that the state attribute is absent (inactive). Neutral attributes (not specified) mean that the state doesn't care about that attribute.
The example array describes a disabled, selected state. Let's imagine that the following selector was used as text color and the view is in the state described by the example array.
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/guide_textColorPrimary_dark" android:state_selected="true" />
<item android:color="@color/guide_textColorSecondary_dark" />
</selector>
The text would be drawn in guide_textColorPrimary_dark
, because the selector specifies that color for selected states. Being enabled is not important. During drawing the current state, expressed as an array, is matched with selectors and values for the matching states are used.
To add a custom state attribute you need to declare it first:
<attr name="guide_state_invalid" format="boolean" />
In the class, that should support that new state, add a corresponding field and a getter/setter pair. It's important to call refreshDrawableState()
when the state was changed to refresh the view.
class InvalidEditText extends EditText{
private boolean valid;
public void setValid(boolean valid) {
if (this.valid == valid)
return;
this.valid = valid;
refreshDrawableState();
}
public boolean isValid() {
return valid;
}
}
The most interesting part is how to turn that valid
field into a state array. It's done in onCreateDrawableState()
. The easiest way is to append the new state attribute to the state array got from the superclass.
class InvalidEditText extends EditText {
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (!isValid()) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
drawableState[drawableState.length - 1] = R.attr.guide_state_invalid;
return drawableState;
}
return super.onCreateDrawableState(extraSpace);
}
}
Now the new state attribute can be used in a selector, just like the original attributes:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/guide_colorInvalid" app:guide_state_invalid="true" />
<item android:color="@color/guide_textColorPrimary_dark" android:state_selected="true" />
<item android:color="@color/guide_textColorSecondary_dark" />
</selector>