Search code examples
androidswipeontouchlistenerandroid-recyclerview

Swipe and OnClick events in RecyclerView


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

Solution

  • 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 Buttons. 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
    }