【问题标题】:RecyclerView Item Click Listener the Right WayRecyclerView 项目点击监听器的正确方式
【发布时间】:2018-10-02 19:15:26
【问题描述】:

我使用RecyclerView 适配器在活动内显示数据,我想在活动内实现onClickListener,目前,我像往常一样在适配器内设置onClickListener,效果很好。

public void onBindViewHolder(MyHolder holder, final int position) {
    final Listdata data = listdata.get(position);
    holder.vname.setText(data.getName());

    holder.vname.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
        }
    });
}

但是我想在活动中实现它,以便我有更大的控制权。这不符合我的目的。我认为这对我们很多人都有用。

【问题讨论】:

  • 在这个答案中> stackoverflow.com/a/49821315/7765139 我创建了一个易于使用的插件类适配器(ClickableAdapter),它添加就像列表视图的 setOnClickListener 一样,但适用于 RecyclerViews。所以你可以直接在activity中调用adaoter,setOnClickListener并处理那里的点击
  • 您可以在 onCreateViewHolder 中设置 clickListener 而不是 onBindViewHolder。看看我的回答。每次滚动 recyclerView 时都会调用 onBindViewHolder。
  • 如果您愿意使用 Kotlin,那么像 this 这样的东西可能适合您?监听器是在适配器中设置的,但是监听器中使用的函数是在Fragment中声明的

标签: java android android-recyclerview android-gridview


【解决方案1】:

您需要在此处查看this tutorial,以便更好地了解如何实现您想要的行为。

如果要处理来自您的活动的onClickListener,您需要基于带有接口的回调实现来工作。将 Activity 的接口传递给您的适配器,然后在单击某些项目时从您的适配器调用回调函数。

这是教程中的示例实现。

让我们先来看看界面。

public interface OnItemClickListener {
    void onItemClick(ContentItem item);
}

您需要修改您的适配器以将侦听器作为参数,如下所述。

private final List<ContentItem> items;
private final OnItemClickListener listener;

public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
    this.items = items;
    this.listener = listener;
}

现在在您的onBindViewHolder 方法中,设置点击监听器。

@Override public void onBindViewHolder(ViewHolder holder, int position) {
    holder.bind(items.get(position), listener);
}

public void bind(final ContentItem item, final OnItemClickListener listener) {
    ...
    itemView.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            listener.onItemClick(item);
        }
    });
}

现在在您的RecyclerView 中设置适配器。

recycler.setAdapter(new ContentAdapter(items, new ContentAdapter.OnItemClickListener() {
    @Override public void onItemClick(ContentItem item) {
        Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
    }
}));

所以整个适配器代码如下所示。

public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {

    public interface OnItemClickListener {
        void onItemClick(ContentItem item);
    }

    private final List<ContentItem> items;
    private final OnItemClickListener listener;

    public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
        this.items = items;
        this.listener = listener;
    }

    @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
        return new ViewHolder(v);
    }

    @Override public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bind(items.get(position), listener);
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder {

        private TextView name;
        private ImageView image;

        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(R.id.name);
            image = (ImageView) itemView.findViewById(R.id.image);
        }

        public void bind(final ContentItem item, final OnItemClickListener listener) {
            name.setText(item.name);
            Picasso.with(itemView.getContext()).load(item.imageUrl).into(image);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View v) {
                    listener.onItemClick(item);
                }
            });
        }
    }
}

【讨论】:

  • 你的回答很好,我有这样的问题,你能告诉我的问题吗? stackoverflow.com/questions/54507274/…
  • 感谢您的提及。当然,我会检查问题并在有时间时回复您。
  • 在这个 itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.onItemClick(item); } });如何调用更新适配器?
  • 打电话notifyDataSetChanged(),应该可以。
  • 这会在每次调用 onBindViewHolder 时创建一个新的侦听器,这会导致滚动大型列表时由于 GC 导致性能明显下降
【解决方案2】:

非常简单干净的解决方案是:

  create a class with the name of RecyclerTouchListener:

    public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

        private GestureDetector gestureDetector;
        private ClickListener clickListener;

        public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
            this.clickListener = clickListener;
            gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                    return true;
                }

                @Override
                public void onLongPress(MotionEvent e) {
                    View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                    if (child != null && clickListener != null) {
                        clickListener.onLongClick(child, recyclerView.getChildPosition(child));
                    }
                }
            });
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

            View child = rv.findChildViewUnder(e.getX(), e.getY());
            if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
                clickListener.onClick(child, rv.getChildPosition(child));
            }
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        }

        public interface ClickListener {
            void onClick(View view, int position);

            void onLongClick(View view, int position);
        }
    }

在您的 recyclerview 活动中:

recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getApplicationContext(), recyclerView, new RecyclerTouchListener.ClickListener() {
            @Override
            public void onClick(View view, int position) {
                speech(countries_list_code[position]);
            }

            @Override
            public void onLongClick(View view, int position) {

            }
        }));

【讨论】:

    【解决方案3】:

    我找到了超级简单的方法!我推荐这个

    示例代码:

    public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {
    
    public interface OnItemClickListener {
        void onItemClick(ContentItem item);
    }
    
    private final List<ContentItem> items;
    private final OnItemClickListener listener;
    
    public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
        this.items = items;
        this.listener = listener;
    }
    
    @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
        return new ViewHolder(v);
    }
    
    @Override public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bind(items.get(position), listener);
    }
    
    @Override public int getItemCount() {
        return items.size();
    }
    
    static class ViewHolder extends RecyclerView.ViewHolder {
    
        private TextView name;
        private ImageView image;
    
        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(R.id.name);
            image = (ImageView) itemView.findViewById(R.id.image);
        }
    
        public void bind(final ContentItem item, final OnItemClickListener listener) {
            name.setText(item.name);
            Picasso.with(itemView.getContext()).load(item.imageUrl).into(image);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View v) {
                    listener.onItemClick(item);
                }
            });
        }
    }
    }
    

    并使用以下代码使用 RecyclerView 适配器:

    recycler.setAdapter(new ContentAdapter(items, new ContentAdapter.OnItemClickListener() {
    @Override public void onItemClick(ContentItem item) {
        Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
    }
    }));
    

    我从here 找到这个

    希望对你有所帮助。

    【讨论】:

      【解决方案4】:

      onCreateViewHolder 中注册clickListener 而不是onBindViewHolder 的性能更高,因为您只在创建视图时添加侦听器,而不是在滚动recyclerView 时添加。

      我使用 ListAdapter 和 DiffUtil 回调而不是 RecyclerViewAdapter

      abstract class BaseListAdapter<ItemType>(
          callBack: DiffUtil.ItemCallback<ItemType> = DefaultItemDiffCallback(),
          private inline val onItemClicked: ((ItemType, Int) -> Unit)? = null
      ) : ListAdapter<ItemType, BaseItemViewHolder>(
          AsyncDifferConfig.Builder<ItemType>(callBack)
              .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
              .build()
      ) {
      
          override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseItemViewHolder {
      
              return BaseItemViewHolder(
      
                  DataBindingUtil.inflate(
                      LayoutInflater.from(parent.context),
                      getLayoutRes(viewType),
                      parent, false
                  )
              ).apply {
                  onViewHolderCreated(this, viewType, binding)
              }
      
          }
      
          fun createCustomViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
      
              return BaseItemViewHolder(
      
                  DataBindingUtil.inflate(
                      LayoutInflater.from(parent.context),
                      getLayoutRes(viewType),
                      parent, false
                  )
              )
          }
      
          override fun onBindViewHolder(
              holder: BaseItemViewHolder,
              position: Int,
              payloads: MutableList<Any>
          ) {
              val item: ItemType? = currentList.getOrNull(position)
      
              item?.let {
                  holder.binding.setVariable(BR.item, item)
                  onViewHolderBound(holder.binding, item, position, payloads)
                  holder.binding.executePendingBindings()
              }
      
          }
      
          override fun onBindViewHolder(holder: BaseItemViewHolder, position: Int) {
      
          }
      
      
          /**
           * get layout res based on view type
           */
          protected abstract fun getLayoutRes(viewType: Int): Int
      
          /**
           * Called when a ViewHolder is created. ViewHolder is either created first time or
           * when data is refreshed.
           *
           * This method is not called when RecyclerView is being scrolled
           */
          open fun onViewHolderCreated(
              viewHolder: RecyclerView.ViewHolder,
              viewType: Int,
              binding: ViewDataBinding
          ) {
      
              binding.root.setOnClickListener {
                  onItemClicked?.invoke(getItem(viewHolder.bindingAdapterPosition), viewHolder.bindingAdapterPosition)
              }
          }
      
          /**
           * bind view while RecyclerView is being scrolled and new items are bound
           */
          open fun onViewHolderBound(
              binding: ViewDataBinding,
              item: ItemType,
              position: Int,
              payloads: MutableList<Any>
          ) {
      
          }
      
      
      }
      
      open class BaseItemViewHolder(
          val binding: ViewDataBinding
      ) : RecyclerView.ViewHolder(binding.root)
      
      
      class DefaultItemDiffCallback<ItemType> : DiffUtil.ItemCallback<ItemType>() {
      
          override fun areItemsTheSame(
              oldItem: ItemType,
              newItem: ItemType
          ): Boolean {
              return oldItem === newItem
          }
      
          override fun areContentsTheSame(
              oldItem: ItemType,
              newItem: ItemType
          ): Boolean {
              return oldItem.hashCode() == newItem.hashCode()
          }
      }
      

      另一个更好的用户体验是将onBindViewHolderpayLoad 结合使用,这样您就可以只更新部分行而不是整行。例如,你有图像、标题和正文成行,只有正文经常变化,没有有效负载图像闪烁,用户体验不好。但是使用有效负载,您可以决定应该更新行的哪一部分,这样您就不会重新加载未更新的部分。

      【讨论】:

      • 答案是java,我不知道你为什么要发布kotlin。
      • @Adam 作者使用 Kotlin 可能是因为它是 Android 团队推荐使用的语言。
      • @Xam 不是在提出问题时。
      【解决方案5】:

      以我的方式,我只是创建了ClickListener 的单个实例,并将点击事件分派给RecyclerView 和Activity 或Fragment:

      class LeagueAdapter(
          onLeagueSelected: (League, Int, View) -> Unit
      ) : RecyclerView.Adapter<LeagueHolder>() {
          private val dataSet = arrayListOf<League>()
      
          private val clickListener = View.OnClickListener { view ->
              val adapterPosition = view.tag as Int
              onLeagueSelected(dataSet[adapterPosition], adapterPosition, view)
      
              // perform adapter related action here ...
          }
      
      
          override fun getItemCount(): Int {
              return dataSet.size
          }
          
          override fun onBindViewHolder(holder: LeagueHolder, position: Int) {
              // put item position in tag field
              holder.itemView.tag = position
              holder.itemView.setOnClickListener(clickListener)
          }
      }
      

      在 Activity 内部,我们有这样的东西:

      private val headerAdapter = LeagueAdapter { league, i, view ->
          Log.e(TAG, "item clicked $i")
      }
      

      【讨论】:

        【解决方案6】:

        您可以让您的Activity 实现View.OnClickListener 并将其传递给适配器。下面是一个例子。

        class  RAdapter extends RecyclerView.Adapter<>{
            View.OnClickListener listner;
            public RAdapter(View.OnClickListener listner) {
                this.listner = listner;
            }
            public void onBindViewHolder(MyHolder holder, final int position) {
                holder.vname.setOnClickListener(listner);
        
            }
        }
        

        但要处理Activity 中的点击,您将需要点击位置。您可以使用adapter.getAdapterPosition() 来验证单击了哪个项目。

        除此之外,要将点击事件传递给片段/活动,您可以使用自定义回调侦听器,这样您的 Adapter 将可重用。

        ViewHolder 中处理点击的更好方法。请参阅下面的示例。

        class Holder extends RecyclerView.ViewHolder implements View.OnClickListener {
                Button button;
                public Holder(View itemView) {
                    super(itemView);
                    button=itemView.findViewById(R.id.b1);
                    button.setOnClickListener(this);
                }
                @Override
                public void onClick(View v) {
                    if(v.getId()==R.id.b1){
                        int position=getAdapterPosition();
                        // Call the call method here 
                        // with position or data Object itself
                    }
                }
            }
        

        【讨论】:

        • 适配器将视图(无论是 Fragment,Activity 等)连接到 RecyclerView 中的视图(这就是它被称为适配器的原因)。适配器本身不应该知道任何关于调用视图(在你的例子中,活动)
        • 我知道适配器是什么。如果必须从适配器调用 Activity 方法,您有什么建议?
        • 我要说的是您的评论// Call a public method of Activity here 是非常错误和反模式的。如果你想与调用者通信,你需要一个回调函数(例如接口)。由您决定如何设计。
        【解决方案7】:

        如果我理解正确,您想在 Activity 中设置点击逻辑。

        您可以通过在 Activity 中设置 OnClickListener 并在 Adapter 构造函数中传递它来做到这一点。

        MyAdapter myAdapter = new MyAdapter(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
                }
            }));
        

        您的 MyAdapter 构造函数将是:

        final private OnClickListener onClickListener;
        
        public MyAdapter(OnClickListener onClickListener) {
            this.OnClickListener = OnClickListener;
        }
        

        所以你的新代码应该是这样的

        public void onBindViewHolder(MyHolder holder, final int position) {
            final Listdata data = listdata.get(position);
            holder.vname.setText(data.getName());
        
            holder.vname.setOnClickListener(onClickListener);
        

        }

        【讨论】:

          【解决方案8】:

          为适配器类创建接口

          private OnItemClickListener mListener;
          
          public CustomAdapter(List<Listdata> listdata, OnItemClickListener listener) {
              mListener = listener;
              ...
              ...
          }
          
          private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
          
              ViewHolder(View view) {
                  ...
                  ...
                  view.setOnClickLister(this);
              }
          
              @override
              public void onClick(View v) {
                  mListener.onAdapterItemClick(getAdapterPosition())
              }
          }
          
          interface OnItemClickListener {
              void onAdapterItemClick(int position);
          }
          

          让activity实现接口

          public class CustomListActivity extends AppCompatActivity implements OnItemClickListener {
          
          ...
          ...
          
          @override
          public void onAdapterItemClick(int position) {
              Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
          }
          

          还有另一种方法,查看this 实现

          【讨论】:

            【解决方案9】:

            RecyclerView 小部件在这种情况下只有 2 个有用的 listeners

            代码受与Accessibility 相关的TouchEvents 示例启发,在Activity/Fragment 中工作,无需在Adapter 中设置任何侦听器

            recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
                var downTouch = false
                override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
                    when (e.action) {
                        MotionEvent.ACTION_DOWN -> downTouch = true
                        MotionEvent.ACTION_UP -> if (downTouch) {
                            downTouch = false
                            recyclerView.findChildViewUnder(e.x, e.y)?.let {
                                val position = rv.getChildAdapterPosition(it)
                                Toast.makeText(rv.context, "clicked on $position", Toast.LENGTH_SHORT)
                                    .show()
                            }
                        }
                        else -> downTouch = false
                    }
                    return super.onInterceptTouchEvent(rv, e)
                }
            })
            

            【讨论】:

              【解决方案10】:

              就个人而言,我喜欢通过 RxJava 主题来处理这个问题:

              Subject 是一种桥接器或代理,在 ReactiveX 的某些实现中可用,它既充当观察者又充当 Observable。因为它是一个观察者,它可以订阅一个或多个 Observable,并且因为它是一个 Observable,它可以通过重新发射它观察到的项目来传递它们,它也可以发射新的项目。

              更多信息请阅读Understanding RxJava Subject — Publish, Replay, Behavior and Async Subject

              在适配器中:

              public static PublishSubject<MyData> onClickSubject = PublishSubject.create();
              

              ViewHolder:

              public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
                  .
                  .
                  .
              
                  @Override
                  public void onClick(View view) {
                      onClickSubject.onNext(getItem(getAdapterPosition()));
                  }
              }
              

              将您的一次性用品添加到 CompositeDisposable 并在 onDestroy() 中处理它们:

              private CompositeDisposable compositeDisposable = new CompositeDisposable();
              

              在 onCreate() 中:

              compositeDisposable.add(MyAdapter.onClickSubject.subscribe(myData -> {
                  //do something here
              }));
              

              在 onDestroy() 中:

              compositeDisposable.dispose();
              

              注意:

              1. getItem() 是 androidx.recyclerview.widget.ListAdapter 和 androidx.paging.PagedListAdapter 的一个方法,如果您要扩展 RecyclerView.Adapter,您可以按位置从数据列表中获取项目。

              2.使用Disposables需要RxJava2或以上版本

              【讨论】:

                【解决方案11】:

                CodePath 中记录了另一种非常简单的方法。

                ItemClickSupport.addTo(recyclerView).setOnItemClickListener(
                    new ItemClickSupport.OnItemClickListener() {
                        @Override
                        public void onItemClicked(RecyclerView recyclerView, int position, View v) {
                            // do stuff
                        }
                    }
                );
                

                ItemClickSupport的实现。

                【讨论】:

                • 你为我节省了很多时间!!我整天都在这。谢谢。
                【解决方案12】:

                我的项目中总是有一个通用适配器,以避免每次使用 Recyclerview 时都创建一个适配器类。这里举个例子

                public class AdapterRecyclerviewTextOnly extends RecyclerView.Adapter<AdapterRecyclerviewTextOnly.ViewHolder> {
                private RecyclerView recyclerView;
                private OnRecyclerviewListener onRecyclerviewListener;
                
                public interface OnRecyclerviewListener {
                    void onRecyclerviewBind(RecyclerView recyclerView, AdapterRecyclerviewTextOnly.ViewHolder viewHolder, int position);
                    void onRecyclerviewClick(RecyclerView recyclerView, int position);
                    int onItemCount(RecyclerView recyclerView);
                }
                
                public void setOnRecyclerviewListener(OnRecyclerviewListener listener) { this.onRecyclerviewListener = listener; }
                
                public AdapterRecyclerviewTextOnly(RecyclerView recyclerView) {
                    super();
                    this.recyclerView = recyclerView;
                }
                
                
                public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
                    RecyclerView recyclerView;
                    public TextView textView;
                
                    ViewHolder(RecyclerView recyclerView, View itemView) {
                        super(itemView);
                        this.recyclerView = recyclerView;
                        this.itemView.setOnClickListener(this);
                
                        this.textView = itemView.findViewById(R.id.textview_title);
                    }
                
                    void onBind(int position) { onRecyclerviewListener.onRecyclerviewBind(this.recyclerView, this, position); }
                
                    @Override
                    public void onClick(View v) {
                        onRecyclerviewListener.onRecyclerviewClick(this.recyclerView, getAdapterPosition());
                    }
                }
                
                @Override
                public AdapterRecyclerviewTextOnly.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                    View inflatedView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview_text_only, parent, false);
                    return new ViewHolder(this.recyclerView, inflatedView);
                }
                
                @Override
                public void onBindViewHolder(AdapterRecyclerviewTextOnly.ViewHolder holder, int position) {
                    holder.onBind(position);
                }
                
                @Override
                public int getItemCount() {
                    return onRecyclerviewListener.onItemCount(this.recyclerView);
                }
                }
                

                然后在你的活动类中,你可以使用这个适配器:

                    this.recyclerView = findViewById(R.id.recyclerview);
                    this.recyclerView.setHasFixedSize(true);
                    this.recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
                    AdapterRecyclerviewTextOnly recyclerViewAdapter = new AdapterRecyclerviewTextOnly(this.recyclerView);
                    this.recyclerView.setAdapter(this.recyclerViewAdapter);
                    this.recyclerViewAdapter.setOnRecyclerviewListener(new AdapterRecyclerviewTextOnly.OnRecyclerviewListener() {
                        @Override
                        public void onRecyclerviewBind(RecyclerView recyclerView, AdapterRecyclerviewTextOnly.ViewHolder viewHolder, int position) {
                
                        }
                
                        @Override
                        public void onRecyclerviewClick(RecyclerView recyclerView, int position) {
                
                        }
                
                        @Override
                        public int onItemCount(RecyclerView recyclerView) { 
                }
                });
                

                您也可以通过 2 或 3 个 recyclerview 重复使用它。 首先,声明一个全局监听器private AdapterRecyclerviewTextOnly.OnRecyclerviewListener listener;

                然后用新对象初始化监听器,然后用监听器设置你的每一个 recyclerview。使用特定标识符:

                if (recyclerView == recyclerViewA){ } else if (recyclerView == recyclerViewB) { } 管理适配器内的回收视图。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-12-27
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-03-16
                  相关资源
                  最近更新 更多