RecyclerView 的 OnClickListener

4

与ListView不同,Android RecyclerView的实现似乎要复杂得多。由于RecyclerView子项没有OnItemClickListener,因此我一直在尝试实现以下内容以注册点击事件:

final RecyclerView rv=(RecyclerView)findViewById(R.id.recycler_view);
    LinearLayoutManager llm=new LinearLayoutManager(this);
    rv.setLayoutManager(llm);
    MyRVAdapter rva=new MyRVAdapter(persons);
    rv.setAdapter(rva);

    rv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int itemPosition = rv.indexOfChild(v);
            Log.d("Tag", String.valueOf(itemPosition));
        }
    });

由于某些原因,我无法使这段代码工作。单击事件根本没有被注册!有人能告诉我我做错了什么吗?我看过一些解决方案,但我认为这应该可以工作。感谢您的帮助!

6个回答

6

不要为整个RecyclerView设置onclicklistener,而是将其设置在您的AdapterViewHolder类的构造函数中。

以下是作为RecyclerView适配器内部类的示例代码:

     class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

      .....
        public ViewHolder(View itemView) {
            super(itemView);
           .......
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {

           .......
        }
    }

在你的适配器的 onCreateViewHolder 方法中,将填充后的视图传递给 ViewGroup 的构造函数,如下所示:

 @Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View v = LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.your_layout, viewGroup, false);
    ViewHolder viewHolder = new ViewHolder(v);
    return viewHolder;
}    

5
虽然已经有一些答案了,但我想提供我的实现方法以及解释。要添加单击监听器,您的内部ViewHolder类需要实现View.OnClickListener接口。这是因为您将在ViewHolder的构造函数中将OnClickListener设置到itemView参数上。让我向您展示我的意思。 (有关完整细节,请参见我回答的另一个类似的问题。)
public class ExampleClickViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    TextView text1, text2;

    ExampleClickViewHolder(View itemView) {
        super(itemView);

        // we do this because we want to check when an item has been clicked:
        itemView.setOnClickListener(this);

        // now, like before, we assign our View variables
        title = (TextView) itemView.findViewById(R.id.text1);
        subtitle = (TextView) itemView.findViewById(R.id.text2);
    }

    @Override
    public void onClick(View v) {
        // The user may not set a click listener for list items, in which case our listener
        // will be null, so we need to check for this
        if (mOnEntryClickListener != null) {
            mOnEntryClickListener.onEntryClick(v, getLayoutPosition());
        }
    }
}

您需要添加的唯一其他内容是自定义适配器界面和一个setter方法:

private OnEntryClickListener mOnEntryClickListener;

public interface OnEntryClickListener {
    void onEntryClick(View view, int position);
}

public void setOnEntryClickListener(OnEntryClickListener onEntryClickListener) {
    mOnEntryClickListener = onEntryClickListener;
}

你的新的支持点击的 Adapter 已经完成。

现在,让我们来使用它...

    ExampleClickAdapter clickAdapter = new ExampleClickAdapter(yourObjects);
    clickAdapter.setOnEntryClickListener(new ExampleClickAdapter.OnEntryClickListener() {
        @Override
        public void onEntryClick(View view, int position) {
            // stuff that will happen when a list item is clicked
        }
    });

基本上,您可以像设置普通的Adapter一样进行操作,只是要使用您创建的setter方法来控制当用户单击特定列表项时要执行的操作。
此GitHub Gist示例中,我提供了一组完整的Java文件,您可以将其用作模板,或者帮助您了解Adapter的工作原理。

3

看起来你已经得到了问题的答案,但这里的回答都没有尝试使用RxJava解决该问题。

我自己是RxJava的粉丝,只要有机会我就不会错过使用它的机会。

下面是我使用的方式:

public class ReactiveAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    String[] mDataset = { "Data", "In", "Adapter" };

    private final PublishSubject<String> onClickSubject = PublishSubject.create();

    @Override 
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final String element = mDataset[position];

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               onClickSubject.onNext(element);
            }
        });
    }

    public Observable<String> getPositionClicks(){
        return onClickSubject.asObservable();
    }
}

它公开了一个Observable来截取点击事件。现在您完全掌控自己的点击事件,并且可以随心所欲地处理它们(还记得RxJava操作符吗?)。为什么不试试呢?

我遇到了错误:Cannot resolve method 'asObservable()'。我缺少了什么? - JerabekJakub
1
@JerabekJakub 使用 import rx.Observable; 而不是 -> io.reactivex.*; - Dr.jacky

2

虽然来晚了,但也许这会帮助其他人。

我找到的将OnClickListener附加到RecyclerView中项目或子视图的最佳位置是在RecyclerView.AdapteronCreateViewHolder中。由于RecyclerView回收/重用项目ViewHolders,因此创建/附加侦听器的效率最高,因为它们仅在创建新持有者时创建和附加,因此比onBindViewHolder甚至绑定(在持有者类内部)频繁得多。

但是,需要2-3个东西才能仅注册对特定数据集项的更改(在适配器数据数组/列表中的特定位置的项目)。

如果您有多个ViewHolder类型,并且该信息有助于确定单击时发生的情况,则需要:

  1. ViewHolder类型或访问Adapter.getViewHolderType()

更明显的是,您需要访问单击的数据集(数组/列表)项,以便可以在该项中注册特定于该项的更改(因此当该项滚动屏幕时更改保留在该项中)。这意味着您需要访问:

  1. RecyclerView的数据集(通常为数组或列表)和
  2. 数据集中需要更改的特定项目的位置

您还可能需要调用notifyItemChanged()notifyItemInserted()或(效率较低的notifyDataSetChanged()

例如,在我的一个项目的适配器中:

// Inflates the appropriate layout according to the ViewType.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View view;
    if (viewType == HOLDER_VIEW_TYPE_ONE) {          
        view = LayoutInflater.from(mContext).inflate(R.layout.type_one_holder_layout, parent, false);
        final RecyclerView.ViewHolder holder = new TypeOneHolder(mContext,view);

        //Place onclick listener after holder. holder needs to be final
        //but thats fine as onCreateViewHolder usually just returns holder
        //right after its created. onBindViewHolder is where changes are
        //made.
        view.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View view) {
                final int position = holder.getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    if (mDataList.get(position).getTimeDisplayState() == ON_STATE) {
                        mMessageList.get(position).setTimeDisplayState(DEFAULT_STATE);
                    } else {
                        mMessageList.get(position).setTimeDisplayState(ON_STATE);
                    }
                    notifyItemChanged(position);
                }
            }

        });
        return holder;
    } 
}

请注意,我使用这个方法在RecyclerView项被点击时显示和隐藏最后更新时间。timeDisplayState在我的ViewHolder.bind()中进行检查,并且相应地显示时间TextView。
请注意,通过在ViewHolder的构造函数中创建OnClickListener也可以实现相同的结果,因为该构造函数在OnCreateViewHolder()中调用。这样做的好处是适配器可以少做一些工作,并将决策/细节委托给持有者,从而使适配器更简单(代码行数更少)和更通用。缺点是您不能仅创建一个OnClickListener并 indiscriminately 附加到所有Holder类型;您必须在每个viewholder类的构造函数中创建侦听器。不过这并不是一个重大问题。

2

您应该在RecyclerView中的各个视图上设置点击监听器,而不是整个RecyclerView。可以通过在ViewHolder类的构造函数中设置监听器来实现此目的,类似于以下示例:

class ViewHolder extends RecyclerView.ViewHolder {
    public ViewHolder(View view, OnClickListener listener) {
        super(view);             
        view.setOnClickListener(listener);
    }
}

3
你应该在 ViewHolder 的构造函数中设置监听器,不必每次绑定视图持有者时都设置它们。 - Xaver Kapeller
1
@XaverKapeller 我已经更新了答案,感谢您的提示! - fractalwrench

2

谢谢!我已经想出了一个解决方案(尽管对我有效,但我不确定技术的效率如何)。我在我的RecyclerView.Adapter类中使用了View.OnClickListener接口。为了防止大规模的垃圾回收,我将点击监听器分配给了RecyclerView.AdapteronCreateViewHolder方法中。这是我RecyclerView.Adapter类的完整实现:

package com.example.evinish.recyclerdemo;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * Created by evinish on 9/25/2015.
 */
public class MyRVAdapter extends RecyclerView.Adapter<MyViewHolder> implements View.OnClickListener{

    List<Person> persons;

    public MyRVAdapter(List<Person> persons) {
        this.persons = persons;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.row_layout,parent,false);
        MyViewHolder vh=new MyViewHolder(view);
        view.setOnClickListener(this);

        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.company.setText(persons.get(position).getCompany());
        holder.occupation.setText(persons.get(position).getOccupation());
        holder.name.setText(persons.get(position).getName());


    }

    @Override
    public int getItemCount() {
        return persons.size();
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
    }

    public void clearAdapter() {
        persons.clear();
        notifyDataSetChanged();
    }

    @Override
    public void onClick(View v) {
        TextView clickedName=(TextView)v.findViewById(R.id.card_text1);
        Toast.makeText(v.getContext(),clickedName.getText().toString(),Toast.LENGTH_LONG).show();

    }
}

如果有更好的方法可以在RecyclerView中注册点击事件,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接