androidlistviewandroid-xmlstatelistdrawable

Unexpected results when creating selector from code and not from xml


I'm having some trouble converting some working XML files into code. I have a ListView, and I need to be able to switch its pressed/checked drawables at run-time, from resources not known in advanced (hence why not using XML);

The following configuration works great:

main.xml:

<?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="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:choiceMode="singleChoice" >
    </ListView>

</LinearLayout>

selector.xml:

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

list_row.xml:

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/selector"
    android:padding="10dp" />

Main.java:

public class Main extends Activity {

    StateListDrawable selector; 

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ListView listView1 = (ListView) findViewById(R.id.listView1);
        StringAdapter adapter = new StringAdapter(this, R.layout.list_row);
        adapter.add("one");     adapter.add("two");     adapter.add("three");       adapter.add("four");
        adapter.add("five");        adapter.add("six");     adapter.add("seven");       adapter.add("eight");
        adapter.add("nine");        adapter.add("ten");     adapter.add("eleven");      adapter.add("twelve");
        listView1.setAdapter(adapter);
    }

    private class StringAdapter extends ArrayAdapter<String>{

        public StringAdapter(Context context, int textViewResourceId) {
            super(context, textViewResourceId);         
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            final CheckedTextView  tv = (CheckedTextView ) inflater.inflate(R.layout.list_row, parent, false);
            tv.setText(getItem(position));
            return tv;
        }
    }
}

The result is like this, which is great (pressed:blue, checked:green, else:orange):

result1

However, if I remove the

android:background="@drawable/selector

from list_row_xml, and apply it via code:

    Drawable blue = getResources().getDrawable(R.drawable.blue);
    Drawable green = getResources().getDrawable(R.drawable.green);
    Drawable orange = getResources().getDrawable(R.drawable.orange);

    selector = new StateListDrawable();
    selector.addState(new int[] { android.R.attr.state_pressed }, blue);
    selector.addState(new int[] { android.R.attr.state_checked }, green);
    selector.addState(new int[] { }, orange);

       tv.setBackgroundDrawable(selector);

I get the following (everything get the state_pressed drawable, blue):

enter image description here

What went wrong? I'm pretty sure I converted the selector to code appropriately.


Solution

  • I copied your code and it all works fine. You should make sure to create a new instance of selector in getView() though. Otherwise if you use the same selector for all your items, pressing one item will affect all your items.

    Here's what your getView() method should look like

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            final CheckedTextView tv = (CheckedTextView) inflater.inflate(R.layout.list_row, parent, false);
            tv.setText(getItem(position));
    
            Drawable blue = getResources().getDrawable(R.drawable.blue);
            Drawable green = getResources().getDrawable(R.drawable.green);
            Drawable orange = getResources().getDrawable(R.drawable.orange);
    
            selector = new StateListDrawable();
            selector.addState(new int[] { android.R.attr.state_pressed }, blue);
            selector.addState(new int[] { android.R.attr.state_checked }, green);
            selector.addState(new int[] {}, orange);
    
            tv.setBackgroundDrawable(selector);
    
            return tv;
        }
    

    Of course there is some optimization you could do, but this will work.