89

I have a simple ListActivity that uses a custom ListAdapter to generate the views in the list. Normally the ListAdapter would just fill the views with TextViews, but now I want to put a button there as well.

It is my understanding and experience however that putting a focusable view in the list item prevents the firing of onListItemClick() in the ListActivity when the list item is clicked. The button still functions normally within the list item, but when something besides the button is pressed, I want onListItemClick to be triggered.

How can I make this work?

Dan Hulme
  • 14,779
  • 3
  • 46
  • 95
CodeFusionMobile
  • 14,812
  • 25
  • 102
  • 140
  • 5
    your solution with descendant Focusability is really helpfull, you should add it as an answer and accept it! – Maksym Gontar Sep 07 '10 at 12:43
  • @Max The reason I don't is because it's really bad practice, a workaround. If I ever found a permanent healthy solution I would make that an answer (if I remember that I wrote this question a year ago :)) – CodeFusionMobile Sep 10 '10 at 19:28
  • I'd also like to see the workaround you have. I've been trying to set the descendant focus and can't get it to work with buttons. Also I've been trying to put a GridView (with ImageViews in) in the list row and that has similar issues. – MrPurpleStreak Sep 11 '10 at 22:03
  • IMHO answer I gave is much more elegant solution for problem then one proposed by Ramps and Praveen. P.s.Not trying to revive forgotten question here but I see u didn't accept any answer yet ;D – Ewoks Mar 27 '13 at 08:37
  • @CodeFusionMobile Could you please accept Ewoks's answer? The highest-voted answer is flawed because it disables the onclick animation for the ListView element (where it turns blue). It would save other developers some time spent trying the top answer, finding out that it's flawed, and then going to Ewoks's. – 1'' Sep 16 '13 at 16:27

8 Answers8

99

as I wrote in previous comment solution is to setFocusable(false) on ImageButton.

There is even more elegant solution try to add android:descendantFocusability="blocksDescendants" in root layout of list element. That will make clicks onListItem possible and separately u can handle Button or ImageButton clicks

Hope it helps ;)

Cheers

Community
  • 1
  • 1
Ewoks
  • 12,285
  • 8
  • 58
  • 67
  • 3
    `android:descendantFocusability="blocksDescendants"` was perfect. – Matthew Mitchell Dec 05 '12 at 20:08
  • 1
    you, my friend, are my personal hero. you should be the top-voted one! this is much much better for almost any cases! – OschtärEi Apr 23 '13 at 18:56
  • This should be the accepted answer. It worked perfectly for me. It looks like all of the views in the list item need to be unfocusable and this one does just that without setting each individually. – NightWatchman May 07 '13 at 00:35
  • 1
    This does not seem to work for me. By root layout, do you mean the row layout or where the listview is defined? Anyhow, I tried all the possible combinations.. not working :( – GreenBee May 11 '13 at 18:46
  • "Root layout of list element" as I wrote.. So let's say you have list and consisting of layouts (call them root layout of list element) that have several Button, ImageButton, TextVeiw.. – Ewoks Jul 26 '13 at 06:53
  • @Ewoks It worked for me. My onListItemClick was not being called. Does this have any side effects? – likejudo Feb 23 '14 at 22:42
  • Not that i an aware of..glad that it helped – Ewoks Feb 23 '14 at 23:00
  • Wouldn't this make all the list items to not get focus at all. Its a bad design and makes the app to be not accessible friendly. – Yasir Dec 16 '14 at 03:53
  • Wat? Not really.. Did you experienced something like that..? Do you have some sample code that do that? – Ewoks Dec 16 '14 at 08:45
  • I had the same issue for my ListView with clickable sub-items in each row. Simple I used android:descendantFocusability="blocksDescendants" in the root layout of the row and I implemented OnClickListener in the adapter for each subItem. In this way if I click on the row element then the onListItemClick is fired and if I click on the sub item inside the row the OnClickListener is fired! – adev Jan 22 '16 at 18:56
59

I hope I can help here. I assume that you have custom layout for listView items, and this layout consists of button and some other views - like TextView, ImageView or whatever. Now you want to have different event fired on button click and different event fired on everything else clicked.

You can achieve that without using onListItemClick() of your ListActivity.
Here is what you have to do:

You are using custom layout, so probably you are overriding getView() method from your custom adapter. The trick is to set the different listeners for your button and different for the whole view (your row). Take a look at the example:

private class MyAdapter extends ArrayAdapter<String> implements OnClickListener {

    public MyAdapter(Context context, int resource, int textViewResourceId,
            List<String> objects) {
        super(context, resource, textViewResourceId, objects);
    }


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String text = getItem(position);
        if (null == convertView) {
            convertView = mInflater.inflate(R.layout.custom_row, null);
        }
        //take the Button and set listener. It will be invoked when you click the button.
        Button btn = (Button) convertView.findViewById(R.id.button);
        btn.setOnClickListener(this);
        //set the text... not important     
        TextView tv = (TextView) convertView.findViewById(R.id.text);
        tv.setText(text);
        //!!! and this is the most important part: you are settin listener for the whole row
        convertView.setOnClickListener(new OnItemClickListener(position));
        return convertView;
    }

    @Override
    public void onClick(View v) {
        Log.v(TAG, "Row button clicked");
    }
}

Your OnItemClickListener class could be declared like here:

private class OnItemClickListener implements OnClickListener{       
    private int mPosition;
    OnItemClickListener(int position){
        mPosition = position;
    }
    @Override
    public void onClick(View arg0) {
        Log.v(TAG, "onItemClick at position" + mPosition);          
    }       
}

Of course you will probably add some more parameters to OnItemClickListener constructor.
And one important thing - implementation of getView shown above is pretty ugly, normally you should use ViewHolder pattern to avoid findViewById calls.. but you probably already know that.
My custom_row.xml file is RelativeLayout with Button of id "button", TextView of id "text" and ImageView of id "image" - just to make things clear.
Regards!

Praveenkumar
  • 24,084
  • 23
  • 95
  • 173
Ramps
  • 5,190
  • 1
  • 45
  • 47
  • 9
    My solution ended up going another way. I solved the problem by setting the descendant Focusability property of the list row layout to block descendants and suddenly it worked. Now I use a simpleCursorAdapter with a custom viewbinder attached to it to do the event setup and other fancy things associated with the buttons. – CodeFusionMobile Jan 20 '10 at 23:10
  • 3
    I used this solution. However, a warning: if your onClick handler causes the contents of the list to change, which would result in getView being called AND if your widget is stateful (like a ToggleButton or CheckBox), AND getView sets that state, you may want to setOnClickListener(null) before you change the state in onView, to avoid getting a loop effect. – Pierre-Luc Paour Jan 27 '11 at 12:51
  • 3
    what about my solution? I have impression that is more elegant and can be applied easier whenever we need it.. – Ewoks Mar 27 '13 at 08:38
  • the first comment should be accepted as answer, so people like me wouldn't need hours to find it – keybee Aug 17 '13 at 12:28
  • Thanks! This was useful. I like it as a more localized alternative to the focus:blockDescendants approach described in other threads. – Yoav Shapira Nov 21 '13 at 17:59
  • `android:descendantFocusability="blocksDescendants"` will help when you are having _Button_ in your customized row and you wanna to perform some other operation on click of _Button_ and some other on click of _ListItem_. – Rupesh Yadav Nov 26 '13 at 10:55
18

When a custom ListView contains focusable elements, onListItemClick won't work (I think it's the expected behavior). Just remove the focus from the custom view, it will do the trick:

For example:

public class ExtendedCheckBoxListView extends LinearLayout {

    private TextView mText;
    private CheckBox mCheckBox;

    public ExtendedCheckBoxListView(Context context, ExtendedCheckBox aCheckBoxifiedText) {
         super(context);
         …
         mText.setFocusable(false);
         mText.setFocusableInTouchMode(false);

         mCheckBox.setFocusable(false);
         mCheckBox.setFocusableInTouchMode(false);
        …       
    }
}
Rabi
  • 2,593
  • 1
  • 23
  • 26
  • 3
    I like your solution: if there is only one "clickable" widget per row, then denying them focusability is an elegant solution. It does mean that a tap anywhere on the row will match, but that's a useful behavior when the widget is a radio or checkbox. The same can be achieved in XML using android:focusable="false" and android:focusableInTouchMode="false". – Pierre-Luc Paour Jan 20 '11 at 14:35
  • 1
    the xml way doesn't seem to work if you change the visibility, so one has to do it through code after setting the "View" to visible – max4ever Aug 29 '12 at 09:23
13

I have the same problem: OnListItemClick not fired ! [SOLVED]
That's happen on class that extend ListActivity,
with a layout for ListActivity that content TextBox and ListView nested into LinearLayout
and another layout for the rows (a CheckBox and TextBox nested into LineraLayout).

That's code:

res/layout/configpage.xml (main for ListActivity)

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent" > 
    <TextView  
        android:id="@+id/selection"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="pippo" /> 
    <ListView  
        android:id="@android:id/list"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:drawSelectorOnTop="false"  
        android:background="#aaFFaa" > 
    </ListView> 
<LinearLayout> 

res/layout/row.xml (layout for single row)  
<LinearLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  android:layout_width="fill_parent"  
  android:layout_height="wrap_content"> 
  <CheckBox  
      android:id="@+id/img"  
      android:layout_width="wrap_content"  
      android:layout_height="wrap_content"  
      **android:focusable="false"**  
      **android:focusableInTouchMode="false"** /> 

  <TextView  
      android:id="@+id/testo"  
      android:layout_width="wrap_content"  
      android:layout_height="wrap_content"  
      **android:focusable="false"**  
      **android:focusableInTouchMode="false"** /> 
</LinearLayout> 

src/.../.../ConfigPage.java

public class ConfigPage extends ListActivity
{
    TextView selection;

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.configpage);  
        // loaded from res/value/strings  
        String[] azioni = getResources().getStringArray(R.array.ACTIONS);  
        setListAdapter(new ArrayAdapter&lt;String&gt;(this, R.layout.row, 
                R.id.testo,   azioni));  
        selection = (TextView) findViewById(R.id.selection);  
    }       

    public void onListItemClick(ListView parent, View view, int position, long id)
    {
        selection.setText(" " + position);
    }
}

This begin to work when I added on row.xml

  • android:focusable="false"
  • android:focusableInTouchMode="false"

I use Eclipse 3.5.2
Android SDK 10.0.1
min SDK version: 3

I hope this is helpful
... and sorry for my english :(

rekaszeru
  • 19,130
  • 7
  • 59
  • 73
rfellons
  • 656
  • 12
  • 28
2

just add android:focusable="false" as one of the attributes of your button

John
  • 941
  • 8
  • 17
1

I've had the same problem with ToggleButton. After half a day of banging my head against a wall I finally solved it. It's as simple as making the focusable view un-focusable, using 'android:focusable'. You should also avoid playing with the focusability and clickability (I just made up words) of the list row, just leave them with the default value.

Of course, now that your focusable views in the list row are un-focusable, users using the keyboard might have problems, well, focusing them. It's not likely to be a problem, but just in case you want to write 100% flawless apps, you could use the onItemSelected event to make the elements of the selected row focusable and the elements of the previously selected row un-focusable.

Ridiculous
  • 443
  • 4
  • 9
  • I used this approach. It is the simplest way, if you can get around the issue of not being able to focus on individual items. This also keeps the state drawables for the list items as they should be, which some of the other workarounds do not. – Jonathan S. Apr 29 '11 at 14:14
1
 ListView lv = getListView();
lv.setTextFilterEnabled(true);

lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
    int position, long id) {
  // When clicked, show a toast with the TextView text
  Toast.makeText(getApplicationContext(), ((TextView) view).getText(),
      Toast.LENGTH_SHORT).show();
}
});
confucius
  • 13,127
  • 10
  • 47
  • 66
-1

I used the getListAdapter().getItem(position) instantiating an Object that holds my values within the item

MyPojo myPojo = getListAdapter().getItem(position);

then used the getter method from the myPojo it will call its proper values within the item .

Lucifer
  • 29,392
  • 25
  • 90
  • 143
Mikey
  • 4,218
  • 3
  • 25
  • 25