Swipe and OnClick events in RecyclerView
I achieved that by assigning OnClickListener
for the buttons in the ViewHolder
, and creating an interface for the touch events.
The sample project is on GitHub: https://github.com/brnunes/SwipeableRecyclerView.
In my case each item is a CardView
with two buttons, and I want to detect the touch events in the CardView
and the Button
s. The CardView
layout looks like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical"
card_view:cardCornerRadius="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/card_view_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="24sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="@id/card_view_title"
android:layout_centerHorizontal="true"
android:gravity="center">
<Button
android:id="@+id/card_view_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1" />
<Button
android:id="@+id/card_view_button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2" />
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
Then in the Activity
, I declared an interface to receive the touch events:
public interface OnItemTouchListener {
public void onCardViewTap(View view, int position);
public void onButton1Click(View view, int position);
public void onButton2Click(View view, int position);
}
And in the ViewHolder
I assign OnClickListeners
to the objects that I want to listen to, and call my custom listener:
public class CardViewAdapter extends RecyclerView.Adapter<CardViewAdapter.ViewHolder> {
private List<String> cards;
private OnItemTouchListener onItemTouchListener;
public CardViewAdapter(List<String> cards, OnItemTouchListener onItemTouchListener) {
this.cards = cards;
this.onItemTouchListener = onItemTouchListener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_view_layout, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.title.setText(cards.get(i));
}
@Override
public int getItemCount() {
return cards == null ? 0 : cards.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView title;
private Button button1;
private Button button2;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.card_view_title);
button1 = (Button) itemView.findViewById(R.id.card_view_button1);
button2 = (Button) itemView.findViewById(R.id.card_view_button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemTouchListener.onButton1Click(v, getPosition());
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemTouchListener.onButton2Click(v, getPosition());
}
});
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemTouchListener.onCardViewTap(v, getPosition());
}
});
}
}
}
Finally, instantiate the custom OnItemTouchListener
and pass it to the CardViewAdapter
constructor:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mItems = new ArrayList<>(30);
for (int i = 0; i < 30; i++) {
mItems.add(String.format("Card number %2d", i));
}
OnItemTouchListener itemTouchListener = new OnItemTouchListener() {
@Override
public void onCardViewTap(View view, int position) {
Toast.makeText(MainActivity.this, "Tapped " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
@Override
public void onButton1Click(View view, int position) {
Toast.makeText(MainActivity.this, "Clicked Button1 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
@Override
public void onButton2Click(View view, int position) {
Toast.makeText(MainActivity.this, "Clicked Button2 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
};
mAdapter = new CardViewAdapter(mItems, itemTouchListener);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
// ... Assign the swipe listener
}
Related videos on Youtube
![Ivan Fork](https://i.stack.imgur.com/ANQtb.png?s=256&g=1)
Ivan Fork
Updated on January 11, 2020Comments
-
Ivan Fork over 4 years
I'm trying to implement a swipe to dismiss action in a RecyclerView but when I set an OnClickListener on any View in a ViewHolder it overrides all OnTouch events on that view.
I can abandon OnClickListener and handle all clicks in the TouchListener but if I have multiple buttons in a child view of the RecycleView than that will be a lot of code and this doesn't look like a right way.
In my RecyleView I'm setting Swipe to dismiss listeners (similar to this):
setOnTouchListener(touchListener); setOnScrollListener(touchListener.makeScrollListener());
It works in the ListView, but in the RecycleView the OnClickListener blocks OnTouchListner events.
Example of the layout for ViewHolder view.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/card_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:descendantFocusability="blocksDescendants"> <ImageView android:id="@+id/keep_icon" android:layout_width="48dp" android:layout_height="48dp" android:layout_centerInParent="true" android:src="@drawable/ic_received" />
Inflating in the RecyclerView.Adapter:
@Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = mInflater.inflate(R.layout.push_card_view_compat, viewGroup, false); return new ViewHolder(v, onClickListener, onKeepListener); }
The ViewHolder:
public ViewHolder(final View itemView, final OnViewHolderClickListener onClickListener, final OnKeepListener onKeepListener) { super(itemView); keepButton = (ImageView) itemView.findViewById(R.id.keep_icon); itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onItemClickListener.onClick(getPosition(), itemView); } }); keepButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onKeepListener.onClick(getPosition(), itemView); } }); }
-
kandroidj over 9 yearstry adding to your xml outter ViewGroup for row items:
android:descendantFocusability="blocksDescendants"
-
Ivan Fork over 9 years@inner_class7 It didn't work.
-
kandroidj over 9 yearsdid you add this to your parent element of your view holder?
-
Ivan Fork over 9 yearsYes, I've also tried "afterDescendants" but neither worked.
-
kandroidj over 9 yearspost your xml please
-
Ivan Fork over 9 yearsAdded xml and more code.
-
pskink over 9 years@IvanFork have you used RecyclerView.OnItemTouchListener ?
-
Ivan Fork over 9 years@pskink Looks like a RecyclerView.OnItemTouchListener is the way to go, an OnClickListener don't block its events. But it works differently than OnTouchListener and needs a little bit different implementation for swipes.
-
pskink over 9 years@IvanFork it works like ViewGroup's touch events: you have onInterceptTouchEvent and OnTouchEvent
-
anber over 9 yearsDid you found the solition? If yes please share it.
-
-
marienke about 9 yearsI wish I could up-vote this a million times! My scrolling is still a bit staggered, because of loading images... any advice on that would be great. :\
-
brnunes about 9 yearsThe solution is to load the image to the memory asynchronously in an AsyncTask, and then show it in the ImageView. Check this article developer.android.com/training/improving-layouts/…. This post also talks about async loading. In the example the images are downloaded from the internet, but it is even simpler if you are loading them locally: android-developers.blogspot.com/2010/07/…