13
votes

Have read over a number of related questions here at SO, as well as looked through the Android docs and source to try to figure this out, but I am stumped, although given that the listSelector seems to only apply styles on selected items, I'm not shocked...

I have a Listview defined in main.xml here:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ListView  
    android:id="@android:id/list"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:fadingEdgeLength="5dp"
    android:divider="#000000"
        android:dividerHeight="1dp"
        android:listSelector="@drawable/list_selector" />
    <TextView 
    android:id="@android:id/empty"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:singleLine="false"
        android:text="@string/message_list_empty" />
</FrameLayout>

The listSelector referenced is here (borrowed largely from the default Android State List found in the SDK: /android/platforms/android-8/data/res/drawable/list_selector_background.xml):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the window has lost focus, disable the items -->
    <item 
        android:state_window_focused="false"
        android:drawable="@drawable/shape_row_disabled" />

    <!-- the list items are disabled -->
    <item 
        android:state_enabled="false"
        android:state_focused="true" 
        android:state_pressed="true"
        android:drawable="@drawable/shape_row_disabled" />
    <item 
        android:state_enabled="false"
        android:state_focused="true" 
        android:drawable="@drawable/shape_row_disabled" />
    <item 
        android:state_enabled="false"
        android:drawable="@drawable/shape_row_disabled" />

    <!-- the list items are enabled and being pressed -->
    <item 
        android:state_focused="true" 
        android:state_pressed="true"
        android:drawable="@drawable/shape_row_transition" />
    <item 
        android:state_focused="false" 
        android:state_pressed="true"
        android:drawable="@drawable/shape_row_transition" />

    <!-- the list items are enabled and being used -->
    <item
        android:state_focused="true"
        android:drawable="@drawable/shape_row_selected" />

    <!-- the default item -->
    <item 
        android:drawable="@drawable/shape_row" />
</selector>

The drawables referenced are shapes, rounded rectangles, like so:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
android:shape="rectangle">
    <gradient 
        android:startColor="#EF6100"
        android:centerColor="#FF8E00" 
        android:endColor="#EF6100" 
        android:angle="270" /> 
    <corners 
        android:bottomRightRadius="7dp" 
        android:bottomLeftRadius="7dp"
        android:topLeftRadius="7dp" 
        android:topRightRadius="7dp" /> 
</shape>

So, the end result should be a ListView with items with a @drawable/shape_row background by default, and then different colored backgrounds depending on the state. When I fire up my list, everything works fine in terms of when states are active, such as an item gets focus, is pressed, etc, but the items themselves have a transparent background when none are selected (i.e. the default view state). I had hoped that the last rule of the State List,

<item android:drawable="@drawable/shape_row" />

would capture that state, but for some reason it is not working. I have moved things around and tried different settings, and nothing. If I edit the row.xml file which I use to define the list items in the ListView and try to add a specific android:background that points to @drawable/shape_row, every row gets the correct background, BUT on pressed, on focus, etc states fail to process (or at least, can't be seen as the background never changes).

Can anyone point me in the right direction here? I am SO close to putting this one to bed, but after trying nearly everything, just can't get the ListView items to take a default background AND respond to the implemented State List via android:listSelector.

Thanks,

Paul

EDIT:

Also just tried adding android:itemBackground="@drawable/shape_row" to the ListView in main.xml, and unsurprisingly no luck (the docs state it should be used to specify menu items' backgrounds, but thought it was worth a shot with list items).

Another thing tried, adding android:visible="true" to each of the selector and item definitions. The StateListDrawable docs seem to indicate that the "Provides initial visibility state of the drawable; the default value is false", but adding this changed nothing.

EDIT2: So, based on the research done below by Qberticus, it seems that there can be only one set of list selectors per view, which confirms the list selector behavior. I can also confirm that setting the android:drawSelectorOnTop does move the selector IN FRONT of the selected item, but this of course obscures the item itself such that I only see the selector itself (which in my case is a colored shape).

Here is what the list looks like with backgrounds set:

normal, unselected list with background set

Due to the set background, there is no change in appearance when items are selected. If I change the android:drawSelectorOnTop directive to true and select an item, I get:

selected, with drawSelectorOnTop set to true

And finally, if I set no background image for the list items, the selector works as expected, but of course, there are no background images as the last rule of the StateSelector does not appear to be followed (shouldn't it act as a catchall?) for non-selected items meaning no nice custom rounded shapes:

selected, no background

So, my problem remains; if I don't set a background for each list item, I can see the selector working BUT the items have no background when not selected. If I set a background for the item, they look great when not selected, but the background obscures the selector. Is it simply not possible to have a uniquely shaped list item with working selectors?

Thanks for anyone else who can weight in.

3

3 Answers

9
votes

Figured this out, the issue was that while I was setting the listSelector for the ListView to the State List correctly via android:listSelector="@drawable/list_selector", I also needed to set the background for the list item to another State List via android:background="@drawable/list_background"

In the list_background.xml state list, I have:

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

    <!-- the list items are disabled -->
<item 
        android:state_window_focused="false"
        android:drawable="@drawable/shape_row_disabled" />

    <!-- the list items are enabled, in focus but not being touched/pressed (idle) -->
    <item 
        android:state_window_focused="true"
        android:state_pressed="false"
        android:state_focused="false"
        android:state_selected="false"
        android:drawable="@drawable/shape_row" />

    <!-- the catch-all -->
    <item 
        android:drawable="@android:color/transparent" />
</selector>

Found the answer here:

Changing background color of ListView items on Android

In conjunction with my list selector above, it works perfectly.

2
votes

The listSelector is only used to specify a View that will be drawn in the same place as the selected item View. I.e., there is only one instance of the listSelector per ListView. You can specify if it can draw on top or not using drawSelectorOnTop.

If you want all your Views to use a state list then you should specify that where the child Views are defined.

I'm using the source for AbsListView as reference. Specifically AbsListView#setSelector(Drawable), AbsListView#positionSelector, AbsListView#drawSelector, and AbsListView#dispatchDraw

AbsListView#drawSelector:

private void drawSelector(Canvas canvas) {
        if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) {
            final Drawable selector = mSelector;
            selector.setBounds(mSelectorRect);
            selector.draw(canvas);
        }
}

AbsListView#positionSelector:

void positionSelector(View sel) {
    final Rect selectorRect = mSelectorRect;
    selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
    positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
            selectorRect.bottom);

    final boolean isChildViewEnabled = mIsChildViewEnabled;
    if (sel.isEnabled() != isChildViewEnabled) {
        mIsChildViewEnabled = !isChildViewEnabled;
        refreshDrawableState();
    }
}

The source indicates that there is only one mSelector created/used and that it is positioned in the same bounding rect as the selected item.

0
votes

You can assign default background for root layout and selector_background for child, in this case if item not selected then default background not obscures the selector. Its work for me.