25

I have a group of FrameLayout which I want to be checkable/selectable,

That is, after a click I would like the FrameLayout to display as checked - when pressed again I would like it to become unchecked. What's more, I want this visual que to be described as usual through though the use of a <selector>.

I can't seem to get this working - I'm not sure what I'm missing:

public class CheckableFrameLayout extends FrameLayout implements Checkable {
    private boolean mChecked = false;
    public CheckableFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setChecked(boolean checked) {
        mChecked = checked;
        refreshDrawableState();
    }

    public boolean isChecked() {
        return mChecked;
    }

    public void toggle() {
        setChecked(!mChecked);
    }
}

The layout of the CheckableFrameLayout:

<com.test.view.CheckableFrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/selector_horizontal"
    android:clickable="true" >

The selector backing it (selector_horizontal.xml):

<item android:drawable="@drawable/selector_vertical_selected" android:state_pressed="false" android:state_checked="true"/>  
<item android:drawable="@drawable/selector_vertical_pressed" android:state_pressed="true" android:state_checked="false"/>
<item android:drawable="@drawable/selector_vertical_normal" android:state_pressed="false" android:state_checked="false"/>

Using the above code the "state_pressed" is working fine, but the View itself is not becoming checked (not is the Checkable code being called as discovered through debug).

Graeme
  • 25,714
  • 24
  • 124
  • 186

6 Answers6

31

Adding the following code to a Checkable class allows Selectors to work:

private static final int[] CheckedStateSet = {
    android.R.attr.state_checked,
};

@Override
protected int[] onCreateDrawableState(int extraSpace) {
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    if (isChecked()) {
        mergeDrawableStates(drawableState, CheckedStateSet);
    }
    return drawableState;
}

@Override
public boolean performClick() {
    toggle();
    return super.performClick();
}
Graeme
  • 25,714
  • 24
  • 124
  • 186
4

Here's a complete working example for a CheckableButton. It also works on Android 4.2.

public class CheckableButton extends Button implements Checkable {

    private static final int[] CheckedStateSet = { android.R.attr.state_checked };

    private boolean mChecked = false;

    public CheckableButton(Context context) {
        super(context);
    }

    public CheckableButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CheckableButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean isChecked() {
        return mChecked;
    }

    @Override
    public void setChecked(boolean checked) {
        mChecked = checked;
        refreshDrawableState();
    }

    @Override
    public void toggle() {
        mChecked = !mChecked;
        refreshDrawableState();
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CheckedStateSet);
        }
        return drawableState;
    }
}
Ridcully
  • 23,362
  • 7
  • 71
  • 86
0

Try using android:state_activated.

<item android:drawable="@color/HighlightColor"      android:state_activated="true"/> 

From the docs:

State value for StateListDrawable, set when a view or its parent has been "activated" meaning the user has currently marked it as being of interest.

Barak
  • 16,318
  • 9
  • 52
  • 84
  • Where should I put that tag? It's not available for ``. – Graeme Jul 02 '12 at 12:47
  • It's just like any other state. I've added an example to my answer. – Barak Jul 02 '12 at 12:52
  • `state_activated` makes no difference at all (It's also API 11) – Graeme Jul 02 '12 at 12:54
  • I think we missed the obvious... `android:checkable="true"`. Try that in the layout. Then again maybe not, the only place I see that mentioned is for building menus... although I suppose it wouldn't hurt to try it anyway. – Barak Jul 02 '12 at 13:26
  • Umm, where? You have `android:clickable`, not `android:checkable`. – Barak Jul 02 '12 at 13:34
  • Ah, I see, misread - `android:checkable` isn't available for a `FrameLayout` – Graeme Jul 02 '12 at 13:35
  • Well, bugger. I'm out of ideas. You wouldn't think it would be so difficult. – Barak Jul 02 '12 at 13:40
  • Nope, I've fixed it now, check out the other answer. I know it's bad form but I don't know why the `Drawable` state doesn't include state_checked. – Graeme Jul 02 '12 at 13:44
0

Try reordering tags in your selector_horizontal.xml These are evaluated top to bottom. It seems that in your case either android:state_pressed="false" or android:state_pressed="true" is applied and evaluation never reaches third line with android:state_checked="true" Try moving first line to last:

<item android:drawable="@color/HighlightColor" android:state_pressed="true"/>
<item android:drawable="@color/selected_color" android:state_checked="true"/>
<item android:drawable="@android:color/transparent" android:state_pressed="false"/>
Praveenkumar
  • 24,084
  • 23
  • 95
  • 173
vArDo
  • 4,408
  • 1
  • 17
  • 26
  • Makes no difference - I made sure each item was implicit (contains both state_pressed and state_checked) so that they won't be confused. – Graeme Jul 02 '12 at 13:00
0

In addition to Graeme's answer above do the following modification to toggle()

@Override
public void toggle() {
    mChecked = !mChecked;
    refreshDrawableState();
}
sujith
  • 2,421
  • 2
  • 17
  • 26
0

Graeme's solution doesn't worked from me under Android 4.0.3 (and I suppose it won't work under 4.0 too). Instead you can change state_checked to state_activated:

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/activated_image" android:state_pressed="false" android:state_activated="true" />
    <item android:drawable="@drawable/not_activated_image" android:state_pressed="false" android:state_activated="false"/>

</selector>

and use setActivated inside your setChecked:

@Override
public void setChecked(boolean checked) {
    mChecked = checked;
    setActivated(checked);
}

and that is all. Implementing onCreateDrawableState isn't required here.

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
  • See the [docs](http://developer.android.com/reference/android/graphics/drawable/StateListDrawable.html#attr_android%3astate_activated) - "This is an alternative representation of state_checked for when the state should be propagated down the view hierarchy.", Sound's like this is only useful if you also want the children of your view to respond `true` if their parent is checked. – Graeme Jul 27 '12 at 08:51
  • Graeme's solution works for me for a checkable Button (I also added refreshDrawableState() to setChecked() and toggle() as suggested by sujith. – Ridcully Jan 22 '13 at 15:08