java ListView 行按钮:如何创建将 View.OnClickListener 连接到 ListView 每一行上的按钮的自定义适配器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16076985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 21:50:00  来源:igfitidea点击:

ListView row buttons: How do I create a custom Adapter that connects a View.OnClickListener to a button on each row of a ListView?

javaandroidlistviewbuttononclick

提问by WonderWorker

I want my ListView to contain buttons, but setting the button's xml property, onClick="myFunction" and then placing a public void myFunction(android.view.View view) method in the activity causes an NoSuchMethodException (the stack trace is null) to be thrown, as although the onclick listener is there, it doesn't fire myFunction(...) and cause the activity to close.

我希望我的 ListView 包含按钮,但是设置按钮的 xml 属性 onClick="myFunction" 然后在活动中放置一个 public void myFunction(android.view.View view) 方法会导致 NoSuchMethodException(堆栈跟踪为空)被抛出,虽然 onclick 侦听器在那里,但它不会触发 myFunction(...) 并导致活动关闭。

How do I create a custom Adapter that connects a View.OnClickListener to a button on each row of a ListView?

如何创建将 View.OnClickListener 连接到 ListView 每一行上的按钮的自定义适配器?

My ListView is created as follows...

我的 ListView 创建如下...

[activity.java content..]

[activity.java 内容..]

public void myFunction(android.view.View view)
{
    //Do stuff
}

[activity.xml content..]

[activity.xml 内容..]

<LinearLayout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".FrmCustomerDetails" >
    <ListView android:id="@+id/LstCustomerDetailsList" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:clickable="true" android:clipChildren="true" android:divider="@null" android:dividerHeight="0dp" android:fastScrollEnabled="true" android:footerDividersEnabled="false" android:headerDividersEnabled="false" android:requiresFadingEdge="vertical" android:smoothScrollbar="true" />
</LinearLayout>

[activity_row_item.xml content..]

[activity_row_item.xml 内容..]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/Llt" android:layout_width="match_parent" android:layout_height="match_parent" >
    <Button android:id="@+id/Btn" android:text="Click me" android:onClick="myFunction" />
</LinearLayout>

回答by WonderWorker

Here is how to create the custom Adapter, connecting View.OnClickListener to a ListView with a button per row...

这是创建自定义适配器的方法,将 View.OnClickListener 连接到每行一个按钮的 ListView ...

1. Create a layout for a typical row

1. 为典型行创建布局

In this case, the row is composed of three view components:

在这种情况下,行由三个视图组件组成:

  • name (EditText)
  • value (EditText:inputType="numberDecimal")
  • delete (Button)
  • 名称(编辑文本)
  • 值 (EditText:inputType="numberDecimal")
  • 删除(按钮)

Xml

xml

pay_list_item.xml layout is as follows:

pay_list_item.xml 布局如下:

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

    <EditText
        android:id="@+id/pay_name"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="2"
        android:hint="Name" />

    <EditText
        android:id="@+id/pay_value"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:inputType="numberDecimal"
        android:text="0.0" />

    <Button
        android:id="@+id/pay_removePay"
        android:layout_width="100dp"
        android:layout_height="fill_parent"
        android:text="Remove Pay"
        android:onClick="removePayOnClickHandler" />

</LinearLayout>

Note: the button has onClick handler defined in xml layout file, because we want to refer its action to a specific list item.

注意:按钮在 xml 布局文件中定义了 onClick 处理程序,因为我们希望将其操作引用到特定的列表项。

Doing this means that the handler will be implemented in Activity file and each button will know which list item it belongs to.

这样做意味着处理程序将在 Activity 文件中实现,每个按钮将知道它属于哪个列表项。

2. Create list item adapter

2. 创建列表项适配器

This is the java class that is the controller for pay_list_item.xml.

这是作为pay_list_item.xml 控制器的java 类。

It keeps references for all of its views, and it also puts these references in tags, extending the ArrayAdapter interface.

它保留所有视图的引用,并将这些引用放在标签中,扩展 ArrayAdapter 接口。

The Adapter:

适配器:

public class PayListAdapter extends ArrayAdapter<Payment> {

    private List<Payment> items;
    private int layoutResourceId;
    private Context context;

    public PayListAdapter(Context context, int layoutResourceId, List<Payment> items) {
        super(context, layoutResourceId, items);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.items = items;
    }

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

        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        row = inflater.inflate(layoutResourceId, parent, false);

        holder = new PaymentHolder();
        holder.Payment = items.get(position);
        holder.removePaymentButton = (ImageButton)row.findViewById(R.id.pay_removePay);
        holder.removePaymentButton.setTag(holder.Payment);

        holder.name = (TextView)row.findViewById(R.id.pay_name);
        holder.value = (TextView)row.findViewById(R.id.pay_value);

        row.setTag(holder);

        setupItem(holder);
        return row;
    }

    private void setupItem(PaymentHolder holder) {
        holder.name.setText(holder.Payment.getName());
        holder.value.setText(String.valueOf(holder.Payment.getValue()));
    }

    public static class PaymentHolder {
        Payment Payment;
        TextView name;
        TextView value;
        ImageButton removePaymentButton;
    }
}

Here we list the Payment class items.

在这里,我们列出了 Payment 类项目。

There are three most important elements here:

这里有三个最重要的元素:

  • PayListAdapter constructor: sets some private fields and calls superclass constructor. It also gets the List of Payment objects. Its implementation is obligatory.
  • PaymentHolder: static class that holds references to all views that I have to set in this list item. I also keep the Payment object that references to this particular item in list. I set it as tag for ImageButton, that will help me to find the Payment item on list, that user wanted to remove
  • Overriden getView method: called by superclass. Its goal is to return the single List row. We create its fields and setup their values and store them in static holder. Holder then is put in row's tag element. Note that there is a performance issue, as the row is being recreated each time it is displayed. I used to add some flag in holder like isCreated, and set it to true after row was already created. then you can add if statement and read tag's holder instead of creating it from scratch.
  • PayListAdapter 构造函数:设置一些私有字段并调用超类构造函数。它还获取付款对象列表。它的实施是强制性的。
  • PaymentHolder:静态类,包含对我必须在此列表项中设置的所有视图的引用。我还将引用此特定项目的 Payment 对象保留在列表中。我将它设置为 ImageButton 的标签,这将帮助我找到列表中的付款项目,该用户想要删除
  • 覆盖 getView 方法:由超类调用。它的目标是返回单个 List 行。我们创建它的字段并设置它们的值并将它们存储在静态持有者中。然后将持有者放入行的标签元素中。请注意,存在性能问题,因为每次显示该行时都会重新创建该行。我曾经在持有者中添加一些标志,如 isCreated,并在行创建后将其设置为 true。然后您可以添加 if 语句并读取标签的持有者,而不是从头开始创建它。

Payment.java is quite simple as for now and it looks a bit like BasicNameValuePair:

Payment.java 就目前而言非常简单,它看起来有点像 BasicNameValuePair:

public class Payment implements Serializable {
    private String name = "";
    private double value = 0;

    public Payment(String name, double value) {
        this.setName(name);
        this.setValue(value);
    }
...
}

There are additional gets and sets for each private field not shown.

每个未显示的私有字段都有额外的获取和设置。

3. Add ListView to the activity layout xml file

3.在Activity布局xml文件中添加ListView

In its simpliest form, it will be enough to add this view to activity layout:

以最简单的形式,将这个视图添加到活动布局就足够了:

<ListView 
    android:id="@+id/EnterPays_PaysList"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
</ListView>

4. Set up adapter to this list view in Activity Java code

4. 在 Activity Java 代码中为这个列表视图设置适配器

In order to display items in ListView you need to set up its adapter and map it to some other ArrayList of Payment objects (as I am extending an Array adapter here). Here is code that is responsible for binding adapter to editPersonData.getPayments() ArrayList:

为了在 ListView 中显示项目,您需要设置它的适配器并将其映射到支付对象的其他一些 ArrayList(因为我在这里扩展了一个 Array 适配器)。这是负责将适配器绑定到 editPersonData.getPayments() ArrayList 的代码:

PayListAdapter adapter = new PayListAdapter(AddNewPerson.this, R.layout.pay_list_item, editPersonData.getPayments());
ListView PaysListView = (ListView)findViewById(R.id.EnterPays_PaysList);
PaysListView.setAdapter(adapter);

5. Adding / removing items to ListView (and its adapter)

5. 向 ListView(及其适配器)添加/删除项目

Adapter is handled just like any other ArrayList, so adding new element to it is as simple as:

Adapter 的处理方式与任何其他 ArrayList 一样,因此向其添加新元素非常简单:

Payment testPayment = new Payment("Test", 13);
adapter.add(testPayment);
adapter.remove(testPayment);

6. Handle Remove Payment button click event

6.处理Remove Payment按钮点击事件

In an activity's code, where ListView is displayed, add public method that will handle remove button click action. The method name has to be exactly the same as it was in pay_list_item.xml:

在显示 ListView 的活动代码中,添加将处理删除按钮单击操作的公共方法。方法名称必须与 pay_list_item.xml 中的名称完全相同:

android:onClick="removePayOnClickHandler"
The method body is as follows:

public void removePayOnClickHandler(View v) {
    Payment itemToRemove = (Payment)v.getTag();
    adapter.remove(itemToRemove);
}

The Payment object was stored in ImageButton's Tag element. Now it is enough to read it from Tag, and remove this item from the adapter.

Payment 对象存储在 ImageButton 的 Tag 元素中。现在从 Tag 中读取它就足够了,并从适配器中删除此项目。

7. Incorporate remove confirmation dialog window

7. 合并移除确认对话窗口

Probably you need also make sure that user intentionally pressed the remove button by asking him additional question in confirmation dialog.

可能您还需要通过在确认对话框中询问其他问题来确保用户有意按下了删除按钮。

Dialogue

对话

a) Create dialog's id constant

a) 创建对话框的 id 常量

This is simply dialog's ID. it should be unique among any other dialog window that is handled by current activity. I set it like that:

这只是对话框的 ID。它在当前活动处理的任何其他对话窗口中应该是唯一的。我是这样设置的:

protected static final int DIALOG_REMOVE_CALC = 1;
protected static final int DIALOG_REMOVE_PERSON = 2;

b) Build dialog

b) 构建对话框

I use this method to build dialog window:

我使用这种方法来构建对话窗口:

private Dialog createDialogRemoveConfirm(final int dialogRemove) {
    return new AlertDialog.Builder(getApplicationContext())
    .setIcon(R.drawable.trashbin_icon)
    .setTitle(R.string.calculation_dialog_remove_text)
    .setPositiveButton(R.string.calculation_dialog_button_ok, new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton) {
            handleRemoveConfirm(dialogRemove);
        }
    })
    .setNegativeButton(R.string.calculation_dialog_button_cancel, null)
    .create();
}

AlertDialog builder pattern is utilized here. I do not handle NegativeButton click action – by default the dialog is just being hidden. If dialog's confirm button is clicked, my handleRemoveConfirm callback is called and action is performed based on dialog's ID:

此处使用 AlertDialog 构建器模式。我不处理 NegativeButton 单击动作——默认情况下,对话框只是被隐藏。如果单击对话框的确认按钮,将调用我的 handleRemoveConfirm 回调并根据对话框的 ID 执行操作:

protected void handleRemoveConfirm(int dialogType) {
    if(dialogType == DIALOG_REMOVE_PERSON){
        calc.removePerson();
    }else if(dialogType == DIALOG_REMOVE_CALC){
        removeCalc();
    }
}

c) Show Dialog

c) 显示对话框

I show dialog after my remove button click. The showDialog(int) is Android's Activity's method:

单击删除按钮后显示对话框。showDialog(int) 是 Android 的 Activity 方法:

OnClickListener removeCalcButtonClickListener = new OnClickListener() {
    public void onClick(View v) {
        showDialog(DIALOG_REMOVE_CALC);
    }
};

the showDialog(int) method calls onCreateDialog (also defined in Activity's class). Override it and tell your app what to do if the showDialog was requested:

showDialog(int) 方法调用 onCreateDialog(也在 Activity 的类中定义)。覆盖它并告诉您的应用程序在请求 showDialog 时该做什么:

@Override
protected Dialog onCreateDialog(int id) {
    switch (id) {
    case DIALOG_REMOVE_CALC:
        return createDialogRemoveConfirm(DIALOG_REMOVE_CALC);
    case DIALOG_REMOVE_PERSON:
        return createDialogRemoveConfirm(DIALOG_REMOVE_PERSON);
    }
}

回答by Emil Adz

Take a look at this blog post I wrote on exactly this matter:

看看我写的关于这个问题的这篇博文:

Create custom ArrayAdapter

创建自定义 ArrayAdapter

There are comments that explain every action I make in the adapter. Here is the explanation in short:

有一些注释解释了我在适配器中所做的每一个操作。以下是简短的解释:

So lets for example take a row where you want to place a CheckBox, ImageViewand a TextViewwhile all of them are clickable. Meaning that you can click the row it self for going to another Actvityfor more details on the row, check its CheckBox or press the ImageViewto perform another operation.

例如,让我们在一行中放置一个CheckBoxImageView然后TextView所有这些都是可点击的。这意味着您可以单击该行以转到另一行以Actvity了解该行的更多详细信息,检查其k复选框或按ImageView执行其他操作。

So what you should do is:

所以你应该做的是:

1.First create an XML layoutfile for your ListViewrow:

1.首先XML layout为您的ListView行创建一个文件:

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<CheckBox
    android:id="@+id/cbCheckListItem"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp" />
<TextView
    android:id="@+id/tvItemTitle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="item string" />
<ImageView
    android:id="@+id/iStatus"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:contentDescription="@drawable/ic_launcher"
    android:src="@drawable/ic_launcher" /> 
  </LinearLayout>

2.Second in your java code define a ViewHolder, a ViewHolderis designed to hold the row views and that way operating more quickly:

2.其次在你的 java 代码中定义 a ViewHolder, aViewHolder旨在保存行视图,这样操作起来更快:

static class ViewHolder 
{
TextView title;
CheckBox checked;
ImageView changeRowStatus;
}

3.Now we have to define CustomArrayAdapter, using the array adapter we can define precisely what is the desired output for each row based on the content of this row or it's position. We can do so by overriding the getView method:

3.现在我们必须定义CustomArrayAdapter,使用数组适配器,我们可以根据每行的内容或其位置精确定义每行所需的输出。我们可以通过覆盖 getView 方法来实现:

private class CustomArrayAdapter extends ArrayAdapter<RowData>
{   
private ArrayList<RowData> list;

//this custom adapter receives an ArrayList of RowData objects.
//RowData is my class that represents the data for a single row and could be anything.
public CustomArrayAdapter(Context context, int textViewResourceId, ArrayList<RowData> rowDataList) 
{
    //populate the local list with data.
    super(context, textViewResourceId, rowDataList);
    this.list = new ArrayList<RowData>();
    this.list.addAll(rowDataList);
}

public View getView(final int position, View convertView, ViewGroup parent)
{
    //creating the ViewHolder we defined earlier.
    ViewHolder holder = new ViewHolder();) 

    //creating LayoutInflator for inflating the row layout.
    LayoutInflater inflator = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    //inflating the row layout we defined earlier.
    convertView = inflator.inflate(R.layout.row_item_layout, null);

    //setting the views into the ViewHolder.
    holder.title = (TextView) convertView.findViewById(R.id.tvItemTitle);
    holder.changeRowStatus = (ImageView) convertView.findViewById(R.id.iStatus);
    holder.changeRowStatus.setTag(position);

    //define an onClickListener for the ImageView.
    holder.changeRowStatus.setOnClickListener(new OnClickListener() 
    {           
        @Override
        public void onClick(View v) 
        {
            Toast.makeText(activity, "Image from row " + position + " was pressed", Toast.LENGTH_LONG).show();
        }
    });
    holder.checked = (CheckBox) convertView.findViewById(R.id.cbCheckListItem);
    holder.checked.setTag(position);

    //define an onClickListener for the CheckBox.
    holder.checked.setOnClickListener(new OnClickListener() 
    {       
        @Override
        public void onClick(View v)
        {
            //assign check-box state to the corresponding object in list.    
            CheckBox checkbox = (CheckBox) v;
            rowDataList.get(position).setChecked(checkbox.isChecked());
            Toast.makeText(activity, "CheckBox from row " + position + " was checked", Toast.LENGTH_LONG).show();    
        }
    });

    //setting data into the the ViewHolder.
    holder.title.setText(RowData.getName());
    holder.checked.setChecked(RowData.isChecked());

    //return the row view.
    return convertView;
}
}

4.Now you need to set this adapter, as the adapter of your ListView. this ListViewcan be created in java or using an XML file, in this case I'm using a list that was defined in the XML file using the “list” id:

4.现在你需要设置这个适配器,作为你的ListView. 这ListView可以在 java 中或使用 XML 文件创建,在这种情况下,我使用在 XML 文件中使用“列表”ID 定义的列表:

public void onCreate(Bundle savedInstanceState) 
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout); 
ListView list = (ListView)findViewById(R.id.list);
CustomArrayAdapter dataAdapter = new CustomArrayAdapter(this, R.id.tvItemTitle, rowDataList);
list.setAdapter(dataAdapter);
}

5.Finally if we want to be able to press the row it self and not only a certain view in it we should assign an onItemClickListenerto the ListView:

5.最后,如果我们希望能够按它应该分配一个行它的自我,而不是只有某一观点onItemClickListenerListView

list.setOnItemClickListener(new OnItemClickListener() 
{
public void onItemClick(AdapterView<?> parent, View view,int position, long id) 
{
    Toast.makeText(activity, "row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});

回答by shiladitya

First, the way of adding listeners in xml using onClick="function" is deprecated. You need a ViewHolder class to link the button in the xml to your java code. Then you can implement onClickListener for that.

首先,不推荐使用 onClick="function" 在 xml 中添加侦听器的方式。您需要一个 ViewHolder 类将 xml 中的按钮链接到您的 java 代码。然后你可以为此实现 onClickListener 。

回答by Mani

Inside your getView() implementation of CustomAdapter, you can try like below.

在 CustomAdapter 的 getView() 实现中,您可以尝试如下。

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

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.xxxxx, null);
            holder = new ViewHolder();
            holder.invite = (Button) convertView.findViewById(R.id.button);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }


        final int pos = position;
        holder.button.setOnClickListener(new View.OnClickListener() {
                @Override
        public void onClick(View v) {
            handleClick(pos);   
            }
        });
 }

    class ViewHolder {
        Button button;
    }