How to Add a Gesture Detector to a View in Android

How to add a gesture detector to a view in Android

This example shows how to add a gesture detector to a view. The layout is just a single View inside of an Activity. You can use the same method to add a gesture detector to any type of view.

Sample Image

We will add the gesture detector to the green View.

MainActivity.java

The basic idea is to add an OnTouchListener to the view. Normally we would get all the raw touch data here (like ACTION_DOWN, ACTION_MOVE, ACTION_UP, etc.), but instead of handling it ourselves, we will forward it on to a gesture detector to do the interpretation of the touch data.

We are using a SimpleOnGestureListener. The nice thing about this gesture detector is that we only need to override the gestures that we need. In the example here I included a lot of them. You can remove the ones you don't need. (You should always return true in onDown(), though. Returning true means that we are handling the event. Returning false will make the system stop giving us any more touch events.)

public class MainActivity extends AppCompatActivity {

private GestureDetector mDetector;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// this is the view we will add the gesture detector to
View myView = findViewById(R.id.my_view);

// get the gesture detector
mDetector = new GestureDetector(this, new MyGestureListener());

// Add a touch listener to the view
// The touch listener passes all its events on to the gesture detector
myView.setOnTouchListener(touchListener);
}

// This touch listener passes everything on to the gesture detector.
// That saves us the trouble of interpreting the raw touch events
// ourselves.
View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// pass the events to the gesture detector
// a return value of true means the detector is handling it
// a return value of false means the detector didn't
// recognize the event
return mDetector.onTouchEvent(event);

}
};

// In the SimpleOnGestureListener subclass you should override
// onDown and any other gesture that you want to detect.
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {

@Override
public boolean onDown(MotionEvent event) {
Log.d("TAG","onDown: ");

// don't return false here or else none of the other
// gestures will work
return true;
}

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.i("TAG", "onSingleTapConfirmed: ");
return true;
}

@Override
public void onLongPress(MotionEvent e) {
Log.i("TAG", "onLongPress: ");
}

@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i("TAG", "onDoubleTap: ");
return true;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("TAG", "onScroll: ");
return true;
}

@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d("TAG", "onFling: ");
return true;
}
}
}

It is a quick setup to run this project, so I recommend you try it out. Notice how and when the log events occur.

How to add gesture detector to a ViewPager

Photoview library do exact things you want it's Also support rotating.
create touchAttacher and pass your image to it and it's Done.

Here is part of PhotoViewAttacher code which does exactly what you want :

public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
mImageView = new WeakReference<>(imageView);

imageView.setDrawingCacheEnabled(true);
imageView.setOnTouchListener(this);

ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer)
observer.addOnGlobalLayoutListener(this);

// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);

if (imageView.isInEditMode()) {
return;
}
// Create Gesture Detectors...
mScaleDragDetector = VersionedGestureDetector.newInstance(
imageView.getContext(), this);

mGestureDetector = new GestureDetector(imageView.getContext(),
new GestureDetector.SimpleOnGestureListener() {

// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (null != mLongClickListener) {
mLongClickListener.onLongClick(getImageView());
}
}
});

mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));

// Finally, update the UI so that we're zoomable
setZoomable(zoomable);
}

implement GestureDetector for an ImageView

public class TestImageView extends ImageView  implements OnGestureListener{
public TestImageView(Context context) {
super(context);
// TODO Auto-generated constructor stub

setOnTouchListener(new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-ge`enter code here`nerated method stub
return false;
}
});
}

@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
return false;
}

@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub

}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
// TODO Auto-generated method stub
return false;
}

@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
}

Now you can use TestImageView in your xml instead of just ImageView.

Android: several gesture detectors on the same view

One solution is to call onTouchEvent for both of your Detectors:

tw.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) {
gestureDetector.onTouchEvent(motionEvent);
scaleGestureDetector.onTouchEvent(motionEvent);
return true;
}
});

This may, however, lead to undesirable behavior as both Detectors may simultaneously trigger; for example, one gesture may be identified as both a scale gesture and a scroll gesture.

To solve this, you can introduce a new variable scaleOngoing to keep track of whether or not a scale event is ongoing:

scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {
public void onScaleEnd(ScaleGestureDetector detector) {
scaleOngoing = false;
}
public boolean onScaleBegin(ScaleGestureDetector detector) {
scaleOngoing = true;
return true;
}
public boolean onScale(ScaleGestureDetector detector) {
// do scaling here
return false;
}
});

Then have your GestureDetector's listener check whether a scale gesture is occurring:

mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(!scaleOngoing) {
// do scrolling here
}
return true;
}
});

Adding multiple gestures on a view, not working

You need to wrap all your gestures on one OnTouchListener class and implement your logic.

In the below example, I missed all arguments. and you should provide all of them by your requirement.

import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

class MyGesture implements View.OnTouchListener {
private View view;
private MyDetector myDetector;

private ScaleGestureDetector scaleDetector;
private GestureDetector gestureDetector;

public MyGesture(View view, MyDetector myDetector) {
this.view = view;
this.myDetector = myDetector;

initialGestures();
}

private void initialGestures() {
scaleDetector = new ScaleGestureDetector(
view.getContext(),
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
myDetector.onScale();
return true;
}
});

gestureDetector = new GestureDetector(
view.getContext(),
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
myDetector.onFling();
return false;
}
});
}

@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
scaleDetector.onTouchEvent(event);
return true;
}

interface MyDetector {
void onScale();

void onFling();
}
}

and use it:

myView.setOnTouchListener(new MyGesture(myView, new MyGesture.MyDetector() {
@Override
public void onScale() {
// do sth
}

@Override
public void onFling() {
// do sth
}
}));

Fling gesture detection on grid layout

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.

How to use SimpleOnGestureListener in custom view?

Have you read this? https://developer.android.com/training/gestures/detector#detect-all-supported-gestures

It's worth reading the whole thing, but basically you override the functions for the gestures you want to handle in your SimpleOnGestureListener

val listener = object : GestureDetector.SimpleOnGestureListener() {
override fun onLongPress (MotionEvent e) {
// do whatever
}
}

Then you create a GestureDetector using that listener:

val detector = GestureDetectorCompat(context, listener)

then you override your view's onTouchEvent method and let your detector handle the events:

override fun onTouchEvent(event: MotionEvent): Boolean {
detector.onTouchEvent(event)
return super.onTouchEvent(event)
}


Related Topics



Leave a reply



Submit