I have a horizontal RecyclerView. I need to scroll it but to disable fling by swipe gesture.
Originally all work is done at onTouchEvent method and I dont know how to disable it without rewriting all touch handling
You can use a GestureDetector with a SimpleOnGestureListener to capture fling events and decide whether or not to allow them.
RecyclerView recycler = findViewById(R.id.recycler);
GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// return true if you want to stop the fling
// return false if you want to allow the fling
return true;
}
});
recycler.setOnTouchListener((v, event) -> detector.onTouchEvent(event));
GestureDetector.OnGestureListener#onFling()
Related
I was struggling with adding a gesture detector to a subview in my project. Do I override the parent's onTouchEvent or the child's onTouchEvent? Do I make an OnTouchListener and add the gesture detector there? The documentation shows an example for how to add a gesture detector to the activity itself but it is not clear how to add it to a view. The same process could be used if subclassing a view (example here), but I want to add the gesture without subclassing anything.
This is the closest other question I could find but it is specific to a fling gesture on an ImageView, not to the general case of any View. Also there is some disagreement in those answers about when to return true or false.
To help myself understand how it works, I made a stand alone project. My answer is below.
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.
short version in kotlin to detect double tap only for a view:
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent?): Boolean {
Log.d("myApp", "double tap")
return true
}
})
myView.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
and don't forget to make myView clickable
I'm trying to detect a onFling event on the entire area of my activity. To do this I:
1) set the GestureDetectorCompat:
private GestureDetectorCompat gDetect;
2) in OnCreate initialize the detector:
gDetect = new GestureDetectorCompat(this, new GestureListener());
3) override the onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
gDetect.onTouchEvent(event);
return super.onTouchEvent(event);
}
4) create the GestureListener class:
class GestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent event) {
return true;
}
#Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.e(null, "onFling: " + event1.toString() + event2.toString());
return super.onFling(event1, event2, velocityX, velocityY);
}
}
It works, but it detects onFling gesture ONLY in the part of my UI where there is not any other view. So, if I swipe in a blank area it works, if I swipe in a part of the activity where is another view (for example a ScrollView or a LinearLayout) the detection isn't triggered.
I hope I've explained my problem: how I can detect gesture on the entire surface of the Activity, to accomplish a simple swipe gesture?
Many tanks.
You can use GestureOverlayView. Here is a great starter resource for GestureOverlayView . Also you can have a look at this SO question.
Basically to use this your application must use the view "GestureOverlayView" and you place your other views in this view. Your activity must implement the interface "OnGesturePerformedListener" and if a gesture is detected then "onGesturePerformedListener" method is called.
Hope this helps. Goodluck!
I am trying to make a simple 2D game using Opengl es 2. I am using GLSurfaceView. I am trying to detect fling gesture on the view but it does not detect anything. Why is this? Following is the code :-
Tried to declare the gesture detecter inside and outside the glsurfaceview.
#SuppressWarnings("deprecation")
final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent e) {
Log.d("", "Longpress detected");
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
Log.d("", "OnFling detected");
return super.onFling(e1, e2, velocityX, velocityY);
}
});
Inside the GLSurfaceView
#Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
When i fling on the surface, it detects the long press and prints the debug message for long press rather than fling. Whatever i do on screen, it just detects longpress.. what is wrong?
Include this override for onDown in your listener:
#Override
public boolean onDown(MotionEvent event) {
return true;
}
From the documentation:
This is because all gestures begin with an onDown() message. If you return false from onDown(), as GestureDetector.SimpleOnGestureListener does by default, the system assumes that you want to ignore the rest of the gesture, and the other methods of GestureDetector.OnGestureListener never get called. This has the potential to cause unexpected problems in your app."
I appreciate that this does not explain why you are still managing to get a longpress detected. If the above resolves your issue, perhaps that is a puzzle that doesn't need solving...
I'm implementing a swipe gesture similar to the one in Android Gmail app where you drag across an item to archive it.
I attach a touch listener to the item view and a gesture detector and listener to listen to touch events like this:
GestureDetector.OnGestureListener gl;
gl = new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
// ... handle horizontal scrolling
return true;
}
};
final GestureDetector gd = new GestureDetector(getContext(), gl);
itemView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent e) {
gd.onTouchEvent(e);
// ... handle TOUCH_UP and TOUCH_CANCEL
return true;
}
});
The problem is that as soon as I start dragging vertically, or go outside the bounds of the item view, the gesture is cancelled and list view starts handling it for normal vertical list scrolling. I need the gesture to not cancel until the user releases the finger, even if it moves outside the table cell, like it's done in Gmail app.
How do I have it not cancelled?
I have a listview that displays entries for a given date. There are buttons above the listview that allow you to increase/decrease the date. Everything works. What I'm looking to do is replace those buttons and have the user swipe right/left to increase/decrease the date.
What's the best way to go about this? I don't care what item is swiped, often there will be no items in the listview for a given date, just as long as it happens on the listview area. I do have click and longclick listeners on the items already.
Just implement the OnGestureListener.
public class MyListActivity extends ListActivity implements OnGestureListener
Use a GestureDetector
GestureDetector detector = new GestureDetector(this, this);
Pass the touch event of the list to the GestureDetector
listView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View view, MotionEvent e) {
detector.onTouchEvent(e);
return false;
}
});
And finally use the fling method to detect a gesture. You can use the velocity values to detect the direction of the movement.
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {}