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.
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
Changing Text Color of Menu Item in Navigation Drawer
How to Do Circular Scrolling on Viewpager
How to Test If a Fragment View Is Visible to the User
Clarify the Firebase Connections
Illegalstateexception When Replacing a Fragment
Android Google Maps API V2 Zoom to Current Location
Preventing/Catching "Illegalargumentexception: Parameter Must Be a Descendant of This View" Error
How to Use Device Default Theme for App
Android Studio Create Project Template for New Projects
Cannot Find Symbol Databindingcomponent on Android Studio 3.2 Canary 16 Kotlin Project
Android Beginner Difference Between Padding and Margin
Why Does Gradle Build My Module in Release Mode When the App Is in Debug
Android. Scrolling 2 Listviews Together
Android Activity Has Leaked Window Com.Android.Internal.Policy.Impl.Phonewindow$Decorview Issue