Nitesh Kumar
Nitesh Kumar

Reputation: 5440

SwipeListener on ListView and ClickListener on ListView's items

This is my old question. However this time I have provided my code as well.

I have a ListView with different types of rows. The row may contain text, image, video or something else. If I click on ImageView (inside the row) I will go to another activity to show the image in full screen, If I click on Video (inside the row) I will go to another activity to play he video.

I have implemented right to left swipe listener on my ListView. If I start the ListView swipe from an empty space in ListView, the swipe works (first and second row in below image). However if I start the ListView swipe from the ListView row's item, then the swipe doesn't work (third and fourth row in below image). If I remove the click events from ImageView and Video then the swipe works even if I start the swipe from the ListView row's item i.e. in this case the swipe works on whole ListView, no matter on which row I do the swipe.

enter image description here

How can I get rid on this problem? I think this can be achieved if I disable all the click events on the all the items inside ListView. How can do so? Is there any other way?

I want both swipe on ListView and click on ListView's item.

SwipeDetector.java - For detecting swipes on the ListView

public class SwipeDetector implements View.OnTouchListener {

private SwipeListener swipeListener;
private ListView mListView;
private int hundred;
private boolean motionInterceptDisallowed = false;

public static enum Action {
    LR, // Left to right
    RL, // Right to left
    TB, // Top to bottom
    BT, // Bottom to top
    None // Action not found
}

private static final int HORIZONTAL_MIN_DISTANCE = 30; // The minimum
// distance for
// horizontal swipe
private static final int VERTICAL_MIN_DISTANCE = 80; // The minimum distance
// for vertical
// swipe
private float downX, downY, upX, upY; // Coordinates
private Action mSwipeDetected = Action.None; // Last action

public SwipeDetector(Context context, ListView listView) {
    hundred = (int) context.getResources().getDimension(R.dimen.hundred);
    mListView = listView;
}

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

public Action getAction() {
    return mSwipeDetected;
}

/**
 * Swipe detection
 */@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_MOVE:
        {
            upX = event.getX();
            upY = event.getY();

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

            float absX = Math.abs(deltaX);
            float absY = Math.abs(deltaY);

            if((absX >= (3 * absY)) && absX > HORIZONTAL_MIN_DISTANCE && mListView != null && !motionInterceptDisallowed) {
                mListView.requestDisallowInterceptTouchEvent(true);
                motionInterceptDisallowed = true;
            }

            if((absX >= (3 * absY)) && absX <= hundred) {
                if (deltaX > 0) {
                    mSwipeDetected = Action.RL;
                    swipeListener.onSwipe(MotionEvent.ACTION_MOVE, Action.RL, absX);
                }
            }


            return false;
        }
        case MotionEvent.ACTION_UP:
            swipeListener.onSwipe(MotionEvent.ACTION_UP, Action.BT, 0);
            if (mListView != null) {
                mListView.requestDisallowInterceptTouchEvent(false);
                motionInterceptDisallowed = false;
            }
            return false;
        case MotionEvent.ACTION_CANCEL:
            return true;
    }
    return false;
}

/**
 * Set chat send listener
 * @param listener
 */
public void setSwipeListener(SwipeListener listener) {
    swipeListener = listener;
}

public interface SwipeListener {
    void onSwipe(int event, Action action, float x);
}

}

This is how I am setting SwipeDetector on my ListView

final SwipeDetector swipeDetector = new SwipeDetector(SwipeActivity.this, mListView);
    swipeDetector.setSwipeListener(mSwipeListener);
    mListView.setOnTouchListener(swipeDetector);

My Activity implements SwipeDetector.SwipeListener

Below is overridden onSwipe() method

@Override
public void onSwipe(int event, SwipeDetector.Action action, float x) {
    switch (event) {
            case MotionEvent.ACTION_MOVE:
                System.out.println("list move");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("list up");
                break;
        }
}

In my adapter I am setting onClickListener() on my TextView, ImageView and VideoView

holder.mTextView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            System.out.println("item textview click");
        }
    });

holder.mImageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            System.out.println("item imageview click");
        }
    });

holder.mVideoView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            System.out.println("item videoview click");
        }
    });

When I start the swipe from the empty area of ListView, the ListView's swipe listener captures it. However, when I start the swipe from the TextView/ImageView/VideoView then that View's onClickListener is fired instead of parent's (ListView's) onTouchListener().

How can I implement the same? I want that when I click on ListView's item then that item's onClick should get fired and when I swipe on the ListView's item then ListView's onSwipe should get fired.

Upvotes: 4

Views: 3585

Answers (2)

Hardik Maru
Hardik Maru

Reputation: 691

I know this is an old question but still for future search here is a better solution:

Try to use RecyclerView instead of ListView and add OnItemTouchListener of recyclerview's item.

recyclerView.addOnItemTouchListener(
            new RecyclerItemClickListener(
                    this,
                    new RecyclerItemClickListener.OnItemClickListener() {
                        @Override
                        public void onItemClick(View view, int position) {
                            // Your code here for item on click event...
                        }
                    },
                    runnableSwipeLeftToRight,
                    runnableSwipeRightToLeft)
    );

Here are two methods which will execute on appropriate swipes:

Runnable runnableSwipeRightToLeft = new Runnable() {
    public void run() {
        // Your Code here...
    }
};


Runnable runnableSwipeLeftToRight = new Runnable() {
    public void run() {
        // Your code here...
    }
};

And here is class code for touch and swipe detection:

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener, RecyclerView.OnLongClickListener {

private OnItemClickListener mListener;
GestureDetector gestureDetector;
Runnable runnableSwipeLeftToRight, runnableSwipeRightToLeft;

private static final String TAG = "RecyclerItemClickListen";
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 200;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;

@Override
public boolean onLongClick(View view) {
    return false;
}

public interface OnItemClickListener {
    public void onItemClick(View view, int position);
}

GestureDetector mGestureDetector;

public RecyclerItemClickListener(Context context, OnItemClickListener listener, Runnable runnableSwipeLeftToRight, Runnable runnableSwipeRightToLeft) {
    this.mListener = listener;
    this.runnableSwipeLeftToRight = runnableSwipeLeftToRight;
    this.runnableSwipeRightToLeft = runnableSwipeRightToLeft;

    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }
    });

    gestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent motionEvent) { }

        @Override
        public boolean onSingleTapUp(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { return false; }

        @Override
        public void onLongPress(MotionEvent motionEvent) { }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float v1) {
            try {
                float diffAbs = Math.abs(e1.getY() - e2.getY());
                float diff = e1.getX() - e2.getX();

                if (diffAbs > SWIPE_MAX_OFF_PATH) {
                    return false;
                }

                // Left swipe
                if (diff > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    onLeftSwipe();

                    // Right swipe
                } else if (-diff > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    onRightSwipe();
                }
            } catch (Exception e) {
                Log.e("YourActivity", "Error on gestures");
            }
            return false;
        }
    });
}

private void onLeftSwipe() {
    Log.d(TAG, "onLeftSwipe: ");
    runnableSwipeRightToLeft.run();
}

private void onRightSwipe() {
    Log.d(TAG, "onRightSwipe:");
    runnableSwipeLeftToRight.run();
}

@Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
    View childView = view.findChildViewUnder(e.getX(), e.getY());
    if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
        mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
    }
    return gestureDetector.onTouchEvent(e);
}

@Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}

}

Hope this will help you.

Upvotes: 0

Rajan Kali
Rajan Kali

Reputation: 12953

Thats Not Possible with both onClickListener for listitem and SwipeListener for List,because it gets Ambiguity between which View to Consider on touch

You Use OnSwipeTouchListener which implements onTouchListener

OnSwipeTouchListener.java

import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class OnSwipeTouchListener implements OnTouchListener {

    private final GestureDetector gestureDetector;

    public OnSwipeTouchListener (Context ctx){
        gestureDetector = new GestureDetector(ctx, new GestureListener());
    }

    private final class GestureListener extends SimpleOnGestureListener {

        private static final int SWIPE_THRESHOLD = 100;
        private static final int SWIPE_VELOCITY_THRESHOLD = 100;

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            boolean result = false;
            try {
                float diffY = e2.getY() - e1.getY();
                float diffX = e2.getX() - e1.getX();
                if (Math.abs(diffX) > Math.abs(diffY)) {
                    if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                        if (diffX > 0) {
                            onSwipeRight();
                        } else {
                            onSwipeLeft();
                        }
                    }
                    result = true;
                } 
                else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
                        if (diffY > 0) {
                            onSwipeBottom();
                        } else {
                            onSwipeTop();
                        }
                    }
                    result = true;

            } catch (Exception exception) {
                exception.printStackTrace();
            }
            return result;
        }
    }

    public void onSwipeRight() {
    }

    public void onSwipeLeft() {
    }

    public void onSwipeTop() {
    }

    public void onSwipeBottom() {
    }
}

And use OnSwipeTouchListener on listitem

  listitem.setOnTouchListener(new OnSwipeTouchListener() {

        public void onSwipeLeft() {
            //stuff to do list view left swipe 
            Toast.makeText(MyActivity.this, "left", Toast.LENGTH_SHORT).show();
        }


        public boolean onTouch(View v, MotionEvent event) {
            //stuff to do on list item click
            return gestureDetector.onTouchEvent(event);
        }
    });

}); //to get click events 

Upvotes: 1

Related Questions