Tuesday, October 27, 2009

Lists and focuses

I received another seemingly trivial question in a comment. The situation is simple: we have a ListView and it contains TextViews. The user clicks (touches) a list row and the row gets highlighted until the user removes his or her finger (releases the mouse button in case of the emulator). Then comes the mistery. If you put a Button into the row, this highlight does not happen. How come?

Download the example program from here.

The secret is the quite complicated relationship between the ListView's selection algorithm and the focus. Button is focusable, basic TextView is not (Button is really just a specialized TextView). ViewGroup (whose child is LinearLayout which encapsulates one list row) delegates the focus to its first focusable child if it is not prevented to do so. As list row gives away the focus to the first focusable child (the Button) and the user did not touch the Button's area, neither the list row, nor the Button is selected. All this happens in "touch mode", try to select the row with the down/up key and you will see a completely different selection algorithm in action - the row is highlighted but the Button is not.



In order to achieve the selection effect you see in the screenshot, the ViewGroup must be prevented giving away the focus. The example program implements a quick and dirty solution: the Button is not focusable (ButtonListAdapter, line 47). More elegant solution would be to declare the ViewGroup (inflated from the buttonrow.xml layout) with FOCUS_BLOCK_DESCENDANTS flag. Conveniently, not being focusable does not prevent the Button in receiving events, click on the action button and the row counter increases nicely.

22 comments:

Anonymous said...

Hi,

I know my next questions are not really related to this particular posts, but am hoping that you could help me.

I need to create a spinner with items that are associated with a list with 2 columns: id and name. Then when a user selects an item, i would like to know the id associated with the selected name.

Thank you very much for your time.

SunD

Gabor Paller said...

I can't imagine (visually :-)) the spinner associated with a list. Say, I select something on the spinner. How does it affect the list?

Anonymous said...

Hi,

Sorry for the dumb question, am a newbie at Android. I think I am looking for something that would allow me to bind data from maybe a Hashtable (or from a table in the db, which contains an ID and a display field) to a Spinner and could automatically retrieve the id upon the user's selection of an item.

Thanks,

SunD

Anonymous said...

Hi,

I am currently binding the Spinner to an ArrayAdapter:

//
// model adapter
//
m_modelAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item);
m_modelAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
//
// model spinner widget
//
mModel = (Spinner)findViewById(R.id.model);
mModel.setAdapter(m_modelAdapter);
//
// add model items here
//
m_modelAdapter.add("Model1");
m_modelAdapter.add("Model2");
m_modelAdapter.add("Model3");
//
// Hashtable here
//
ht = new Hashtable<String, Integer>();
ht.put("Model1", 1);
ht.put("Model2", 2);
ht.put("Model3", 3);

I would then retrieve the "id" from the hashtable when the user selects an item.

I think the above code is inefficient, maintaining two different set of data (1. spinner data, 2. hashtable data).

Is there a better way?

Thanks,

SunD

Anonymous said...

Hi
Very useful post. Thank you

amitn said...

I was stuck with something related to ListView. I am developing a listview which has thumbnail images as its components, essentially forming a filmstrip. As documented, i created a custom adaptor whose getView method returns a linearlayout (whose background image is like a filmstrip) holding the image view. The filmstrip was nicely displayed on the emulator.

Now, I intend to scroll this filmstrip after a particular interval (say 5 secs). Upon scrolling, one of the items in the filmstrip should appear selected - till the next thumbnail gets selected (or scrolled to), say after 5 secs.

I have tried making use of the setSelector method, but no luck.

Unknown said...

Hi,

Interesting article and helpful.

I created a TableLayout with rows in it. Now I want the row to be highlighted like the listview when a row is selected, both short press and long press (to invoke context menu).

However, it doesn't highlight. Is there something that I am missing in my xml layout? Or I didn't set one of its attributes?

Please help. I've been searching the internet but no such issue can be found, which brings me to 80% sure that I am doing something wrong. :)

Gabor Paller said...

Jophrey, I am not aware that the TableLayout provides any selection feature. If you want such a beast, you have to implement it yourself (not a trivial task).

Unknown said...

Gabor,
Thanks for the reply. I manage to find out how to do it easily by customizing the listview row. That did the trick. :)

Gabor Paller said...

Other approach I am playing with is adding Buttons to a TableLayout and customizing the look&feel of those buttons (text, background, highlight) to be similar to list row selection. If I succeed, I will publish my example program soon.

Gabor Paller said...

Jophrey, check this out.

Anonymous said...

Hi,

Very helpful article thanks. I had the same problem. I tried both your solutions in my custom ListView and they work.

But when I add setOnClickListener() or setOnTouchListener() to a View, it no longer highlights the list line anymore.

Do you know how to workaround this?

Thanks

Dave

Anonymous said...

UPDATE from Dave:

I have it highlighting properly now with a touch listener! I found that only setOnTouchListener() works, not setOnClickListener(), and you must set this listener just before the return statement in getView().

Thanks once again.
Dave

Andrew Case said...

This is great information. I recently found your blog and it's a wealth of knowledge, thanks for the hard work!

I'm trying to do some tricky listview work myself, but have been getting caught up. It's easy to slap a ContextMenu on this code, but if you then attach an onClickListener onto the list item's TextView, then it immediately loses it's context menu ability.

Do you know how you would provide a clickable view that provides one functionality but still allows for the standard ContextMenu registration using the Acitivity's registerForContextMenu() function?

Andrew Case said...
This comment has been removed by the author.
mkarthikeyas said...

Hi,
I tried many ways to actually set the selected colour. But it was always vanishing after releasing the mouse click.
I just tried a simple dirty workaround. I set the background color of the view(here LinearLayout) myself. When another item is selected I revert back the background color.

Thanks,
Karthik

Unknown said...

I want a list view in following format

Image Text Checkbox

I hav implmentd dis by extendin list adapter BUT I ALSO WANT ONCLICKLISTNER for each row ... if i add checkbox it is disablin list click listener

Gabor Paller said...

Vabs, check out this post. It is not exactly what you want but I am sure you find the solution to your problem. :-)

moncherion said...

I have tried making use of the setSelector method, but no luck.

loo said...

thank you so much have been looking for this for so long

Anonymous said...

Thanx for the post. Its exactly what I searched for.

Anonymous said...

again the android bites ... another open-source traps ... hate it!!!