'Why don't custom color states defined in onCreateDrawableState work?

I have a custom class McEditText that merges a constraint layout with a background xml drawable colored stroke.

(There's more to McEditText than Im showing here. The ellipses are the excluded code. So the problem is not the merge)

<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    ...

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/edit_border"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/mc_edit_text_border"
        android:paddingTop="3dp"
        android:paddingBottom="3dp"
        android:paddingStart="8dp"
        android:paddingEnd="8dp"
        app:layout_constraintTop_toBottomOf="@id/edit_label"
        app:layout_constraintStart_toStartOf="parent">

        ...

    </androidx.constraintlayout.widget.ConstraintLayout>

    ...

</merge>

mc_edit_text_border.xml in my drawables folder

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="@android:color/transparent" />

    <stroke
        android:width="2dp"
        android:color="@drawable/mc_edit_text_bordercolor" />

</shape>

mc_edit_text_bordercolor.xml in my drawables folder

<?xml version="1.0" encoding="utf-8"?>
<selector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:color="@color/InputDisabledBorderColor" android:state_enabled="false"/>
    <item android:color="@color/InputReadonlyBorderColor" app:state_readonly="true"/>

    <item android:color="@color/InputErrorBorderColor" app:state_error="true" android:state_selected="false"/>
    <item android:color="@color/InputErrorActiveBorderColor" app:state_error="true" android:state_selected="true"/>

    <item android:color="@color/InputSuccessBorderColor" app:state_success="true" android:state_selected="false"/>
    <item android:color="@color/InputSuccessActiveBorderColor" app:state_success="true" android:state_selected="true"/>

    <item android:color="@color/InputActiveBorderColor" android:state_selected="true"/>
    <item android:color="@color/InputBorderColor" android:state_selected="false"/>
</selector>

So far its all pretty standard stuff. The complexity is that I've added a few extra states, which you can see in the above selector xml: state_error, state_readonly and state_success. I read about how to do that here.

So in my attrs.xml...

<resources>
    <attr name="state_error" format="boolean"/>
    <attr name="state_success" format="boolean"/>
    <attr name="state_readonly" format="boolean"/>

    <declare-styleable name="McEditText">
        <attr name="state_error"/>
        <attr name="state_success"/>
        <attr name="state_readonly"/>

        <attr name="editTextKind" format="enum">
            <enum name="normal" value="0" />
            <enum name="error" value="1" />
            <enum name="success" value="2" />
            <enum name="readonly" value="3" />
            <enum name="disabled" value="4" />
        </attr>

    </declare-styleable>

</resources>

Then in my McEditText.kt I do this...

class McEditText @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet
) : ConstraintLayout(context, attrs) {
 
    private val stateError = intArrayOf(R.attr.state_error)
    private val stateSuccess = intArrayOf(R.attr.state_success)
    private val stateReadOnly = intArrayOf(R.attr.state_readonly)

    enum class Kind {
        NORMAL, ERROR, SUCCESS, READONLY, DISABLED
    }

    init {
        context.theme.obtainStyledAttributes(attrs, R.styleable.McEditText, 0, 0).apply {
            try {
                kind = Kind.values()[getInteger(R.styleable.McEditText_editTextKind, Kind.NORMAL.ordinal)]
           } finally {
                recycle()
            }
        }

        _layout = inflate(context, R.layout.layout_mcedittext, this@McEditText) as ConstraintLayout
        _editBorder = _layout.findViewById(R.id.edit_border)

        if (kind == Kind.DISABLED) {
            _editBorder.isEnabled = false
        } else {
            _editBorder.isEnabled = true
        }

        redraw()
    }

    private fun redraw() {
        refreshDrawableState()
        invalidate()
        requestLayout()
    }

    override fun onCreateDrawableState(extraSpace: Int): IntArray {
        var drawableState = super.onCreateDrawableState(extraSpace + 2) 
        when (kind) {
            Kind.ERROR -> View.mergeDrawableStates(drawableState, stateError)
            Kind.SUCCESS -> View.mergeDrawableStates(drawableState, stateSuccess)
            Kind.READONLY -> View.mergeDrawableStates(drawableState, stateReadOnly)
            else -> {drawableState = super.onCreateDrawableState(extraSpace)}
        }
        return drawableState
    }
}

Then finally I create a couple instances of the control in my fragment.xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <com.mcafee.vision.ui.components.McEditText
        android:id="@+id/as_disabled"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:label="Disabled"
        custom:editTextKind="disabled"
        />

    <View
        android:layout_width="match_parent"
        android:layout_height="10dp"/>


    <com.mcafee.vision.ui.components.McEditText
        android:id="@+id/as_error"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:label="Error"
        custom:editTextKind="error"
        />
</LinearLayout>

The problem is that it doesnt work. When I make an instance of this control the border color never changes for my custom states (though it does work for the built in states like enabled=false). Im not sure how to set the custom states beyond merging them in the onCreateDrawableState() override. This article details how to do this but it doesn't appear to work. Am I missing something?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source