Question

I want to create a CustomView that displays an image. On click the view should change its state. There should be three states (off, set, notset) the view can represent. I want to do this with a selector in XML. It does not necesseraliy need to be a custom selector. I could reuse three states of the selector (it does not matter if the names of the state are different then).

Is there a good way to achieve this?

Was it helpful?

Solution

Just in case your issue has not resolved itself yet. I do the changing of states with a new implementation of the Android Button.

The states are defined in .xml and set via a selector. Here the three states defined in the attrs.xml file:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="states">
        <attr name="state_on" format="boolean" />
        <attr name="state_off" format="boolean" />
        <attr name="state_notset" format="boolean" />
    </declare-styleable>
</resources>

And the selector (statebutton_selector.xml) inside the drawables folder: (I assume that enabling a specific state automatically disables the other states - the drawables like "state_on" are just .png images representing the individual states)

<?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/com.example.statebuttontest">
<item
    app:state_on="true"
    app:state_off="false"
    app:state_notset="false"
    android:drawable="@drawable/state_on" />
<item
    app:state_on="false"
    app:state_off="true"
    app:state_notset="false"
    android:drawable="@drawable/state_off" />
<item
    app:state_on="false"
    app:state_off="false"
    app:state_notset="true" 
    android:drawable="@drawable/state_notset" />
</selector>

Also, be aware to reference your correct package name in the selector xml file, as stated in your Manifest file:

xmlns:app="http://schemas.android.com/apk/res/com.example.statebuttontest"

And finally, the StateButton class that extends Button. With a simple OnClickListener the state is changed. I also implemented an OnStateChangedListener that for example can be implemented by an Activity that contains the Button and will be called whenever the state changes.

The changing of the state itself is done inside the onCreateDrawableState(...) method that is called automatically every time the Button is clicked. "extraspace + 1" means, that there will be one additional state inside the drawableStates array.

public class StateButton extends Button implements OnClickListener {

    private static final int[] mStates = { R.attr.state_notset, R.attr.state_on, R.attr.state_off };
    private int mStateIndex = 0; // first state is "notset"

    private OnStateChangedListener mListener;

    public StateButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        changeState();
    }

    public void changeState() {
        mStateIndex = (mStateIndex+1) % mStates.length;

        // notify listener
        if(mListener != null) mListener.onStateChanged(mStates[mStateIndex]);
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {

        final int[] drawableState = super.onCreateDrawableState(extraSpace+1);

        int [] state = { mStates[mStateIndex] };

        mergeDrawableStates(drawableState, state);

        return drawableState;
    }

    public void setOnStateChangedListener(OnStateChangedListener l) {
        this.mListener = l;
    }
}

Last but not least, set the selector as the background of your Button:

<com.example.statebuttontest.StateButton
        android:id="@+id/stateButton1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:background="@drawable/statebutton_selector"
        android:text="" />

An example of the Activity (with Listener):

public class MainActivity extends Activity implements OnStateChangedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        StateButton s = (StateButton) findViewById(R.id.stateButton1);
        s.setOnStateChangedListener(this);
    }

    @Override
    public void onStateChanged(int state) {
        Log.i("Main", "State changed to: " + getResources().getResourceEntryName(state));
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top