1

I want to dynamically select a Drawable based upon selected state for the view. I have used boolean states instead of string because string is not supported as per this post. Here are code details:

attrs.xml

<declare-styleable name="StateView">
    <attr name="state_not_available" format="boolean" />
    <attr name="state_idle" format="boolean" />
    <attr name="state_busy" format="boolean" />
</declare-styleable>

selector_driver_state.xml

<selector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto">
    <item custom:state_not_available="true"
        android:drawable="@drawable/drawable_icon_not_available"/>
    <item custom:state_idle="true"
        android:drawable="@drawable/drawable_icon_idle"/>
    <item custom:state_busy="true"
        android:drawable="@drawable/drawable_icon_busy"/>
    <item android:drawable="@drawable/drawable_icon_busy" />
</selector>

StateView.kt

class StateView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var STATE_NOT_AVAILABLE = intArrayOf(R.attr.state_not_available)
    private var STATE_IDLE = intArrayOf(R.attr.state_idle)
    private var STATE_BUSY = intArrayOf(R.attr.state_busy)

    private var mStateNotAvail : Boolean = false
    private var mStateIdle : Boolean = false
    private var mStateBusy : Boolean = false

    init {
        initAttrs(context, attrs)
    }

    private fun initAttrs(context: Context, attrs: AttributeSet?) {
        attrs?.let {
            val attrArr = context.obtainStyledAttributes(attrs, R.styleable.StateView)
            try {
                mStateNotAvail = attrArr.getBoolean(R.styleable.StateView_state_not_available, false)
                mStateIdle = attrArr.getBoolean(R.styleable.StateView_state_idle, false)
                mStateBusy = attrArr.getBoolean(R.styleable.StateView_state_busy, false)
            }
            finally {
                attrArr.recycle()
            }
        }
    }

fun setStateBusy(state: Boolean) {
        mStateNotAvail = false
        mStateIdle = false
        mStateBusy = state
        invalidate()
        requestLayout()
    }

    override fun onCreateDrawableState(extraSpace: Int): IntArray {

        val drawableStates = super.onCreateDrawableState(extraSpace + 1)
        if(mStateNotAvail)
            mergeDrawableStates(drawableStates, STATE_NOT_AVAILABLE)
        else if(mStateIdle)
            mergeDrawableStates(drawableStates, STATE_IDLE)
        else
            mergeDrawableStates(drawableStates, STATE_BUSY)
        return drawableStates
    }

Usage:

<com.abc.package.components.StateView
    android:layout_width="20dp"
    android:layout_height="20dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:id="@+id/viewState"
    app:state_idle="true"                    ----> not working!
    android:layout_marginLeft="10dp"/>

Problem is that StateView is not rendering. I tried to set state programmatically, it doesn't work as well using setStateBusy. Any leads? Thank you.

NightFury
  • 13,436
  • 6
  • 71
  • 120

1 Answers1

1
  1. You cannot use String attributes with selector Drawable. You need to define a custom String attribute app:state="Idle" for StateView and programmatically change the drawable based on the selected state.

    //changes in attrs.xml

    <declare-styleable name="StateView"> <attr name="state" format="reference|string" /> </declare-styleable>

    // changes in layout file

    <com.abc.package.components.StateView app:state="Idle" ...

  2. Add following method setState in the StateView class

    fun setState(state: String) { if(state?.equals("idle")){ imageView.setDrawable(R.drawable. drawable_icon_idle); } if(state?.equals("busy")){ imageView.setDrawable(R.drawable. drawable_icon_busy); } ...
    }

Hope this resolves your issue. Now you can also remove most of lines from the StateView.kt class

PEHLAJ
  • 9,980
  • 9
  • 41
  • 53