Search code examples
javaandroidandroid-listviewsamsung-mobile

ListView item state_selected is lost on real device


I have a problem with a simple ListView on a Samsung phone.

I create a new ListView programmatically. The items are simple TextView. I put a listener on my list view:

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            view.setSelected(true);
        }
    });

The TextView (i.e. items of listView) use a ColorStateList as textColor (pressed-->Green ; selected-->Blue ; default-->Red).

On the emulator, everything is fine : items are red by default , when I press one it become green, and when I release it become blue. If I select another item : the previously selected go back to red and the newly selected became blue.

On my Samsung device : items are red by default , when I press one it become green, and when I release it become red again (i.e. not selected).

It seems to be a bug in the Samsung ListView implementation (it's a custom implementation and so difficult to trace without source code).

EDIT: not a bug but a slightly different behavior because of touch mode (see link in accepted answer)

Do you have any ideas on how to workaround this bug/behavior?

Additional constraint: I can't use an xml selector because I receive the color to use only at runtime.

My device is a Samsung GT-B5330 , API 15. (but I expect it occurs on most Samsung devices)

Here is the complete (compilable) code

import android.R;
import android.app.Activity;
import android.content.res.ColorStateList;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class TestActivity extends Activity {

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RelativeLayout rootLayout = new RelativeLayout(this);
    setContentView(rootLayout);

    //create listView
    ListView listView = new ListView(this);
    listView.setAdapter(new MyListAdapter());
    listView.setDivider(null);
    listView.setDividerHeight(0);
    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    listView.setItemsCanFocus(false);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            view.setSelected(true);
        }
    });
    listView.setBackgroundColor(Color.WHITE);

    //positionning listView
    RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(100,200);
    lp.setMargins(50,50,10,10);
    rootLayout.addView(listView, lp);
}



private class MyListAdapter implements ListAdapter{

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ColorStateList colorStateList = new ColorStateList(
                new int[][]{
                        new int[]{R.attr.state_pressed},
                        new int[]{R.attr.state_selected},
                        new int[]{-R.attr.state_selected},
                },
                new int[]{
                        Color.GREEN,
                        Color.BLUE,
                        Color.RED});
        TextView textView = new TextView(parent.getContext());
        textView.setText("Item " + position);
        textView.setTextColor(colorStateList);
        return textView;
    }

    @Override
    public boolean areAllItemsEnabled() {
        return true;
    }

    @Override
    public boolean isEnabled(int position) {
        return true;
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
    }

    @Override
    public int getCount() {
        return 10;
    }

    @Override
    public Object getItem(int position) {
        return "data "+position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }


    @Override
    public int getItemViewType(int position) {
        return position;
    }

    @Override
    public int getViewTypeCount() {
        return getCount();
    }

    @Override
    public boolean isEmpty() {
        return getCount()>0;
    }
}
}

Solution

  • I don't have a samsung device on hand (Galaxy Nexus dosen't count, it has plain Android and it works quite fine with your example) so I cannot test my assumptions but it seems that ListView drops selected state of the item after it has been released. You can check it with HierarchyViewer (use Romain Guy's ViewServer if your phone isn't rooted).

    It is dangerous to rely on selection having a touchscreen because of TouchMode (see http://android-developers.blogspot.ru/2008/12/touch-mode.html). In two words: you don't have a concept of selection (or focus, btw) when a user is interacting with a touchscreen. Emulator usually has D-pad so it may have slightly different behavior.

    So my suggestion for you is to use state_checked instead of state_selected. Android has CheckedTextView that may help. Just call ListView's setItemChecked. This solution also has nice properties of keeping checked item position between configuration changes and automatical unchecking previously checked item when other item is pressed (if CHOICE_MODE_SINGLE is used).

    If it is not acceptable and you need to stick with state_selected then you may wrap your TextView into LinearLayout, it should prevent selection from disappearing. But don't forget that ListView reuses the same view for other list items when it goes out of screen, so you need to track selection state in your adapter to properly set it.