【问题标题】:OnClickListener for RecyclerViewRecyclerView 的 OnClickListener
【发布时间】:2015-09-25 10:15:12
【问题描述】:

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

由于某种原因,我无法让此代码正常工作。点击事件根本没有注册!谁能告诉我我做错了什么?我已经看到了一些解决方案,但我认为这应该可行。感谢您的帮助!

【问题讨论】:

    标签: android view onclicklistener android-recyclerview


    【解决方案1】:

    不要为整个 RecyclerView 设置 onclicklistener,而是将其设置在 Adapter.ViewHolder 类的构造函数中

    一些示例代码类似于以下类,它是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;
    }    
    

    【讨论】:

      【解决方案2】:

      虽然已经有一些答案,但我想我可能会提供我的实现以及解释。 (详情请见another similar question I answered)。

      因此,要添加点击侦听器,您的内部 ViewHolder 类需要实现 View.OnClickListener。这是因为您将为ViewHolder 的构造函数的itemView 参数设置一个OnClickListener。让我告诉你我的意思:

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

      您需要添加的唯一其他东西是Adapter 的自定义接口和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 方法来控制当您的用户单击特定列表项时您将执行的操作。

      我在this Gist on GitHub 上制作了一组示例,其中显示了可用作模板的完整 Java 文件,或帮助您了解 Adapter 的工作原理。

      【讨论】:

        【解决方案3】:

        谢谢大家!我想出了一个解决方案(尽管它对我有用,但我不确定该技术的效率如何)。我在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注册点击事件,请告诉我。

        【讨论】:

          【解决方案4】:

          您的问题似乎已经得到了答案,但这里的答案都没有尝试使用 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 运算符吗?)。

          你为什么不试试这个?

          【讨论】:

          • 我收到错误无法解析方法“asObservable()”。我错过了什么?
          • @JerabekJakub 使用 import rx.Observable; NOT -> io.reactivex.*;
          【解决方案5】:

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

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

          【讨论】:

          • 你应该在ViewHolder的构造函数中设置监听器,不需要在绑定视图持有者时设置它们。
          • @XaverKapeller 我已经更新了答案,谢谢你的提示!
          【解决方案6】:

          聚会迟到了,但也许这会帮助其他人。

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

          但是,仅注册特定数据集项目(适配器数据数组/列表中特定位置的项目)的更改需要 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 行时,我使用它来显示和隐藏 recyclerview 项目中的最后更新时间。在我的ViewHolder.bind() 中检查了 timeDisplayState,并相应地显示时间文本视图。

          请注意,通过在 ViewHolder 的构造函数中创建 OnCLickListener 可以获得相同的结果,这是因为在 OnCreateViewHolder() 中调用了该构造函数。这样做的好处是适配器可以做更少的事情并将决策/细节委托给持有者,因此,可以使适配器更简单(更少的代码行)和更通用。不利的是,您不能只创建一个 OnClickListener 来不加选择地附加到所有持有者类型;您必须在每个查看器类的构造函数中创建侦听器。不过,这并不是一个真正的大问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-03-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-11-05
            • 2023-03-13
            • 2021-10-24
            • 2021-12-21
            相关资源
            最近更新 更多