88

My Question: How do I detect when a user moves their finger up/down vs left/right (and how do I know which direction of those groups their finger moved)?

My Situation: I want to change the brightness of my app when they move their finger up and down (up = brighter, down = darker), and I want to switch between activities and/or views based on their left/right swipe.

1

13 Answers 13

196

I wrote a simple class for this: it's well documented so I wont explain it here

public class OnSwipeListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // Grab two events located on the plane at e1=(x1, y1) and e2=(x2, y2) // Let e1 be the initial event // e2 can be located at 4 different positions, consider the following diagram // (Assume that lines are separated by 90 degrees.) // // // \ A / // \ / // D e1 B // / \ // / C \ // // So if (x2,y2) falls in region: // A => it's an UP swipe // B => it's a RIGHT swipe // C => it's a DOWN swipe // D => it's a LEFT swipe // float x1 = e1.getX(); float y1 = e1.getY(); float x2 = e2.getX(); float y2 = e2.getY(); Direction direction = getDirection(x1,y1,x2,y2); return onSwipe(direction); } /** Override this method. The Direction enum will tell you how the user swiped. */ public boolean onSwipe(Direction direction){ return false; } /** * Given two points in the plane p1=(x1, x2) and p2=(y1, y1), this method * returns the direction that an arrow pointing from p1 to p2 would have. * @param x1 the x position of the first point * @param y1 the y position of the first point * @param x2 the x position of the second point * @param y2 the y position of the second point * @return the direction */ public Direction getDirection(float x1, float y1, float x2, float y2){ double angle = getAngle(x1, y1, x2, y2); return Direction.fromAngle(angle); } /** * * Finds the angle between two points in the plane (x1,y1) and (x2, y2) * The angle is measured with 0/360 being the X-axis to the right, angles * increase counter clockwise. * * @param x1 the x position of the first point * @param y1 the y position of the first point * @param x2 the x position of the second point * @param y2 the y position of the second point * @return the angle between two points */ public double getAngle(float x1, float y1, float x2, float y2) { double rad = Math.atan2(y1-y2,x2-x1) + Math.PI; return (rad*180/Math.PI + 180)%360; } public enum Direction{ up, down, left, right; /** * Returns a direction given an angle. * Directions are defined as follows: * * Up: [45, 135] * Right: [0,45] and [315, 360] * Down: [225, 315] * Left: [135, 225] * * @param angle an angle from 0 to 360 - e * @return the direction of an angle */ public static Direction fromAngle(double angle){ if(inRange(angle, 45, 135)){ return Direction.up; } else if(inRange(angle, 0,45) || inRange(angle, 315, 360)){ return Direction.right; } else if(inRange(angle, 225, 315)){ return Direction.down; } else{ return Direction.left; } } /** * @param angle an angle * @param init the initial bound * @param end the final bound * @return returns true if the given angle is in the interval [init, end). */ private static boolean inRange(double angle, float init, float end){ return (angle >= init) && (angle < end); } } } 

To use simply extend the OnSwipeListener and override the onSwipe method

Sign up to request clarification or add additional context in comments.

13 Comments

Far the best implementation of the 4-way swipe detection I tried so far (in terms of detection accuracy). Thanks.
Can you please just suggest anything about using it inside a class that extends an activity?
Look for my answer for usage example
To force the override of onSwipe, mark it and the class as abstract.
This seems to be an elegant code snippet. I did integrate it into one of my projects, in which it applied to a webview class. However while sliding up/down works like a charm, sliding left/right is a pain in the neck - it only occasionally worked. I don't know it's because the way I did wasn't right, or my cell phone is not sensitivity (then how come it works fine for up/down) or something else? I'll try another phone and come back to update.
|
64

You simply have to extend SimpleOnGestureListener class,

Declare this in your class,

private static final int SWIPE_MIN_DISTANCE = 120; private static final int SWIPE_MAX_OFF_PATH = 250; private static final int SWIPE_THRESHOLD_VELOCITY = 200; 

As an example for horizontal swipe you can see the below code,

 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) { onLeftSwipe(); } // left to right swipe else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { onRightSwipe(); } } catch (Exception e) { } return false; } } 

You can do this similarly for vertical swipe purpose.

9 Comments

What do I used for those constants you indicated in your answer?
That represents the distance of swipe i.e the minimum distanc the user should swipe for invoking the purpose,same for others as the name suggests.
How can I use that inside the class that extends Activity?
@madprops This code which I wrote was used inside an activity ,MyGestureDetector is an inner class and the variables are the attributes for that Activity.
Why do you return false not true, haven't you handled the gesture as you want? Also why is the body inside a try/catch statement?
|
37

Fernandour answer is perfect, I am writing this answer about how to use it with Activity and Fragment as many people are looking for it.

public class MyActivity extends Activity implements View.OnTouchListener{ private RelativeLayout someLayout; //take any layout on which you want your gesture listener; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); gestureDetector=new GestureDetector(this,new OnSwipeListener(){ @Override public boolean onSwipe(Direction direction) { if (direction==Direction.up){ //do your stuff Log.d(TAG, "onSwipe: up"); } if (direction==Direction.down){ //do your stuff Log.d(TAG, "onSwipe: down"); } return true; } }); someLayout.setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { Log.d(TAG, "onTouch: "); gestureDetector.onTouchEvent(event); return true; } } 

Comments

23

A full Usage Example for fernandohur answer above:

If you want to apply OnSwipeListener to one of your views, so:
Wherever this view is - set a touch listener for that view, like this:

myview.setOnTouchListener(this); 

Now in your Activity's OnCreate or in your Custom view constructor do this:

// Global private GestureDetectorCompat detector; // In OnCreate or custome view constructor (which extends one of Android views) detector = new GestureDetectorCompat(context, onSwipeListener); 

Override in the same class the onTouch event, like this:

@Override public boolean onTouch(View view, MotionEvent motionEvent) { return detector.onTouchEvent(motionEvent); } 

And also have in the same class this listener object:

OnSwipeListener onSwipeListener = new OnSwipeListener() { @Override public boolean onSwipe(Direction direction) { // Possible implementation if(direction == Direction.left|| direction == Direction.right) { // Do something COOL like animation or whatever you want // Refer to your view if needed using a global reference return true; } else if(direction == Direction.up|| direction == Direction.down) { // Do something COOL like animation or whatever you want // Refer to your view if needed using a global reference return true; } return super.onSwipe(direction); } }; 

Comments

5

The best answer that i test it several times and its worked for me

float firstX_point, firstY_point; @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: firstX_point = event.getRawX(); firstY_point = event.getRawY(); break; case MotionEvent.ACTION_UP: float finalX = event.getRawX(); float finalY = event.getRawY(); int distanceX = (int) (finalX - firstX_point); int distanceY = (int) (finalY - firstY_point); if (Math.abs(distanceX) > Math.abs(distanceY)) { if ((firstX_point < finalX)) { Log.d("Test", "Left to Right swipe performed"); } else { Log.d("Test", "Right to Left swipe performed"); } }else{ if ((firstY_point < finalY)) { Log.d("Test", "Up to Down swipe performed"); } else { Log.d("Test", "Down to Up swipe performed"); } } break; } return true; } 

Comments

3

that is how I did it, easiest way

float initialX, initialY; @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: initialX = event.getX(); initialY = event.getY(); // Log.d(TAG, "Action was DOWN"); break; case MotionEvent.ACTION_MOVE: //Log.d(TAG, "Action was MOVE"); break; case MotionEvent.ACTION_UP: float finalX = event.getX(); float finalY = event.getY(); //Log.d(TAG, "Action was UP"); if (initialX < finalX) { // Log.d(TAG, "Left to Right swipe performed"); } if (initialX > finalX) { // Log.d(TAG, "Right to Left swipe performed"); } if (initialY < finalY) { // Log.d(TAG, "Up to Down swipe performed"); } if (initialY > finalY) { // Log.d(TAG, "Down to Up swipe performed"); } break; case MotionEvent.ACTION_CANCEL: //Log.d(TAG,"Action was CANCEL"); break; case MotionEvent.ACTION_OUTSIDE: // Log.d(TAG, "Movement occurred outside bounds of current screen element"); break; } return super.onTouchEvent(event); } 

Comments

2

You could override SimpleGestureListener and calculate the diff between start end current coordinates:

private class GestureListener extends SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (e2.getY() > e1.getY()) { // direction up }else { // direction down } if (e2.getX() > e1.getX()) { // direction right }else { // direction left } return true; } } 

1 Comment

This answer is not correct. Let e=(x,y) be the x,y coordinates of event e. Let e1 = (0,0) and e2 = (200, 1) In this simple example (the user swiped 200 pixels on the x axis and 1 pixel on the y axis). Your code would have interpreted this as a Y axis swipe, which won't make much sense for the user.
1

I solved this way:

viewPager.setOnTouchListener(new View.OnTouchListener() { float prevX = -1; @Override public boolean onTouch(View v, MotionEvent event) { if (prevX != -1) { if (event.getX() > prevX) { if (viewPager.getCurrentItem() == 0) { // Left to Right swipe } //Log.d("DEBUG", MotionEvent.ACTION_MOVE + ":" + event.getAction() + ":" + event.getActionMasked() + ":Left Swipe" + ":" + prevX + ":" + event.getX() + ":" + viewPager.getCurrentItem()); } else if (prevX > event.getX()) { // Right to left swipe //Log.d("DEBUG", MotionEvent.ACTION_MOVE + ":" + event.getAction() + ":" + event.getActionMasked() + ":Right Swipe" + ":" + prevX + ":" + event.getX() + ":" + viewPager.getCurrentItem()); } } if (event.getAction() == MotionEvent.ACTION_MOVE) { prevX = event.getX(); } else { prevX = -1; } return false; } }); 

Comments

0

I have an open source gesture library on bitbucket that does this. Within this library is a 'HGFling' class. This demonstrates how to detect the direction of a fling. You can download the library from: https://bitbucket.org/warwick/hacergestov3. It's open source.

Comments

0

Adding Kotlin implementation for @Fernandour answer. For Java look at @farhan patel answer I am adding this because I was having difficulty, hope it will save someone#s time.

class ClientFragment : Fragment(), View.OnTouchListener { private lateinit var gestureDetector: GestureDetector override fun onTouch(v: View?, event: MotionEvent?): Boolean { Log.d(TAG, "onTouch: "); gestureDetector.onTouchEvent(event); return true } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { ... gestureDetector = GestureDetector(activity, object : OnSwipeListener() { override fun onSwipe(direction: Direction): Boolean { when(direction){ Direction.up -> { Log.d(TAG, "onSwipe: up") sendCommand("UP") return true } Direction.down ->{ Log.d(TAG, "onSwipe: down") sendCommand("DOWN") return true } Direction.left -> { Log.d(TAG, "onSwipe: left") sendCommand("LEFT") return true } Direction.right ->{ Log.d(TAG, "onSwipe: right") sendCommand("RIGHT") return true } else -> { } } return true } }) dpadLayout.setOnTouchListener(this) 

Comments

0

the extended version of @Fernandour 's answer easy to implement direct with on touch listener no to do extra code… with on touch o'clock ,long press ,double click implementation….

 public class OnSwipeListener implements View.OnTouchListener{ public enum Direction{up,down,left,right;} private GestureDetector gestureDetector; private Context context; public OnSwipeListener(Context c) { this.context = c; gestureDetector = new GestureDetector(c, new GestureListener(c)); } public boolean onTouch(final View view, final MotionEvent motionEvent) { return gestureDetector.onTouchEvent(motionEvent); } public void onSwipeRight() { } public void onSwipeLeft() { } public void onSwipeUp() { } public void onSwipeDown() { } public void onClick() { } public void onDoubleClick() { } public void onLongClick() { } public double getAngle(float x1, float y1, float x2, float y2) { double rad = Math.atan2(y1-y2,x2-x1) + Math.PI; return (rad*180/Math.PI + 180)%360; } private final class GestureListener extends GestureDetector.SimpleOnGestureListener { Context context; public GestureListener(Context c) { this.context = c; } @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onSingleTapUp(MotionEvent e) { onClick(); return super.onSingleTapUp(e); } @Override public boolean onDoubleTap(MotionEvent e) { onDoubleClick(); return super.onDoubleTap(e); } @Override public void onLongPress(MotionEvent e) { onLongClick(); super.onLongPress(e); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { float x1 = e1.getX(); float y1 = e1.getY(); float x2 = e2.getX(); float y2 = e2.getY(); Direction direction = getDirection(x1,y1,x2,y2); return onSwipe(direction); } public Direction getDirection(float x1, float y1, float x2, float y2){ double angle = getAngle(x1, y1, x2, y2); return fromAngle(angle); } public Direction fromAngle(double angle){ if(inRange(angle, 45, 135)){ onSwipeUp(); return Direction.up; } else if(inRange(angle, 0,45) || inRange(angle, 315, 360)){ onSwipeRight(); return Direction.right; } // else if(inRange(angle, 225, 315)){ // //onSwipeDown(); // // } else if(inRange(angle,135, 225)){ onSwipeLeft(); return Direction.left; } else { return Direction.down; } } private boolean inRange(double angle, float init, float end){ return (angle >= init) && (angle < end); } public boolean onSwipe(Direction direction){ return false; } }; } 

Comments

0

Best Way and Simple Way to detect Swipe left, Right, Top, Bottom

  1. First Make One Java Class and implements View.OnTouchListener Add below code in this Class:
 public class OnSwipeTouchListener implements View.OnTouchListener { private final GestureDetector gestureDetector; Context context; OnSwipeTouchListener(Context ctx, View mainView) { gestureDetector = new GestureDetector(ctx, new GestureListener()); mainView.setOnTouchListener(this); context = ctx; } @Override public boolean onTouch(View v, MotionEvent event) { return gestureDetector.onTouchEvent(event); } public class GestureListener extends GestureDetector.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; } } void onSwipeRight() { Toast.makeText(context, "You Swipe Right", Toast.LENGTH_SHORT).show(); this.onSwipe.swipeRight(); } void onSwipeLeft() { Toast.makeText(context, "You Swipe Left", Toast.LENGTH_SHORT).show(); this.onSwipe.swipeLeft(); } void onSwipeTop() { Toast.makeText(context, "You Swipe Up", Toast.LENGTH_SHORT).show(); this.onSwipe.swipeTop(); } void onSwipeBottom() { Toast.makeText(context, "You Swipe Down", Toast.LENGTH_SHORT).show(); this.onSwipe.swipeBottom(); } interface onSwipeListener { void swipeRight(); void swipeTop(); void swipeBottom(); void swipeLeft(); } onSwipeListener onSwipe; } 

And In Your MainActivity Class Use This Code:

 public class MainActivity extends AppCompatActivity { OnSwipeTouchListener onSwipeTouchListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); onSwipeTouchListener = new OnSwipeTouchListener(this, findViewById(R.id.relativeLayout)); } } 

In activity_main.xml:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/relativeLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context=".MainActivity"> </RelativeLayout> 

Comments

0

The answers available are far too complex for such a simple problem. I suggest using Kotlin in another approach to it (code is as3, but you can get the idea):

var touchDistance:Number = Point.distance(_moveTouchPoint, _startTouchPoint); if (touchDistance >= SWIPE_MIN_DISTANCE) { var xDiff:Number = _moveTouchPoint.x - _startTouchPoint.x; var yDiff:Number = _moveTouchPoint.y - _startTouchPoint.y; var yGreater:Boolean = Math.abs(yDiff) >= Math.abs(xDiff); if (yGreater) { // direction is up or down changePlayerDirectionTo(yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN); } else { // direction is left or right changePlayerDirectionTo(xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT) } } 

In each case either x or y will be greater in absolute value, which can resolve to certain direction set. From then on you can rely on the coordinate sign to detect which direction exactly.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.