aromero
aromero

Reputation: 25761

ListView Item Selected State not working

So I have a ListView and I want to change the color of each items background and text. This ListView is inside a ListFragment. My code inflates the layout in the onCreateView and inflates the layout of each item in the newView.

The android:state_pressed="true" is working fine, whenever I press in one item the background changes to that color. But when selecting an item neither the bg color or text color changes, even though I've defined an item with android:state_selected="true" in the selector.

Edit: I'm using SDK level 11 (Android 3.0) and a Motorola Xoom.

The list fragment layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>
</LinearLayout>

The list item layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="25dp"
    android:background="@drawable/list_item_bg_selector">
    <TextView android:id="@+id/form_title"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="@dimen/text_size_xlarge"
        android:textStyle="bold"
        android:textColor="@drawable/list_item_text_selector" />
    <TextView android:id="@+id/form_subtitle"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="@dimen/text_size_medium"
        android:textStyle="normal"
        android:layout_marginTop="5dp"
        android:textColor="@drawable/list_item_text_selector" />
</LinearLayout>

The background selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item 
        android:state_pressed="true"
        android:drawable="@color/white" />
    <item
        android:state_selected="true"
        android:drawable="@drawable/list_item_bg_selected" />
    <item 
        android:drawable="@color/list_bg" />
</selector>

The text selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_selected="true"
        android:drawable="@color/white" />
    <item 
        android:drawable="@color/list_text_blue" />
</selector>

Upvotes: 21

Views: 31691

Answers (4)

Arcao
Arcao

Reputation: 1390

The best solution with support of all API levels is to implement Checkable feature for list item View which means that the top view of your list item layout has to implement Checkable interface (in my case it was TextView, but the same can be applied on ViewGroup classes like LinearLayout). When you click on a list item, the ListView call setChecked method and there we change the state of View to use android:state_checked="true" selector. Together with list view android:choiceMode="singleChoice" it will select only one item.

The trick is to override onCreateDrawableState method and set the checked state here for drawables. See example of SelectableTextView bellow. After the setChecked is called, the checked state is stored and called refreshDrawableState.

Example of SelectableTextView:

package com.example.widget.SelectableTextView;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.Checkable;
import android.widget.TextView;

public class SelectableTextView extends TextView implements Checkable {
    private static final int[] CHECKED_STATE_SET = {
                    android.R.attr.state_checked
    };

    private boolean mChecked;

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

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

    public SelectableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SelectableTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

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

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

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

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

Example of selectable_list_item.xml layout:

<?xml version="1.0" encoding="utf-8"?>
<com.example.widget.SelectableTextView xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:id="@android:id/text1"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:textColor="@color/list_item_selector_foreground"
          android:background="@drawable/list_item_selector_background"
          tools:text="Item 1"/>

Example of list_item_selector_foreground.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- checked -->
    <item android:color="@color/list_item_text_active" android:state_checked="true"/>

    <item android:color="@color/list_item_text"/>
</selector>

Example of list_item_selector_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/list_item_background_selected" android:state_pressed="true"/>
    <item android:drawable="@color/list_item_background_selected" android:state_focused="true"/>
    <item android:drawable="@color/list_item_background_active" android:state_checked="true"/>

    <item android:drawable="@color/list_item_background"/>
</selector>

Upvotes: 3

Yunus Emre INCE
Yunus Emre INCE

Reputation: 99

Do not forget to set clickable="true" for the layout. This solved my problem.

List item layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/list_item_bg_selector"
        android:clickable="true" >

        <TextView
            android:id="@+id/tvNewsPreviewTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLines="3"
            android:ellipsize="end"
            android:textSize="@dimen/news_preview_title_textsize"
            android:textStyle="bold" />
    </RelativeLayout>

Background selector:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" >
            <shape android:shape="rectangle">
                <stroke android:width="1dp" android:color="@color/black" />
                <gradient android:startColor="@color/white" android:endColor="@color/white" />
            </shape>
        </item>
        <item>
            <shape android:shape="rectangle">
                <stroke android:width="1dp" android:color="@color/holo_gray_darker" />
                <gradient android:startColor="@color/holo_gray_bright" android:endColor="@color/holo_gray_bright" />
            </shape>
        </item>
    </selector>

Upvotes: 1

user2089800
user2089800

Reputation: 11

@Andrew S: Along with using activated state in selector , activated state must be set to false for default case as shown in below selector code.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="false"
        android:state_activated="false"
        android:color="@color/dark_text_blue"/>
  <item android:state_pressed="true"
        android:color="@color/red"/>
  <item android:state_activated="true"
        android:color="@color/red"/>
</selector>

Upvotes: 0

aromero
aromero

Reputation: 25761

The answer is to use the android:state_activated="true" state, instead of the "selected" state. More on this here: ListFragment Item Selected Background

Upvotes: 45

Related Questions