Swipe and OnClick events in RecyclerView

24,393

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
}
Share:
24,393

Related videos on Youtube

Ivan Fork
Author by

Ivan Fork

Updated on January 11, 2020

Comments

  • Ivan Fork
    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
      kandroidj over 9 years
      try adding to your xml outter ViewGroup for row items: android:descendantFocusability="blocksDescendants"
    • Ivan Fork
      Ivan Fork over 9 years
      @inner_class7 It didn't work.
    • kandroidj
      kandroidj over 9 years
      did you add this to your parent element of your view holder?
    • Ivan Fork
      Ivan Fork over 9 years
      Yes, I've also tried "afterDescendants" but neither worked.
    • kandroidj
      kandroidj over 9 years
      post your xml please
    • Ivan Fork
      Ivan Fork over 9 years
      Added xml and more code.
    • pskink
      pskink over 9 years
      @IvanFork have you used RecyclerView.OnItemTouchListener ?
    • Ivan Fork
      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
      pskink over 9 years
      @IvanFork it works like ViewGroup's touch events: you have onInterceptTouchEvent and OnTouchEvent
    • anber
      anber over 9 years
      Did you found the solition? If yes please share it.
  • marienke
    marienke about 9 years
    I 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
    brnunes about 9 years
    The 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/…