I've got a ViewFlipper at the side of the screen in my app containing a bunch of different views, and I would like the user to be able to dismiss this by swiping to the left. So I did the usual...
private class SwipeDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//Dismiss view
}
#Override
public boolean onDown(MotionEvent e) {
return true;
}
}
...and then set the onTouchListener of the ViewFlipper to call onTouchEvent in SwipeDetector. It all worked great, however I then noticed because it was consuming all the touch events going into the ViewFlipper, I couldn't click anything when the ViewFlipper itself. If I don't override onDown or I make it return false then the TouchListener ignores the rest of the event and I don't get the fling. I'm happy to hack together some kind of custom swipe detection if I could even handle all the ACTION_MOVE events following the user touching the ViewFlipper but can't even do that. Is there any way to keep listening to the MotionEvent without consuming the onDown?
What works for me is extending View.OnTouchListener instead of SimpleGestureListener, and override onTouch method. Please, take this snippet as a very simple (and improvable) approach to get horizontal swipe detection.-
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
return true;
}
case MotionEvent.ACTION_UP: {
upX = event.getX();
float deltaX = downX - upX;
if (Math.abs(deltaX) > MIN_DISTANCE) {
if (deltaX < 0) {
this.onLeftToRightSwipe();
return true;
}
if (deltaX > 0) {
this.onRightToLeftSwipe();
return true;
}
return true;
}
return true;
}
}
return false;
}
Did you notice the GestureDetector? It is available since API Version 1.
http://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html
Both fling and scroll gestures are handled.
Related
so I tried Zala's code for handling the gestures from this question android how to handle right to left swipe gestures, it works but the problem is my component is inside a scrollview so the gestures sometimes are detected sometimes not, I tried few different codes to solve this scrollview issue still the same behavior.
Anyone could help please !
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:{
downX = event.getX();}
case MotionEvent.ACTION_UP:{
upX = event.getX();
float deltaX = downX - upX;
if(Math.abs(deltaX)>0){
if(deltaX>=0){
swipeToRight();
return true;
}else{
swipeToLeft();
return true;
}
}
}
}
return false;
}
});
I would like to set up following layout with working scroll and click events:
My CustomSlidingDrawer.xml:
<com.project.util.CustomSliderDrawer
android:id="#+id/slidingDrawerHotelList"
android:layout_width="0dp"
android:layout_height="match_parent"
android:allowSingleTap="false"
android:content="#+id/content"
android:handle="#+id/handle"
android:orientation="horizontal" >
<RelativeLayout
android:id="#+id/handle"
android:layout_width="0dp"
android:layout_height="match_parent" >
<RelativeLayout
android:id="#+id/content"
android:layout_width="0dp"
android:layout_height="0dp" >
</RelativeLayout>
</RelativeLayout>
</com.project.util.CustomSliderDrawer>
The width of the elements are set during runtime.
Here I have to mention, that my real content is in the handle of the Drawer, and the content is only a dummy. I'm doing that in this way, because I was not able to hide the handle. Or make it invisible. So, if I want to swipe in I touch the handle (my content) and pull. In the SlidingDrawer, the handle is a placeholder for a fragment which contains a CustomListView.
What now works: I can scroll the SlidingDrawer.
What behavior I would like to gain:
On the SlidingDrawer the MotionEvents are detected in X-Axis direction (Vertical swipe). If a MotionEvent in Y-Axis direction is detected, then the Events shoud go to my CustomListView. Even the onClicks should pass through to the ListView.
What I've tried: I read a lot: here, this page and this one
Then I tried to put all the navigation control in the main activity, what makes really sense in my eyes:
MainActivity:
slidingDrawer.setOnTouchListener(new OnTouchListener() {
int downX, downY;
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
downY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int deltaX = downX - (int) event.getX();
int deltaY = downY - (int) event.getY();
if (Math.abs(deltaX) > Math.abs(deltaY)) {
Log.i(TAG, "scrollX direction");
isVerticalSwipe = true;
} else {
Log.i(TAG, "scrollY direction");
isVerticalSwipe = false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
});
But when I try here to return "isVerticalSwipe" as local, the SlidingDrawer works not properly anymore. :-(
The 2nd try was to intercept the touch events in the custom sliding drawer:
CustomSlidingDrawer:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onInterceptTouchEvent ACTION_DOWN");
xDistance = yDistance = 0f;
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onInterceptTouchEvent ACTION_MOVE");
final int curX = (int) ev.getX();
final int curY = (int) ev.getY();
xDistance += Math.abs(curX - downX);
yDistance += Math.abs(curY - downY);
downX = curX;
downY = curY;
if (xDistance > yDistance)
return false;
}
return super.onInterceptTouchEvent(ev);
}
Here happens only the DOWN event and the MOVE event is never reached.
I also made some trys with "dispatchTouchEvent" but I got no success.
I know, that the events will pass through the "layer" of my views - from top to bottom.
So it would be perfect to know, how I can implement the construct from the first link above in my CustomSlidingDrawer (= top view) and when a vertical swipe is detected keep and handle it and if it's a horizontal swipe, then please send it to the CustomListView and perform there the events.
Any help, thoughts, ideas or code snippets are appreciated. :)
Thank you!
[EDIT]
I've made some progress. I get now my events from the SlidingDrawer down to my ListView. I can scroll and click in the list - that works fine. But unfortunately, my SlidingDrawer shows no reaction anymore. These lines are in my custom SlidingDrawer:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
gestureDetector = new GestureDetector(new MyGestureDetector());
if (gestureDetector.onTouchEvent(event)) {
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL) {
return false;
} else {
Log.i(TAG, "gestureDetector returned false");
return false;
}
}
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent e) {
Log.i(TAG, "onDown");
downX = (int) e.getX();
downY = (int) e.getY();
return false;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
float deltaX = downX - e2.getX();
float deltaY = downY - e2.getY();
if (Math.abs(deltaX) > Math.abs(deltaY)) {
Log.i(TAG, "scrollX direction");
return true;
} else {
Log.i(TAG, "scrollY direction");
return false;
}
}
}
I don't really understand why it's not working, if there is a swipe in x-direction, then my Drawer should handle the gesture, bc I return true. Instead every event is forwarded to the ListView.
I tried to run "dispatchTouchEvent" before the "onInterceptTouchEvent" but I had no success. Maybe there is a better solution instead of using a SlidingDrawer for moving views by finger?
Okay, after a lot of tears, sweat and blood I finally found a solution which works for me: I don't take a SlidingDrawer anymore and move my layout via changing the LayoutParams.
Over the "dispatchTouchEvent" I get access to my list.
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mGestureDetector.onTouchEvent(ev)) {
return true;
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "ACTION_UP");
Log.i(TAG, "getLeft: " + getLeft());
if (getLeft() < 280) {
// run ani to left
} else {
// run ani to right
}
}
return super.dispatchTouchEvent(ev);
}
And in my GestureDetector I handle in the overridden onScroll-function the layout translation. If someone has need for the detector don't hesitate to ask, I'll share.
I have a ViewPager which uses GridViews for pages. I would like the ViewPager to switch pages when I swipe across the screen.
The problem is that swipes are not detected when they are made across the GridView. Outside of the GridView, the swipes work correctly; it seems that the GridView is trapping all touch events without passing it to ViewPager first.
While fiddling with the source code, I did this to a custom class extended from GridView:
#Override
public boolean onTouchEvent(MotionEvent event) {
return pager.onInterceptTouchEvent(event);
}
-- where pager refers to the ViewPager class. With this, ViewPager will correctly detect swipes and move pages accordingly, but it doesn't allow GridView to accept any events, so I can't click on the items.
What I would like to be able to do is correctly detect swipes in ViewPager and item clicks on GridView.
I had trouble with colig's implementation, but I was able to get it to work by subclassing ViewPager and overriding the onInterceptTouchEvent() method. I only checked for swipes in the X direction to allow for vertical scrolling if necessary.
private static final int minSwipeDistance = 30;
private float mTouchX;
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean response = super.onInterceptTouchEvent(event);
float x = event.getX();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTouchX = x;
break;
case MotionEvent.ACTION_MOVE:
float dX = Math.abs(x - mTouchX);
if (dX > minSwipeDistance)
return true;
break;
}
return response;
}
Alix is on the right track. I managed to come up with this simple-looking fix. I'm not entirely sure of how it works, but it does! And for future reference, it works for other kinds of views too -- TableLayout, for example -- not just GridView.
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
downX = x;
downY = y;
return super.onInterceptTouchEvent(event);
}
case MotionEvent.ACTION_MOVE: {
deltaX = Math.abs(downX - x);
deltaY = Math.abs(downY - y);
return super.onTouchEvent(event);
}
case MotionEvent.ACTION_UP: {
if (deltaX > 4 && deltaY > 4) {
super.onTouchEvent(event);
}
}
}
return super.onInterceptTouchEvent(event);
}
You can override onInterceptTouchEvent for dispatch evenement where you want
I have a viewflipper which includes a number of layouts. Each layout has a scrollview as the root.
Below the viewflipper I have a horizontal scrollview which contains textviews which acts as a navigation bar.
My original issue was that the fling stopped working as soon as I added a scrollview but I added the following code:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
And the scrolling now works along with the fling.
The only issue I have now though is that the HSV nav bar now acts a little strange. Sometimes when I try to slide it across, to select new contents, the HSV springs back to where it was. Other times the HSV thinks I want to fling and moves to the next contents. This is of course dependant of the speed I try to scroll it.
I want the nav bar to work independently of the flipper.
I'm trying to understand the code to fix it myself but not getting anywhere.
I have the following in my onCreate:
gestureDetector = new GestureDetector(new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
return false;
}};
I have the MyGestureDetector class:
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) {
flipper.setInAnimation(flip_in_from_right);
flipper.setOutAnimation(flip_out_to_left);
flipper.showNext();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_left);
flipper.setOutAnimation(flip_out_to_right);
flipper.showPrevious();
}
} catch (Exception e) {
// nothing
}
And then the override:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
Can anyone shed any light on this?
Where exactly is your HSV? Is it pinned to the bottom of the screen? If so, here's what I would do. Within your onFling event, check the Y position of the fling and make sure it's not within your HSV. If it is, then return false and you should be good to go.
I have list items that have a HorizontalScrollView in each of them. Hence, I should be able to swipe up and down the list and also swipe through the list item horizontally. (e.g. Pulse News App).
What I'm encountering is that, whenever I scroll the HorizontalScrollView, the ListView also gets scrolled by tiny bit but giving out annoying disturbances in terms of User Experience.
So, I was actually thinking to subclass ListView and implement my own ListView widget. However, I don't know which method to override so that I could increase X tolerance and stop the listview from giving these unwanted flickering.
gestureDetector = new GestureDetector(new YScrollDetector());
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Call super first because it does some hidden motion event handling
boolean result = super.onInterceptTouchEvent(ev);
//Now see if we are scrolling vertically with the custom gesture detector
if (gestureDetector.onTouchEvent(ev)) {
return result;
}
//If not scrolling vertically (more y than x), don't hijack the event.
else {
return false;
}
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
try {
if (Math.abs(distanceY) > Math.abs(distanceX)) {
return true;
} else {
return false;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
This is will help you, you have to override these in the custom list view you create,i used this to eliminate the problem in my application. Hope this helps.