Wednesday, May 11, 2011

Adding a fling gesture listener to a view

Here's an example of a simple fling gesture listener, that you can add to almost any view, like ImageView to browse photos and images by flicking, or even to layout to change user interface on fling.

First, lets' start with the most simplest version:

public class MyActivity extends Activity {
  private void onCreate() {
     // Set your layout
     final ImageView imageView = (ImageView) findViewById(R.id.image_view);
     imageView.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(final View view, final MotionEvent event) {
           return gdt.onTouchEvent(event);
        }
     });
  }

  private final GestureDetector gdt = new GestureDetector(new GestureListener());
 
 
  private class GestureListener extends SimpleOnGestureListener {

     private final int SWIPE_MIN_DISTANCE = 120;
     private final int SWIPE_THRESHOLD_VELOCITY = 200;
 
     @Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
           // Right to left, your code here
           return true;
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) >  SWIPE_THRESHOLD_VELOCITY) {
           // Left to right, your code here
           return true;
        }
        if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           // Bottom to top, your code here
           return true;
        } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           // Top to bottom, your code here
           return true;
        }
        return false;
     }
  }
}



So here's the example of an activity, that somewhere on it's layout has an ImageView, that we want user to be able to fling.
What we basically do, is recording when and where user has touched the view, and where and when released, so we can calculate how fast and at what distance has user flinged. That's what SWIPE_THRESHOLD_VELOCITY and SWIPE_MIN_DISTANCE fields are for. You can set your own values, of course. The ones I gave are the optimal IMO.

This example is good, but what if we want to be able to detect flings on other views in other activities? Copying the code for every activity would be annoying and too difficult to support. Not even speaking about the ugly way we need to run some code on a specific fling. Here's a version of a "really easy to use" listener, that can be used anywhere without much trouble:

OnFlingGestureListener.java


public abstract class OnFlingGestureListener implements OnTouchListener {

  private final GestureDetector gdt = new GestureDetector(new GestureListener());

  @Override
  public boolean onTouch(final View v, final MotionEvent event) {
     return gdt.onTouchEvent(event);
  }

  private final class GestureListener extends SimpleOnGestureListener {

     private static final int SWIPE_MIN_DISTANCE = 60;
     private static final int SWIPE_THRESHOLD_VELOCITY = 100;

     @Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
           onRightToLeft();
           return true;
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
           onLeftToRight();
           return true;
        }
        if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           onBottomToTop();
           return true;
        } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           onTopToBottom();
           return true;
        }
        return false;
     }
  }

  public abstract void onRightToLeft();

  public abstract void onLeftToRight();

  public abstract void onBottomToTop();

  public abstract void onTopToBottom();

}


And previous example with the new listener:


public class MyActivity extends Activity {

  private void onCreate() {
     // Set your layout
     final ImageView imageView = (ImageView) findViewById(R.id.image_view);
     imageView.setOnTouchListener(new OnFlingGestureListener() {

        @Override
        public void onTopToBottom() {
           //Your code here
        }

        @Override
        public void onRightToLeft() {
           //Your code here
        }

        @Override
        public void onLeftToRight() {
           //Your code here
        }

        @Override
        public void onBottomToTop() {
           //Your code here
        }
     });
  }
}


As easy as that.

P.S. Criticism and comments are welcomed.

11 comments:

  1. Very nice worked out. However one question. Usually you want the code to affect you content. However your public abstract class (anonymous inner class) cannot make use of earlier defined variables.

    I wanted to use the following

    @Override
    public void onRightToLeft() {
    //next page

    nmr++;

    helloTxt.setText(Html.fromHtml(readTxt(nmr)));

    //Your code here
    }


    However I get the error "cannot rever to non-final variable helloTxt inside an inner class difined in a different method"


    the same for the variable "nmr"

    What can I do?

    Greetz,

    Bart

    ReplyDelete
  2. this code is succefully run?????
    please check it

    ReplyDelete
  3. There is a bug in the OnFlingGestureListener class :

    public boolean onTouch(final View v, final MotionEvent event) {
    return gdt.onTouchEvent(event);
    }

    should be :

    public boolean onTouch(final View v, final MotionEvent event) {
    gdt.onTouchEvent(event);
    return true;
    }

    ReplyDelete
  4. That fling listener class is pretty smart, thanks.

    ReplyDelete
  5. Thank you for this little tuto. Well explained, very usefull. cheers from France !

    ReplyDelete
  6. I am having keypad type layout with buttons in linearLayout my OnTouchListener are not working on button whereas they work well on textViews

    ReplyDelete