Fling gesture detection on grid layout
Solution 1
Thanks to Code Shogun, whose code I adapted to my situation.
Let your activity implementOnClickListener
as usual:
public class SelectFilterActivity extends Activity implements OnClickListener {
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;
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* ... */
// Gesture detection
gestureDetector = new GestureDetector(this, new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
};
}
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) {
Toast.makeText(SelectFilterActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Toast.makeText(SelectFilterActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
// nothing
}
return false;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
}
}
Attach your gesture listener to all the views you add to the main layout;
// Do this for each view added to the grid
imageView.setOnClickListener(SelectFilterActivity.this);
imageView.setOnTouchListener(gestureListener);
Watch in awe as your overridden methods are hit, both the onClick(View v)
of the activity and the onFling
of the gesture listener.
public void onClick(View v) {
Filter f = (Filter) v.getTag();
FilterFullscreenActivity.show(this, input, f);
}
The post 'fling' dance is optional but encouraged.
Solution 2
One of the answers above mentions handling different pixel density but suggests computing the swipe parameters by hand. It is worth noting that you can actually obtain scaled, reasonable values from the system using ViewConfiguration
class:
final ViewConfiguration vc = ViewConfiguration.get(getContext());
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
// (there is also vc.getScaledMaximumFlingVelocity() one could check against)
I noticed that using these values causes the "feel" of fling to be more consistent between the application and rest of system.
Solution 3
I do it a little different, and wrote an extra detector class that implements the View.onTouchListener
onCreate
is simply add it to the lowest layout like this:
ActivitySwipeDetector activitySwipeDetector = new ActivitySwipeDetector(this);
lowestLayout = (RelativeLayout)this.findViewById(R.id.lowestLayout);
lowestLayout.setOnTouchListener(activitySwipeDetector);
where id.lowestLayout is the id.xxx for the view lowest in the layout hierarchy and lowestLayout is declared as a RelativeLayout
And then there is the actual activity swipe detector class:
public class ActivitySwipeDetector implements View.OnTouchListener {
static final String logTag = "ActivitySwipeDetector";
private Activity activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;
public ActivitySwipeDetector(Activity activity){
this.activity = activity;
}
public void onRightSwipe(){
Log.i(logTag, "RightToLeftSwipe!");
activity.doSomething();
}
public void onLeftSwipe(){
Log.i(logTag, "LeftToRightSwipe!");
activity.doSomething();
}
public void onDownSwipe(){
Log.i(logTag, "onTopToBottomSwipe!");
activity.doSomething();
}
public void onUpSwipe(){
Log.i(logTag, "onBottomToTopSwipe!");
activity.doSomething();
}
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) > Math.abs(deltaY))
{
if(Math.abs(deltaX) > MIN_DISTANCE){
// left or right
if(deltaX > 0) { this.onRightSwipe(); return true; }
if(deltaX < 0) { this.onLeftSwipe(); return true; }
}
else {
Log.i(logTag, "Horizontal Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
return false; // We don't consume the event
}
}
// swipe vertical?
else
{
if(Math.abs(deltaY) > MIN_DISTANCE){
// top or down
if(deltaY < 0) { this.onDownSwipe(); return true; }
if(deltaY > 0) { this.onUpSwipe(); return true; }
}
else {
Log.i(logTag, "Vertical Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
return false; // We don't consume the event
}
}
return true;
}
}
return false;
}
}
Works really good for me!
Solution 4
I slightly modified and repaired solution from Thomas Fankhauser
Whole system consists from two files, SwipeInterface and ActivitySwipeDetector
SwipeInterface.java
import android.view.View;
public interface SwipeInterface {
public void bottom2top(View v);
public void left2right(View v);
public void right2left(View v);
public void top2bottom(View v);
}
Detector
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class ActivitySwipeDetector implements View.OnTouchListener {
static final String logTag = "ActivitySwipeDetector";
private SwipeInterface activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;
public ActivitySwipeDetector(SwipeInterface activity){
this.activity = activity;
}
public void onRightToLeftSwipe(View v){
Log.i(logTag, "RightToLeftSwipe!");
activity.right2left(v);
}
public void onLeftToRightSwipe(View v){
Log.i(logTag, "LeftToRightSwipe!");
activity.left2right(v);
}
public void onTopToBottomSwipe(View v){
Log.i(logTag, "onTopToBottomSwipe!");
activity.top2bottom(v);
}
public void onBottomToTopSwipe(View v){
Log.i(logTag, "onBottomToTopSwipe!");
activity.bottom2top(v);
}
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) { this.onLeftToRightSwipe(v); return true; }
if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
}
else {
Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
}
// swipe vertical?
if(Math.abs(deltaY) > MIN_DISTANCE){
// top or down
if(deltaY < 0) { this.onTopToBottomSwipe(v); return true; }
if(deltaY > 0) { this.onBottomToTopSwipe(v); return true; }
}
else {
Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
v.performClick();
}
}
}
return false;
}
}
it is used like this:
ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);
LinearLayout swipe_layout = (LinearLayout) findViewById(R.id.swipe_layout);
swipe_layout.setOnTouchListener(swipe);
And in implementing Activity
you need to implement methods from SwipeInterface, and you can find out on which View the Swipe Event was called.
@Override
public void left2right(View v) {
switch(v.getId()){
case R.id.swipe_layout:
// do your stuff here
break;
}
}
Solution 5
The swipe gesture detector code above is very useful! You may however wish to make this solution density agnostic by using the following relative values (REL_SWIPE)
rather than the absolute values (SWIPE_)
DisplayMetrics dm = getResources().getDisplayMetrics();
int REL_SWIPE_MIN_DISTANCE = (int)(SWIPE_MIN_DISTANCE * dm.densityDpi / 160.0f);
int REL_SWIPE_MAX_OFF_PATH = (int)(SWIPE_MAX_OFF_PATH * dm.densityDpi / 160.0f);
int REL_SWIPE_THRESHOLD_VELOCITY = (int)(SWIPE_THRESHOLD_VELOCITY * dm.densityDpi / 160.0f);
![gav](https://i.stack.imgur.com/tgYaQ.jpg?s=256&g=1)
Comments
-
gav about 4 years
I want to get
fling
gesture detection working in my Android application.What I have is a
GridLayout
that contains 9ImageView
s. The source can be found here: Romain Guys's Grid Layout.That file I take is from Romain Guy's Photostream application and has only been slightly adapted.
For the simple click situation I need only set the
onClickListener
for eachImageView
I add to be the mainactivity
which implementsView.OnClickListener
. It seems infinitely more complicated to implement something that recognizes afling
. I presume this is because it may spanviews
?-
If my activity implements
OnGestureListener
I don't know how to set that as the gesture listener for theGrid
or theImage
views that I add.public class SelectFilterActivity extends Activity implements View.OnClickListener, OnGestureListener { ...
-
If my activity implements
OnTouchListener
then I have noonFling
method tooverride
(it has two events as parameters allowing me to determine if the fling was noteworthy).public class SelectFilterActivity extends Activity implements View.OnClickListener, OnTouchListener { ...
If I make a custom
View
, likeGestureImageView
that extendsImageView
I don't know how to tell the activity that afling
has occurred from the view. In any case, I tried this and the methods weren't called when I touched the screen.
I really just need a concrete example of this working across views. What, when and how should I attach this
listener
? I need to be able to detect single clicks also.// Gesture detection mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int dx = (int) (e2.getX() - e1.getX()); // don't accept the fling if it's too short // as it may conflict with a button push if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.absvelocityY)) { if (velocityX > 0) { moveRight(); } else { moveLeft(); } return true; } else { return false; } } });
Is it possible to lay a transparent view over the top of my screen to capture flings?
If I choose not to
inflate
my child image views from XML can I pass theGestureDetector
as a constructor parameter to a new subclass ofImageView
that I create?This is the very simple activity that I'm trying to get the
fling
detection to work for: SelectFilterActivity (Adapted from photostream).I've been looking at these sources:
Nothing has worked for me so far and I was hoping for some pointers.
-
Bishwash over 4 yearsHow to solve this problem? Please answer stackoverflow.com/questions/60464912/…
-
-
Cdsboy over 14 yearsThank you for this code! It was very helpful. However, I ran into one very very frustrating catch while trying to get gestures working. In my SimpleOnGestureListener, I have to override onDown for any of my gestures to register. It can just return true but i has to be defined. P.S: I don't know if its my api revision or my hardware, but i'm using 1.5 on a HTC Droid Eris.
-
Thane Anthem about 13 years+1 for bringing this up. Note that DensityMetrics.densityDpi was introduced in API 4. For backward compatibility with API 1, use DensityMetrics.density instead. This then changes the calculation to be just SWIPE_MIN_DISTANCE * dm.density.
-
lomza about 13 yearsI tried your code and it doesn't matter if I swipe or click (with my mouse, cause I work in emulator), I always get a Toast I defined in onClick method, so the emulator detects only clicks, without swipes. Why is it so?
-
Thomas Ahle almost 13 yearsI use
swipeMinDistance = vc.getScaledPagingTouchSlop()
andswipeMaxOffPath = vc.getScaledTouchSlop()
. -
Jono almost 13 yearsI tried this code and it did not work. still was unable to scroll at all when i apply a onClick listener to one of the child views inside my gallery view
-
JWL almost 13 yearsThis actually made it much easier for me to apply gesture functionality, and required "less" wiring :D Thanks @Thomas
-
Someone Somewhere over 12 yearsThis looks like a neat utility class - but I think your four on...swipe() methods should be interfaces
-
Marek Sebera over 12 yearsthese returns shouldn't be there (line "we dont consume the event"), isn't it? It disables vertical scrolling feature.
-
IgorGanapolsky over 12 yearsWhere did you get the number 160.0f?
-
IgorGanapolsky over 12 yearsIomza: did you try putting break statements and stepping through your code?
-
IgorGanapolsky over 12 yearsKudos for using an inner class! Very clean approach.
-
Piotr over 12 yearsIsn't keeping reference to activity causing memory leaks?
-
Admin over 12 yearsYes.Really It works well..nice code to implement for ontouch event in android
-
paiego over 12 yearsdeveloper.android.com/guide/practices/screens_support.html Density-independent pixel (dp) The conversion of dp units to screen pixels is simple: px = dp * (dpi / 160)
-
Sandy over 12 yearsI was looking all over for this. NO example of onFling() on the Internet has this, which will lead to poor UX. Thanks!
-
Marek Sebera over 12 yearsI slightly modified it again, see the
v.performClick();
, which is used to not consume event to OnClickListener, if set on same view -
Admin over 12 yearsWhat is this onClick function, it is giving me errors.. please help
-
Jon O about 12 years@JeffreyBlattman Could you elaborate and/or edit the post to fix them?
-
Jeffrey Blattman about 12 yearsspecifically, the onTouch() method. first, if the delta X is not big enough, it returns without checking the delta Y. the result is that is never detects the left-right swipes. second, it also shouldn't return true if it drops through finding no swipe. third, it shouldn't return true on action down. this prevents any other listener like onClick from working.
-
Jeffrey Blattman about 12 years@Piotr it's not a problem as long a the object holding the reference is the same scope as the activity itself. the problem occurs when you keep a reference to an activity in a place that has a larger scope than the activity ... like from a static member for example.
-
Mikey almost 12 yearshow do you get the value for this SWIPE_MAX_OFF_PATH?
-
almalkawi almost 12 yearsOne problem with ViewPager is that you have no control of distance and velocity parameters for the fling gesture.
-
paiego over 11 years160.0f is the comes from the 160 DPI which is standard density upon which DP (density independent pixels) is based. public static final int DENSITY_MEDIUM Added in API level 4 Standard quantized DPI for medium-density screens. Constant Value: 160 (0x000000a0)
-
Gaurav Arora over 11 yearsI want to implement swipe gesture via 2 fingers. Please help me out!
-
Chocolava over 11 yearsHi, I'm a total beginner so this question might be really obvious or trivial but please answer. The part where you have written, it is used as: ActivitySwipeDetector swipe = new ActivitySwipeDetector(this); This statement will be a part of MainActivity, correct? Then, "this" will be an activity of MainActivity. Whereas the constructor takes an instance of SwipeInterface. Kindly help me out here. Thanks a lot.
-
Marek Sebera over 11 years@Chocolava create new question, comment is not a fine place to ask like this.
-
Duc Tran over 11 years@MarekSebera this doesn't work with ScrollView & ListView? how to handle them?
-
Marek Sebera over 11 years@silentbang again, this is not place to ask such questions. please create new question thread.
-
bad_keypoints over 11 yearsi'm trying to use it in a camera app, for gestures related to camera operations. I'm not getting any registered events occuring. My outermost layout is RelativeLayout, in the XML of which I put its ID = relative_layout Now the three lines in onCreate() I put are: paste.org/59785
-
bad_keypoints over 11 yearsactually this is my full project setup. ge.tt/9P9t7sU/v/0 can you run and see if it's working for you?
-
Tushar over 11 years@ThaneAnthem I think you meant DisplayMetrics, not DensityMetrics. :P
-
Lo-Tan over 11 yearsI tried implementing this on a view that contains clickable elements. When a swipe starts over a clickable element (e.g. a list view which has onItemClick listener registered), then onTouchEvent is never invoked. Thus, the user cannot start a swipe over a clickable element, which is unfortunate for me and I am still trying to figure out how to work around this, as our clickable elements take up quite a bit of view space and we still want swipe support for the entire view. If a swipe doesn't start over a clickable element, then it works perfectly.
-
Phil over 11 years@Lo-Tan, this occurs because your clickable item is a child view, and is thus on top of the
SwipeInterceptorView
, so its click is handled first. You can fix this by implementing your own clicking mechanism by implementingonTouchListener
, or as a work-around you can listen for long clicks instead of clicks (seeView.setOnLongClickListener
). -
Lo-Tan over 11 yearsI am actually trying that at the very moment. Or possible cancelling the click event if they start a drag :) Thanks much.
-
superuser almost 11 yearsIf i want to have a fragment on top of my activity, would I do fragment.setOnClickListener(SelectFilterActivity.this); fragment.setOnTouchListener(gestureListener);
-
android developer over 10 yearssuppose i need to have the functionality of handling touches by myself too (for example for dragging) , what should i do?
-
WonderCsabo over 10 years
getScaledTouchSlop
gives me very little offset result, awkwardly. For example only 24 pixels on a 540 high screen, that's very hard to keep it in range with the finger. :S -
abdfahim over 10 yearscan anybody please tell me how to call the class. ActivitySwipeDetector swipe = new ActivitySwipeDetector(this); obviously is giving error, as no such constructor. Should I give ActivitySwipeDetector swipe = new ActivitySwipeDetector(this,null);
-
Stan over 10 yearsAnd what are the next steps? How to set that listener to a particular view? And what if this view is a part of a fragment?
-
Jibran Khan over 10 yearsGot it working for DoubleTap by following the implementation of SimpleGestureListener.
-
Alessandro Roaro over 10 yearsThis piece of code is very useful, but passing the activity as a parameter and then calling its methods isn't good design. A better choice would be setting the swipe callbacks as abstract methods and let the activity implement them.
-
linuxjava over 10 yearsMy setOnClickListener did not work. So I had to override onSingleTapConfirmed and it worked as expected.
-
Anton Kashpor about 10 years@AbdullahFahim ActivitySwipeDetector(this, YourActivity.this);
-
Pararth almost 10 yearsthis is a classic example with the basic swiping options, thank you for the elementary! dr watson!
-
user280109 over 9 yearsthe layout i am using is a RelativeLayout, can i just replace the text "LinearLayout" with "RelativeLayout"? Also when i try and implement your code with the following : "ActivitySwipeDetector swipe = new ActivitySwipeDetector(this)" I get the error "The Constructor ActivitySwipeDetector(MainActivity) is undefined" anyone know how to fix this?
-
qix over 9 yearsI find that a
MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()
is more comfortable for a thumb swipe naturally traveling in a slight arc. -
Ralphilius about 9 yearsUsing this code but it's not working with my child fragment. Would be great if you guys can take a look at it stackoverflow.com/questions/29848754/…
-
Edward Falk almost 9 yearsJust one note: if the activity has widgets that will be accepting input themselves, swipe gestures that start on such widgets will not work. See stackoverflow.com/questions/31504065/…
-
Edward Falk almost 9 yearsAlso, see ViewConfiguration which provides a number of standard values, adjusted for pixel density.
-
Edward Falk almost 9 yearsOne solution is to attach the swipe detector to every view in your app. Another is to implement onInterceptTouchEvent in your SwipeInterceptorView.
-
Erum almost 9 years@Sarvesh can some one pls tell me how to enable siwpe left or right in button while button is at viewpager position's 0 and i m unable to do both siwping on button and page swipe ?
-
Elia12345 almost 9 yearsThank you for a really good implementation. Additionally I would suggest to check
absDeltaY > minSwipeDelta
,absVelocityY > minSwipeVelocity
,absVelocityY < maxSwipeVelocity
only in case ifminSwipeDelta
!=getScaledTouchSlop
,minSwipeVelocity
!=getScaledMinimumFlingVelocity
,maxSwipeVelocity
!=getScaledMaximumFlingVelocity
, i.e. to check only if these so-called “default” (I mean getScaledTouchSlop, getScaledMinimumFlingVelocity, getScaledMaximumFlingVelocity) values are scaled or changed according to your own wish. -
Elia12345 almost 9 yearsThe point is that according to the source code, the mentioned "default" values are already checked by GestureDetector, and OnFling is triggered only if they are confirmed (by the way the triggering takes place only in case of
ACTION_UP
, notACTION_MOVE
orACTION_POINTER_UP
, i.e. only as a result of the fully realized gesture). (I have not checked other API versions, so comments are appreciated). -
Anthony almost 9 yearsViewPager is not used in the gallery.
-
ERJAN almost 9 yearsstill, nobody explained what this onclick() doing and what is inside it
-
JoxTraex over 4 yearsYou should probably credit Code Shogun, as I don't see a reference of credit link in your post. Where did this come from?
-
SkorpEN about 4 yearsIn my case MotionEvent.ACTION_CANCEL same as MotionEvent.ACTION_UP make it work as expected.