Android Swipe on List

30,983

Solution 1

I had the same problem and I didn't find my answer here.

I wanted to detect a swipe action in ListView item and mark it as swiped, while continue to support OnItemClick and OnItemLongClick.

Here is me solution:

1st The SwipeDetector class:

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class SwipeDetector implements View.OnTouchListener {

    public static enum Action {
        LR, // Left to Right
        RL, // Right to Left
        TB, // Top to bottom
        BT, // Bottom to Top
        None // when no action was detected
    }

    private static final String logTag = "SwipeDetector";
    private static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;
    private Action mSwipeDetected = Action.None;

    public boolean swipeDetected() {
        return mSwipeDetected != Action.None;
    }

    public Action getAction() {
        return mSwipeDetected;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = event.getX();
            downY = event.getY();
            mSwipeDetected = Action.None;
            return false; // allow other events like Click to be processed
        case MotionEvent.ACTION_UP:
            upX = event.getX();
            upY = event.getY();

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

            // horizontal swipe detection
            if (Math.abs(deltaX) > MIN_DISTANCE) {
                // left or right
                if (deltaX < 0) {
                    Log.i(logTag, "Swipe Left to Right");
                    mSwipeDetected = Action.LR;
                    return false;
                }
                if (deltaX > 0) {
                    Log.i(logTag, "Swipe Right to Left");
                    mSwipeDetected = Action.RL;
                    return false;
                }
            } else if (Math.abs(deltaY) > MIN_DISTANCE) { // vertical swipe
                                                            // detection
                // top or down
                if (deltaY < 0) {
                    Log.i(logTag, "Swipe Top to Bottom");
                    mSwipeDetected = Action.TB;
                    return false;
                }
                if (deltaY > 0) {
                    Log.i(logTag, "Swipe Bottom to Top");
                    mSwipeDetected = Action.BT;
                    return false;
                }
            }
            return false;
        }
        return false;
    }
}

2nd I use the swipe detector class in the list view:

    final ListView lv = getListView();
    final SwipeDetector swipeDetector = new SwipeDetector();
    lv.setOnTouchListener(swipeDetector);
    lv.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (swipeDetector.swipeDetected()){
                    // do the onSwipe action 
                } else {
                    // do the onItemClick action
                }
            }
    });
    lv.setOnItemLongClickListener(new OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view,int position, long id) {
            if (swipeDetector.swipeDetected()){
                // do the onSwipe action 
            } else {
                // do the onItemLongClick action
            }
        }
    });

This way I can support 3 actions - swipe, click, long click and I can use the ListView item info.

ADDED LATER:

Since ListView catches a scrolling action, it is sometimes hard to swipe. To fix it, I made the following change to SwipeDetector.onTouch:

public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            mSwipeDetected = Action.None;
            return false; // allow other events like Click to be processed
        }
        case MotionEvent.ACTION_MOVE: {
            upX = event.getX();
            upY = event.getY();

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

            // horizontal swipe detection
            if (Math.abs(deltaX) > HORIZONTAL_MIN_DISTANCE) {
                // left or right
                if (deltaX < 0) {
                    Log.i(logTag, "Swipe Left to Right");
                    mSwipeDetected = Action.LR;
                    return true;
                }
                if (deltaX > 0) {
                    Log.i(logTag, "Swipe Right to Left");
                    mSwipeDetected = Action.RL;
                    return true;
                }
            } else 

            // vertical swipe detection
            if (Math.abs(deltaY) > VERTICAL_MIN_DISTANCE) {
                // top or down
                if (deltaY < 0) {
                    Log.i(logTag, "Swipe Top to Bottom");
                    mSwipeDetected = Action.TB;
                    return false;
                }
                if (deltaY > 0) {
                    Log.i(logTag, "Swipe Bottom to Top");
                    mSwipeDetected = Action.BT;
                    return false;
                }
            } 
            return true;
        }
    }
    return false;
}

Solution 2

Here is a very simplified version using the two listeners (onTouch for swipe detection, and onClickIem for item click detection) using the isSwipe flag to stop the onClickItemListener until its confirmed that is was not a swipe

Detecting the click
taking into consideration that it is not a swipe first

        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
            {
                if(!isSwipe)
                {
                    adapter.increase(arg2);
                    adapter.notifyDataSetChanged();
                }
            }
        });

Detecting the swipe

        listView.setOnTouchListener(new OnTouchListener() {
        private int action_down_x = 0;
            private int action_up_x = 0;
            private int difference = 0;
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        action_down_x = (int) event.getX();
                        isSwipe=false;  //until now
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if(!isSwipe)
                        {
                            action_up_x = (int) event.getX();
                            difference = action_down_x - action_up_x;
                            if(Math.abs(difference)>50)
                            {
                                Log.d("action","action down x: "+action_down_x);
                                Log.d("action","action up x: "+action_up_x);
                                Log.d("action","difference: "+difference);
                                //swipe left or right
                                if(difference>0){
                                    //swipe left
                                    Log.d("action","swipe left");
                                    adapter.decrease(selectedItem);
                                    adapter.notifyDataSetChanged();
                                }
                                else{
                                    //swipe right
                                    Log.d("action","swipe right");
                                }
                                isSwipe=true;
                            }
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.d("action", "ACTION_UP - ");
                        action_down_x = 0;
                        action_up_x = 0;
                        difference = 0;
                        break;
                }
                return false;   //to allow the clicklistener to work after
            }
        })

Solution 3

here's a snippet I use for detecting swipes. You could then use a viewflipper to change the view.

  @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (gestureDetector.onTouchEvent(event)) {
                return true;
            } else {
                return false;
            }
        }

        private static final int SWIPE_MIN_DISTANCE = 30;
        private static final int SWIPE_MAX_OFF_PATH = 250;
        private static final int SWIPE_THRESHOLD_VELOCITY = 200;
        class MyGestureDetector extends SimpleOnGestureListener {
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                    float velocityY) {
                try {
                    if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
                        return false;
                    // right to left swipe
                    if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
                            && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                        leftFling();
                    } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
                            && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                        rightFling();
                    }
                } catch (Exception e) {
                    // nothing
                }
                return false;
            }

        }
Share:
30,983

Related videos on Youtube

JPM
Author by

JPM

Java Programmer for 6 years and seasoned Android developer, some C#, slightly new Windows Mobile developer. Member of the Bluetooth SIG Naming and Version Committee Musically inclined, play guitar and singer/songwriter, band investor among many other titles in the music biz. I am nerdier than 95% of all people. Are you a nerd? Click here to take the Nerd Test, get nerdy images and jokes, and talk on the nerd forum! http://www.nerdtests.com/images/ft/nq/a287f5ee0b.gif

Updated on January 25, 2020

Comments

  • JPM
    JPM over 4 years

    Does anyone have a simple example of a ListActivity displaying Textviews in a column and when you swipe left to right you see that row in a new view? This would be to say edit the data for that row or show more detailed info on that row. Please do not reference code shogun or other sites as I have googled and have not seen this answered.

    • JPM
      JPM over 13 years
      I already am using contextmenu to change font sizes and for a longpress for other activities. I want the user to be able to swipe left to right to open the full data screen for what row they swiped on.
    • JPM
      JPM about 11 years
      I guess fragments solves this problem now.
  • JPM
    JPM about 13 years
    Was looking more for a prrof example. I have tried viewflippers but always something weird with the list size when you do that. I like your code though its pretty clean. I will have to use it in my future attempts, thanks
  • prometheuspk
    prometheuspk about 12 years
    Why is is that if you had returned true other events like Click would not be processed?
  • AndroidDev
    AndroidDev almost 12 years
    @Pinhassi what will be the value for HORIZONTAL_MIN_DISTANCE and VERTICAL_MIN_DISTANCE
  • Asaf Pinhassi
    Asaf Pinhassi almost 12 years
    HORIZONTAL_MIN_DISTANCE and VERTICAL_MIN_DISTANCE are the minimum distances that the user's finger should move so that the action would be considered as a swipe. I set it to: HORIZONTAL_MIN_DISTANCE = 40; VERTICAL_MIN_DISTANCE = 80;
  • Mika
    Mika about 11 years
    Fantastic way around the problem of getting the id of the row inside the touch event. You just find out if the user is swiping while clicking a row.
  • Nikhil Agrawal
    Nikhil Agrawal almost 11 years
    @Pinhassi Excellent sir +1 for you. Sir can I perform animation while swipping.
  • onder
    onder almost 11 years
    dosent handle top to bottom swipe in OnItemClick or setOnItemLongClickListener. Why can this be happening? Regards
  • Si8
    Si8 over 10 years
    That's a Great tutorial! How would I allow the user to SWIPE and give the DELETE option. So rather than deleting it, they can swipe and it gives a button to press to delete?
  • Munazza
    Munazza over 10 years
    Hey I used your solution to avoid interference between swipe and item click, I need just left to right and right to left swipe. But now I can not scroll list upward and downward? How could I do that? To maintain scroll, click and left right swipe?
  • ASP
    ASP over 10 years
    @Pinhassi great one!!
  • derjohng
    derjohng about 10 years
    @Pinhassi Awesome solution!!
  • Oliver Dixon
    Oliver Dixon about 10 years
    Correct me if I'm wrong but; should you be using DP values for HORIZONTAL_MIN_DISTANCE & VERTICAL_MIN_DISTANCE. Kind of future proofs this class. (For the ultra high res phones and tables)
  • Gofilord
    Gofilord over 9 years
    This is pretty great man. The only thing is it's not that responsive. I need to first hold my finger long enough on the item, and only then I can swipe. Also, most of the times it doesn't even register my swipe. Is there a better solution?
  • MirroredFate
    MirroredFate over 8 years
    I'm not if something changed, but I cannot get the click events to trigger after the swipe.
  • Mohammad Rajob
    Mohammad Rajob about 8 years
    I am using it in fragment, but now working for me, please help.. My listview is custom listview. Thx