Android: ListView elements with multiple clickable buttons
Asked Answered
S

8

186

I've a ListView where every element in the list contains a TextView and two different Buttons. Something like this:

ListView
--------------------
[Text]
[Button 1][Button 2]
--------------------
[Text]
[Button 1][Button 2]
--------------------
... (and so on) ...

With this code I can create an OnItemClickListener for the whole item:

listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> list, View view, int position, long id) {
        Log.i(TAG, "onListItemClick: " + position);

        }

    }
});

However, I don't want the whole item to be clickable, but only the two buttons of each list element.

So my question is, how do I implement a onClickListener for these two buttons with the following parameters:

  • int button (which button of the element has been clicked)
  • int position (which is the element in the list on which the button click happened)

Update: I found a solution as described in my answer below. Now I can click/tap the button via the touch screen. However, I can't manually select it with the trackball. It always selects the whole list item and from there goes directly to the next list item ignoring the buttons, even though I set .setFocusable(true) and setClickable(true) for the buttons in getView().

I also added this code to my custom list adapter:

@Override
public boolean  areAllItemsEnabled() {
    return false;           
}

@Override
public boolean isEnabled(int position) {
        return false;
}

This causes that no list item is selectable at all any more. But it didn't help in making the nested buttons selectable.

Anyone an idea?

Sphygmic answered 10/11, 2009 at 16:17 Comment(3)
Are these still needed ?Litigate
If you look at the BaseAdapter code, you'd see that areAllItemsEnabled() and isEnabled() are just hardcoded to true, making them simple placeholders without any logic.Rayleigh
What if I want to use a SimpleCursorAdapter?Do I have to make a custom adapter extends simplecursoradapter ?Cene
S
151

The solution to this is actually easier than I thought. You can simply add in your custom adapter's getView() method a setOnClickListener() for the buttons you're using.

Any data associated with the button has to be added with myButton.setTag() in the getView() and can be accessed in the onClickListener via view.getTag()

I posted a detailed solution on my blog as a tutorial.

Sphygmic answered 21/11, 2009 at 18:37 Comment(5)
how exacly did you get the curItem.url from query, would you be more specific?Cene
Thanks znq, That was very useful for me... Saved a bundle of time.Vania
Watch this talk at 11:39 there is an excellent example: youtu.be/wDBM6wVEO70?t=11m39s Then do what @Sphygmic says...setTag() when the convertView==null and do getTag() in the onClick() method of the button's onClickListener(). Thank you!Ferric
This is quite useful, thanks a lot, but, is there a way that we can declare the listener in the fragment itself and not in the adapter? I ask it because I'm using the same custom adapter class to inflate different menus, and I wouldn't like to have my ListAdapter class full of conditionals depending on the menu I'm inflating and the button I'm tappingCabaret
it would be great to have answers in SO itself and not point to some other page. The blog link is dead!Anuria
L
63

This is sort of an appendage @znq's answer...

There are many cases where you want to know the row position for a clicked item AND you want to know which view in the row was tapped. This is going to be a lot more important in tablet UIs.

You can do this with the following custom adapter:

private static class CustomCursorAdapter extends CursorAdapter {

    protected ListView mListView;

    protected static class RowViewHolder {
        public TextView mTitle;
        public TextView mText;
    }

    public CustomCursorAdapter(Activity activity) {
        super();
        mListView = activity.getListView();
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        // do what you need to do
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View view = View.inflate(context, R.layout.row_layout, null);

        RowViewHolder holder = new RowViewHolder();
        holder.mTitle = (TextView) view.findViewById(R.id.Title);
        holder.mText = (TextView) view.findViewById(R.id.Text);

        holder.mTitle.setOnClickListener(mOnTitleClickListener);
        holder.mText.setOnClickListener(mOnTextClickListener);

        view.setTag(holder);

        return view;
    }

    private OnClickListener mOnTitleClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final int position = mListView.getPositionForView((View) v.getParent());
            Log.v(TAG, "Title clicked, row %d", position);
        }
    };

    private OnClickListener mOnTextClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final int position = mListView.getPositionForView((View) v.getParent());
            Log.v(TAG, "Text clicked, row %d", position);
        }
    };
}
Landan answered 16/3, 2011 at 2:54 Comment(5)
or you can also use something like this ListView mListView = (ListView) v.getParent().getParent();Peggie
How can I get the position if I use the adapter in a separate class. I simply used the position inside OnClickListener, something like this like_c.get(position) . It suggest to make the position to final in getView method of the adapter. If I make that change the position I get is same for all the items in the lisview. Could you suggest me how to solve this.Manella
Thats because when you use the position parameter of the getView method it gives you the position of the item of the list which is most recently created but not the one you clickedImbed
@Manikandan: If you're using the getView() method of the adapter then you already have the position passed to you in the method params. In that case you could use something like setTag() to store the position information.Landan
I am using your code in my app but the click event is called after a delay or when I try to scroll the listview. Any ideas why this is happening?Carcinogen
P
22

For future readers:

To select manually the buttons with the trackball use:

myListView.setItemsCanFocus(true);

And to disable the focus on the whole list items:

myListView.setFocusable(false);
myListView.setFocusableInTouchMode(false);
myListView.setClickable(false);

It works fine for me, I can click on buttons with touchscreen and also alows focus an click using keypad

Peyton answered 23/3, 2012 at 14:39 Comment(1)
What about a ExpandableListView, I have multiple views in the item, I just want the views in the child clickable, thanks.Gamely
B
12

I don't have much experience than above users but I faced this same issue and I Solved this with below Solution

<Button
        android:id="@+id/btnRemove"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/btnEdit"
        android:layout_weight="1"
        android:background="@drawable/btn"
        android:text="@string/remove" 
        android:onClick="btnRemoveClick"
        />

btnRemoveClick Click event

public void btnRemoveClick(View v)
{
    final int position = listviewItem.getPositionForView((View) v.getParent()); 
    listItem.remove(position);
    ItemAdapter.notifyDataSetChanged();

}
Buenabuenaventura answered 22/6, 2013 at 5:58 Comment(0)
M
9

Probably you've found how to do it, but you can call

ListView.setItemsCanFocus(true)

and now your buttons will catch focus

Morrill answered 2/8, 2010 at 20:57 Comment(0)
M
5

I am not sure about be the best way, but works fine and all code stays in your ArrayAdapter.

package br.com.fontolan.pessoas.arrayadapter;

import java.util.List;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import br.com.fontolan.pessoas.R;
import br.com.fontolan.pessoas.model.Telefone;

public class TelefoneArrayAdapter extends ArrayAdapter<Telefone> {

private TelefoneArrayAdapter telefoneArrayAdapter = null;
private Context context;
private EditText tipoEditText = null;
private EditText telefoneEditText = null;
private ImageView deleteImageView = null;

public TelefoneArrayAdapter(Context context, List<Telefone> values) {
    super(context, R.layout.telefone_form, values);
    this.telefoneArrayAdapter = this;
    this.context = context;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.telefone_form, parent, false);

    tipoEditText = (EditText) view.findViewById(R.id.telefone_form_tipo);
    telefoneEditText = (EditText) view.findViewById(R.id.telefone_form_telefone);
    deleteImageView = (ImageView) view.findViewById(R.id.telefone_form_delete_image);

    final int i = position;
    final Telefone telefone = this.getItem(position);
    tipoEditText.setText(telefone.getTipo());
    telefoneEditText.setText(telefone.getTelefone());

    TextWatcher tipoTextWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            telefoneArrayAdapter.getItem(i).setTipo(s.toString());
            telefoneArrayAdapter.getItem(i).setIsDirty(true);
        }
    };

    TextWatcher telefoneTextWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            telefoneArrayAdapter.getItem(i).setTelefone(s.toString());
            telefoneArrayAdapter.getItem(i).setIsDirty(true);
        }
    };

    tipoEditText.addTextChangedListener(tipoTextWatcher);
    telefoneEditText.addTextChangedListener(telefoneTextWatcher);

    deleteImageView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            telefoneArrayAdapter.remove(telefone);
        }
    });

    return view;
}

}
Maudemaudie answered 6/7, 2012 at 15:54 Comment(0)
R
3

I Know it's late but this may help, this is an example how I write custom adapter class for different click actions

 public class CustomAdapter extends BaseAdapter {

    TextView title;
  Button button1,button2;

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

    public int getCount() {
        return mAlBasicItemsnav.size();  // size of your list array
    }

    public Object getItem(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = getLayoutInflater().inflate(R.layout.listnavsub_layout, null, false); // use sublayout which you want to inflate in your each list item
        }

        title = (TextView) convertView.findViewById(R.id.textViewnav); // see you have to find id by using convertView.findViewById 
        title.setText(mAlBasicItemsnav.get(position));
      button1=(Button) convertView.findViewById(R.id.button1);
      button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //your click action 

           // if you have different click action at different positions then
            if(position==0)
              {
                       //click action of 1st list item on button click
        }
           if(position==1)
              {
                       //click action of 2st list item on button click
        }
    });

 // similarly for button 2

   button2=(Button) convertView.findViewById(R.id.button2);
      button2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //your click action 

    });



        return convertView;
    }
}
Reposit answered 16/9, 2016 at 5:56 Comment(0)
S
-4

Isn't the platform solution for this implementation to use a context menu that shows on a long press?

Is the question author aware of context menus? Stacking up buttons in a listview has performance implications, will clutter your UI and violate the recommended UI design for the platform.

On the flipside; context menus - by nature of not having a passive representation - are not obvious to the end user. Consider documenting the behaviour?

This guide should give you a good start.

http://www.mikeplate.com/2010/01/21/show-a-context-menu-for-long-clicks-in-an-android-listview/

Sunfast answered 17/7, 2012 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.