Why Doesn't Recyclerview Have Onitemclicklistener()

Why doesn't RecyclerView have onItemClickListener()?

tl;dr 2016 Use RxJava and a PublishSubject to expose an Observable for the clicks.

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

Original Post:

Since the introduction of ListView, onItemClickListener has been problematic. The moment you have a click listener for any of the internal elements the callback would not be triggered but it wasn't notified or well documented (if at all) so there was a lot of confusion and SO questions about it.

Given that RecyclerView takes it a step further and doesn't have a concept of a row/column, but rather an arbitrarily laid out amount of children, they have delegated the onClick to each one of them, or to programmer implementation.

Think of Recyclerview not as a ListView 1:1 replacement but rather as a more flexible component for complex use cases. And as you say, your solution is what google expected of you. Now you have an adapter who can delegate onClick to an interface passed on the constructor, which is the correct pattern for both ListView and Recyclerview.

public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

public TextView txtViewTitle;
public ImageView imgViewIcon;
public IMyViewHolderClicks mListener;

public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
super(itemLayoutView);
mListener = listener;
txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
imgViewIcon.setOnClickListener(this);
itemLayoutView.setOnClickListener(this);
}

@Override
public void onClick(View v) {
if (v instanceof ImageView){
mListener.onTomato((ImageView)v);
} else {
mListener.onPotato(v);
}
}

public static interface IMyViewHolderClicks {
public void onPotato(View caller);
public void onTomato(ImageView callerImage);
}

}

and then on your adapter

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

String[] mDataset = { "Data" };

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

MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() {
public void onPotato(View caller) { Log.d("VEGETABLES", "Poh-tah-tos"); };
public void onTomato(ImageView callerImage) { Log.d("VEGETABLES", "To-m8-tohs"); }
});
return vh;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Get element from your dataset at this position
// Replace the contents of the view with that element
// Clear the ones that won't be used
holder.txtViewTitle.setText(mDataset[position]);
}

// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length;
}
...

Now look into that last piece of code: onCreateViewHolder(ViewGroup parent, int viewType) the signature already suggest different view types. For each one of them you'll require a different viewholder too, and subsequently each one of them can have a different set of clicks. Or you can just create a generic viewholder that takes any view and one onClickListener and applies accordingly. Or delegate up one level to the orchestrator so several fragments/activities have the same list with different click behaviour. Again, all flexibility is on your side.

It is a really needed component and fairly close to what our internal implementations and improvements to ListView were until now. It's good that Google finally acknowledges it.

RecyclerVIew onItemClickListener Not working

You this approach:

@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) {
//your code
}
});
}

RecyclerView Item Click Listener the Right Way

You need to check this tutorial here for better understanding on how you can achieve the behaviour that you want.

In case of handling the onClickListener from your activity you need to work based on a callback implementation with an interface. Pass the interface from the activity to your adapter and then call the callback function from your adapter when some items are clicked.

Here's a sample implementation from the tutorial.

Let us first have the interface.

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

You need to modify your adapter to take the listener as the parameter like the one stated below.

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

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

Now in your onBindViewHolder method, set the click listener.

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

Now setting the adapter in your RecyclerView.

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

So the whole adapter code looks like the following.

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

How to add OnItemClick Listener on recycler view

Create a custom interface class like this

public interface ClickInterface {

public void recyclerviewOnClick(int position);
}

implement it in your Fragment and initialize the interface

YourFragment extends Fragment implements ClickInterface{

private ClickInterface listner;

------- Your oncreateView --------
listner=this; //Now pass this in your adapter
}

In your adapter constructor get this listner like this

public MyAdapter(String[] myDataset,ClickInterface listner){
this.mDataset = myDataset;
this.listner=listner;
}

and at last in your ViewHolder

public class MyViewHolder extends RecyclerView.ViewHolder{
public CardView mCardView;
public TextView mTextView;
public MyViewHolder(View v){
super(v);

mCardView = (CardView) v.findViewById(R.id.card_view);
mTextView = (TextView) v.findViewById(R.id.tv_text);

mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

listner.recyclerviewOnClick(getAdapterPosition());

}
});
}
}

Now you will get the position in your fragment in

public void recyclerviewOnClick(int position){
// Here you will get the position
}


Related Topics



Leave a reply



Submit