37

So I have an activity with 2 ListView widgets, when you select a value in the first one, the second is populated with values related to the selection in the first ListView. This mechanic works without a problem, but now I want the user choices to stay highlighted. I've read a good ammount of question related to this topic and it seems there is a myriad of ways one can accomplish this but after trying about 4-5 of em' I still can't get it to work.

I've got it working on the second ListView using the android:listSelector="#CCCCCC" XML Attribute, but this seems to be wiped clean once a OnItemClickListener is introduced into the mix (like the one I use on my first ListView).

So far here's what I've got:

Custom OnItemClickListener I found browsing various answer regarding this topic (slightly modified it in order for it to load my info the second ListView):

private class ItemHighlighterListener implements OnItemClickListener {

    private View oldSelection = null;

    public void clearSelection() {
        if(oldSelection != null) {
            oldSelection.setBackgroundColor(android.R.color.transparent);
        }
    }

    public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
        clearSelection();
        oldSelection = view;
        view.setBackgroundDrawable(view.getContext().getResources().getDrawable(R.drawable.list_selector));
        loadClubs(mXMLPortalOptions.getRegion(pos).getId());
        mClubList.setAdapter(new ArrayAdapter<String>(getApplicationContext(), R.layout.list_item_white, mClubs));
    }
}

Here's my list_selector.xml file :

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

    <item android:state_selected="true"><shape>
            <solid android:color="#CCCCCC" />
        </shape></item>

    <item android:state_selected="false"><shape>
            <solid android:color="#FFFFFF" />
        </shape></item>

</selector>

The method (OnItemClick) is called and executed, but the background of my ListItem stays the same color :/

I can't believe that this simple task has proven so complicated.

If I have omitted code that could be useful or if my question is lacking details, feel free to point that out and I'll do my best to explain myself.

user229044
  • 232,980
  • 40
  • 330
  • 338
Jean-Philippe Roy
  • 4,752
  • 3
  • 27
  • 41

11 Answers11

63

Put a position variable for selected item. Change the position in onItemClicked() method. Check the selected position in List Adapter inside getView() and set the background for the selected item.

public class TestAdapter extends BaseAdapter
{
    private Context context;
    private ArrayList<TestList> testList;
    private int selectedIndex;
    private int selectedColor = Color.parseColor("#1b1b1b");

    public TestAdapter(Context ctx, ArrayList<TestList> testList)
    {
        this.context = ctx;
        this.testList = testList;
        selectedIndex = -1;
    }

    public void setSelectedIndex(int ind)
    {
        selectedIndex = ind;
        notifyDataSetChanged();
    }

    @Override
    public int getCount()
    {
        return testList.size();
    }

    @Override
    public Object getItem(int position)
    {
        return testList.get(position);
    }

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

    private class ViewHolder
    {
        TextView tv;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        View vi = convertView;
        ViewHolder holder;
        if(convertView == null)
        {
            vi = LayoutInflater.from(context).inflate(R.layout.test_list_item, null);
            holder = new ViewHolder();

            holder.tv = (TextView) vi;

            vi.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) vi.getTag();
        }

        if(selectedIndex!= -1 && position == selectedIndex)
        {
            holder.tv.setBackgroundColor(Color.BLACK);
        }
        else
        {
            holder.tv.setBackgroundColor(selectedColor);
        }
        holder.tv.setText("" + (position + 1) + " " + testList.get(position).getTestText());

        return vi;
    }

}

Now set the selectedIndex variable when a list item clicked.

public class TestActivity extends Activity implements OnItemClickListener
{
    // Implemented onItemClickListener

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id)
    {
        adapter.setSelectedIndex(position);
    }
}
Shaiful
  • 5,643
  • 5
  • 38
  • 41
  • can you please explain in more detail.. i am getting something but not 100%. so please explain in detail steps..please – Sumedh Tambat Dec 05 '12 at 05:22
  • Excellent explanation - Several new concepts all explained in one shot, thanks! – Plasty Grove Jun 13 '13 at 16:54
  • Great and simple, that's how I like my code! I didn't get it to work until I made some things static and moved some things around (see my answer below). +1 – whyoz Jul 10 '13 at 21:34
  • Hello I am trying to use this approach with an expandable listview but can't get it work , anytime i open a group I find an item selected in it – Prateek Sep 05 '13 at 11:18
  • @Shaiful actually this worked, but it make a slow UI response cause every time click on item, it call `notifyDataSetChanged()` Could you tell me if there is anyway to fix it? – ductran Oct 11 '13 at 08:15
  • 1
    @R4j It should not make your UI slow. Maybe there are other reason. Each time `notifyDataSetChanged()` called only the visible number of rows get updated. – Shaiful Oct 12 '13 at 09:37
  • Can you explain me this below code., if(selectedIndex!= -1 && position == selectedIndex) { holder.tv.setBackgroundColor(Color.BLACK); } else { holder.tv.setBackgroundColor(selectedColor); } – kavie Oct 19 '14 at 09:19
  • @dya If selecectedIndex is set and the current rendered position is the selectedIndex then set the background color to black. otherwise set it to normal color. It's just a skeleton code. modify it yourself. – Shaiful Oct 20 '14 at 10:52
5

To expand on Shaiful's great solution, you might not get his to work in your situation.

If you are using have your code all in public void onListItemClick(ListView l, View v, int index, long id), if you're using fragments and have to declare an interface instead of implementing OnListItemClickListener, or whatever is causing your IDE to generate errors, you might have to access variables and methods statically.

public static int selectedPosition = 0;
ArrayAdapter<Your_obj> adapter = null;

@Override
public void onListItemClick(ListView l, View v, int index, long id) {
    super.onListItemClick(l, v, index, id);

        selectedPosition = index;
        Your_adapter.setSelectedIndex(selectedPosition);
        adapter.notifyDataSetChanged();
}

And in Your_adapter:

private static int selectedIndex;

//public Your_adapter...

public static void setSelectedIndex(int ind) {
    selectedIndex = ind;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    WellHolder holder = null;

    if (null == convertView) {

                //set up your "holder"
    }

    if (position == selectedIndex) {
        convertView.setBackgroundColor(convertView.getResources().getColor(R.color.cyan));
    }
    else {
        convertView.setBackgroundColor(convertView.getResources().getColor(R.color.silver));
    }

    return convertView;
}

Some other differences are that you don't have to initialize any variables as "0" or "-1" and notifyDataSetChanged() is called in your activity.

Once again, thanks for your solution @Shaiful. It certainly helped save me time trying to get what is default in iOS to work for Android, all while avoiding selector/item/focused/pressed/etc.

whyoz
  • 5,168
  • 47
  • 53
  • It's because you are not using state list instead this is an inneficient and not so suitable solution, it's better to lean on the platform on this kind of stuf – Necronet Oct 28 '13 at 09:34
4

I faced similar problem. That's my solution:

First add custom list selector to your list view:

<ListView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:listSelector="@drawable/listselector" />

Inside listselector.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_accelerated="false"
        android:drawable="@drawable/bg" />
</selector>

And finally a drawable bg.xml with color of your highlight:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#33b5e6"/>
</shape>
zjadarka
  • 93
  • 5
3

I think the best and easiest solution is this. You don't need to set any android:listSelector on the ListView itself or do any changes to the adapter. You don't even need to call any setSelection(position) in the OnItemClickListener as it is handled automatically.

  1. Set to your ListView:

    android:choiceMode="singleChoice"
    
  2. Set background of the list item itself:

    android:background="?android:attr/activatedBackgroundIndicator"
    
  3. That's it.

This way you'll get default system behavior. That's how it is done in the default android.R.layout.simple_list_item_activated_1 layout.

Robyer
  • 4,632
  • 2
  • 23
  • 21
2

lv.setSelector(R.drawable.highlighter);

put a highlighter.png image in drawable folder
simplest way to highlight selected item in list view.

Artem Shmatkov
  • 1,434
  • 5
  • 22
  • 41
2
//create a list_itemselectorin drawable folder
//you will get the list item selected background color change once you select //the item

    <selector xmlns:android="http://schemas.android.com/apk/res/android">

        <!-- Focused State -->
        <item android:state_focused="true"><shape>
                <solid android:color="#66FFFFFF" />
            </shape></item>
        <!-- Pressed State -->

        <item android:state_pressed="true"><shape>
                <solid android:color="@color/Black" />
            </shape></item>

        <!-- Default State -->
        <item><shape>
                <solid android:color="@color/Black" />
            </shape></item>

    </selector>


    //create a list in layout folder
      <ListView
            android:id="@+id/mySlidingList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:choiceMode="singleChoice"
            android:divider="@color/GrayHot"
            android:dividerHeight="1dip"
            android:listSelector="@drawable/list_itemselector"
            android:scrollbars="none" />

// And see the output.

DeepakPanwar
  • 1,389
  • 14
  • 22
2

I was looking for it two weeks ago and the result is that is not possible with a drawable selector. For more info read this post in Android Developers Blog: Touch Mode

In resume: Only when your finger are on the screen, item is selected.

Other possibility is save which item is selected in a var and paint different using your custom adapter like Shaiful says.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
i.masm
  • 661
  • 6
  • 7
0

If you can use a drawable for displaying listItem Highlighted then you should use following code:-

listView.setSelector(R.drawable.bg_image);

It works.

Zied R.
  • 4,964
  • 2
  • 36
  • 67
Suresh Sharma
  • 1,826
  • 22
  • 41
0

There is simple fully-XML solution, which worked for me. Firstly, define XML-drawable with selector code in which "normal" state will correspond to "selected unpressed" visual state of a list item, and state_pressed=true to "pressed" visual state. Example of file "custom_item_selector.xml", resembling Holo blue selection:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid
                android:color="#643292ff">
            </solid>
            <stroke
                android:width="1dp"
                android:color="#c83292ff">
            </stroke>
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <solid
                android:color="#323292ff">
            </solid>
            <stroke
                android:width="1dp"
                android:color="#783292ff">
            </stroke>
        </shape>
    </item>
</selector>

(may also set focused state there). Secondly, apply this xml-drawable as ListView's listSelector and set it's desired choiceMode:

<ListView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/listView"
            android:choiceMode="singleChoice"
            android:listSelector="@drawable/custom_item_selector"/>

That's all. It allows to define different visual states for "simply selected" and "pressed selected" items, for example making items brighter on press.

jetc
  • 31
  • 6
0

To keep the list items(multiple selection) highlighted, when clicked(activated), please follow the steps.

1. Set background to list item layout as drawable.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="@drawable/list_item_selector">

        <ImageView
            android:id="@+id/icon"
            android:layout_width="22px"
            android:layout_height="22px"
            android:layout_marginLeft="4px"
            android:layout_marginRight="10px"
            android:layout_marginTop="4px"
            android:src="@mipmap/ic_launcher" >
        </ImageView>

        <TextView
            android:id="@+id/label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@+id/label"
            android:textSize="20px" >
        </TextView>
    </LinearLayout>

2. drawable 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="@android:color/holo_red_light" />

<item android:state_activated="true" android:drawable="@android:color/holo_orange_dark" />

</selector>

3. Listview set multiple choice mode

getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

When Pressed:enter image description here

Below image shows, when user selected multiple list items.

When Activated: enter image description here

Annada
  • 1,075
  • 1
  • 18
  • 21
0

To summarize this post and maybe help someone else in future I suggest the answer :)

First, we need to create res/drawable/list_item_background.xml file with the following contents:

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

Specify your drawable resources, of course. And you can also add other <item> elemens with different states like state_pressed, state_focused etc.

Then, we should set the background parameter to our custom list item ViewGroup element (f.i. res/layout/list_item_layout.xml) like this:

android:background="@drawable/list_item_background"

The next step is modifying our custom Adapter class. Here is the following code fragment:

public class CustomAdapter extends BaseAdapter {
    private List<Item> items;
    private LayoutInflater itemInflater;        
    private int selectedIndex; // add this

    public CustomAdapter(Context c, List<Item> items) {
        this.items = items;
        this.itemInflater = LayoutInflater.from(c);
        selectedIndex = -1; // add this
    }

    /* add this */
    public void setSelectedIndex(int index) {
        selectedIndex = index;
        notifyDataSetChanged();
    }

    /* other adapter's stuff */

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            convertView = itemInflater.inflate(R.layout.list_item_layout, parent, false);
        }

        // add this
        convertView.setActivated(selectedIndex != -1 && position == selectedIndex);

        /* do some stuff */

        return convertView;
    }
}

Finally, we should call the setSelectedIndex(position) adapter's method in onItemClick(...) method of AdapterView.OnItemClickListener.

public class YourActivity extends Activity
        implements AdapterView.OnItemClickListener {

    private CustomAdapter mCustomAdapter;

    /* activity implementation */

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mCustomAdapter.setSelectedIndex(position);
    }
}

Now, we can be satisfied with proper list items highlighting :)

P.S. If we want to enable multiple choice mode on our list we'll just place the following string to our activity class where listView instance is kept:

listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

Thus, we'll get proper multiple items highlighting.

-- Hope this helps anyone :)

Eugene Matiyuk
  • 356
  • 2
  • 9