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;
}
}
}
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.