Fling gesture detection on grid layout


Question

I want to get fling gesture detection working in my Android application.

What I have is a GridLayout that contains 9 ImageViews. 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 each ImageView I add to be the main activity which implements View.OnClickListener. It seems infinitely more complicated to implement something that recognizes a fling. I presume this is because it may span views?

  • If my activity implements OnGestureListener I don't know how to set that as the gesture listener for the Grid or the Image views that I add.

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • If my activity implements OnTouchListener then I have no onFling method to override (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, like GestureImageView that extends ImageView I don't know how to tell the activity that a fling 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 the GestureDetector as a constructor parameter to a new subclass of ImageView 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.

1
1070
12/28/2015 8:49:10 AM

Accepted Answer

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.

803
7/12/2016 6:21:18 AM

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.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon