Search code examples
androidandroid-recyclerviewswipe

Swipe to reveal Action Button on list created using RecyclerView in Android


Main Activity of my App has a custom List and each list item should reveal an action button associated with it on a left swipe and hide that button when the same list item is swiped back again to right. Now I am trying to detect swipe events for each list item by creating Swipe detector class and then setting it as the touch listener for list item in the ItemHolder class.

However, because of the RecyclerView being scrollable, the swipe on the individual list items is going undetected. How do I detect swipe on individual list items?

MainActivity - onCreate()

 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_recycler);

    ArrayList<Subject> subjects = new ArrayList<>();

    subjects.add(new Subject(1, "Physics"));
    subjects.add(new Subject(2, "Chemistry"));
    subjects.add(new Subject(3, "Biology"));

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.subject_recycler_list_view);
    RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
    SubjectRecyclerListAdapter adapter = new SubjectRecyclerListAdapter(getApplicationContext(), R.layout.row_layout , subjects);

    recyclerView.setAdapter(adapter);
    recyclerView.setLayoutManager(layoutManager);
}

SwipeDetector

public class SwipeDetector implements View.OnTouchListener {
    static final int MIN_DISTANCE = 50;
    private float downX, downY, upX, upY;

    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                downX = event.getX();
                downY = event.getY();
                //   return true;
            }
            case MotionEvent.ACTION_UP: {
                upX = event.getX();
                upY = event.getY();

                float deltaX = downX - upX;
                float deltaY = downY - upY;

                // swipe horizontal?
                if ( Math.abs(deltaX) > MIN_DISTANCE){
                    // left or right
                    if ( deltaX < 0){
                        Log.i("Swipe Detector","LeftToRightSwipe!");
                        return true;
                    }
                    if ( deltaX > 0){
                        Log.i("Swipe Detector","RightToLeftSwipe!");
                        return true;
                    }
                } else{
                    Log.i("Swipe Detector","Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                }
            }
        }

        return false;
    }
}

itemHolder - Constructor

public SubjectHolder(Context context, View itemView) {
    super(itemView);

    this.subjectTextView = (TextView) itemView.findViewById(R.id.name);

    SwipeDetector swipeDetector = new SwipeDetector();
    itemView.setOnTouchListener(swipeDetector);
}

The swipe detection works for any other view except the list items of RecyclerView (or children of any other scrollable view). How do I detect the swipe on the list Items?


Solution

  • The problem is solved but I don't know if it is the right way to do it but it definitely works. Here is how it goes -

    Create an interface SwipeActions with two methods which will be used for callbacks on left and right swipe.

    SwipeActions

    public interface SwipeActions {
        public void onLeftSwipe(MotionEvent e);
        public void onRightSwipe(MotionEvent e);
    }
    

    Then mention of SwipeAction as one of the properties of SwipeDetector and then intialise it in the constructor.

    public class SwipeDetector implements View.OnTouchListener {
        static final int MIN_DISTANCE = 50;
        private float downX, downY, upX, upY;
        public SwipeActions swipeActions;
    
        public SwipeDetector(SwipeActions swipeActions) {
            this.swipeActions = swipeActions;
        }
    
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    downX = event.getX();
                    downY = event.getY();
                }
                case MotionEvent.ACTION_UP: {
                    upX = event.getX();
                    upY = event.getY();
    
                    float deltaX = downX - upX;
                    float deltaY = downY - upY;
    
                    if ( Math.abs(deltaX) > MIN_DISTANCE){
                        if ( deltaX < 0){
                            swipeActions.onRightSwipe(event);
                            return true;
                        }
                        if ( deltaX > 0){
                            swipeActions.onLeftSwipe(event);
                            return true;
                        }
                    } else{
                        Log.i("Swipe Detector","Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    }
                }
            }
    
            return false;
        }
    }
    

    The Swipe Actions take the MotionEvent object as parameter which would be used in the MainActivity to retrieve an object of the child view on which the swipe is performed. So modify the MainActivity to this -

    MainActivity - onCreate

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_recycler);
    
        ArrayList<Subject> subjects = new ArrayList<>();
    
        subjects.add(new Subject(1, "Physics"));
        subjects.add(new Subject(2, "Chemistry"));
        subjects.add(new Subject(3, "Biology"));
    
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.subject_recycler_list_view);
    
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
        SubjectRecyclerListAdapter adapter = new SubjectRecyclerListAdapter(getApplicationContext(), R.layout.row_layout , subjects);
        SwipeDetector swipeDetector = new SwipeDetector(new SwipeActions() {
            @Override
            public void onLeftSwipe(MotionEvent evnt) {
                View childView = recyclerView.findChildViewUnder(evnt.getX(), evnt.getY());
                if(childView != null) {
                    // Perform your action on childView (Swiped List Item)
                }
                else {
                    Log.i("Main Recycler Activity", "Child View is Null");
                }
            }
    
            @Override
            public void onRightSwipe(MotionEvent evnt) {
                View childView = recyclerView.findChildViewUnder(evnt.getX(), evnt.getY());
                if(childView != null) {
                    if(swipedViews.contains(childView)) {
                        // Perform your action on childView (Swiped List Item)
                    }
                }
                else {
                    Log.i("Main Recycler Activity", "Child View is Null");
                }
            }
        });
    
        recyclerView.setOnTouchListener(swipeDetector);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(layoutManager);
    }
    

    Also, I removed the TouchListener from the itemHolder

    itemHolder - Constructor

    public SubjectHolder(Context context, View itemView) {
        super(itemView);
    
        this.subjectTextView = (TextView) itemView.findViewById(R.id.name);
    }