Android: ListView elements with multiple clickable buttons

AndroidListviewButton

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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);
        }
    };
}

Solution 3 - Android

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

Solution 4 - Android

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();
			
}

Solution 5 - Android

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

ListView.setItemsCanFocus(true)

and now your buttons will catch focus

Solution 6 - Android

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;
}

}

Solution 7 - Android

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;
    }
}

Solution 8 - Android

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/

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionznqView Question on Stackoverflow
Solution 1 - AndroidznqView Answer on Stackoverflow
Solution 2 - Androidgreg7gkbView Answer on Stackoverflow
Solution 3 - AndroidRudolf RealView Answer on Stackoverflow
Solution 4 - AndroidBhavin ChauhanView Answer on Stackoverflow
Solution 5 - AndroidRafalView Answer on Stackoverflow
Solution 6 - AndroidmFontolanView Answer on Stackoverflow
Solution 7 - AndroidManoharView Answer on Stackoverflow
Solution 8 - AndroidGusdorView Answer on Stackoverflow