Gesture Listener onFling Not Acting Consistant
Asked Answered
W

5

11

Update: See bounty for expanded question.

I have a GestureDetector setup on a ListView. The ListView is an entire fragment that comes from side of window and overlays another fragment partially. I want to give user ability to swipe it close (i.e. Wunderlist is a great example of this function on right side).

Here is my setup:

    gestureListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {

            if (gestureDetector.onTouchEvent(event)) {
                return true;
            }

            return false;
        }
    };
    listView.setOnTouchListener(gestureListener); 

The Listener itself:

public class GestureListener extends SimpleOnGestureListener {

        private static final int SWIPE_MIN_DISTANCE = 180;
        private static final int SWIPE_THRESHOLD_VELOCITY = 50;

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {

            try {
                if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
                        && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {

                    FragmentManager fma = getActivity()
                            .getSupportFragmentManager();



                    FragmentManager fm = getFragmentManager();
                    FragmentTransaction t = fm.beginTransaction();

                    SherlockListFragment mFrag = new RateReviewFragment();

                    t.add(R.id.main_frag, mFrag);
                    t.setCustomAnimations(R.anim.animation_enter,
                            R.anim.animation_leave);
                    t.remove(mFrag);
                    t.commit();

                }
                return false;
            } catch (Exception e) {

            }
            return false;
        }





        @Override
        public void onLongPress(MotionEvent e) {

            // edited out; this opens up a context menu
        }

    }

Sometimes, when the ListView scrolls to the bottom (or list scrolling is interrupted with clicks on a list row) the GestureListener simply stops ... listening. Swiping will not work. You have to scroll back to top to get it to work again.

If anyone can help me isolate these issues I would be grateful!

UPDATE : Only one issue remains, "the listener stops listening"

UPDATE TWO: I may have figured out what is CAUSING this; just dont know the fix:

I have found something out after logging my actions; The lower I get in the list (and the more I interact with it), the measurements are not consistent. For example, if I move my finger a centimeter left and right - at very top of list, it will say I moved, for example, 200 pixels. But when I have the problem stated above, it is far lower, maybe 50, for the SAME 1 centimeter distance. So the window doesn't close because it's not meeting my if conditions.

Sidenote: I have stated more than once, the problem is when "interacting with the list". This means: If I quickly scroll from top to bottom; no issue; but If I slowly work my way down, perhaps tapping on the screen, scrolling on and off, clicking buttons on the listView, some of which open a new activity (and then come back to same position), this is "interacting" with".

Worthy answered 6/6, 2013 at 20:42 Comment(2)
If you are looking for the new gmail app kind of sliding drawer from the left you might want to have a look at this - developer.android.com/training/implementing-navigation/…Olecranon
@Olecranon this is not that kind of slider for navigation. I actually already have that on left side.. This a right side pop out to show content.Worthy
I
3

try this,

    gestureDetector = new GestureDetector(getActivity(),
            new GestureListener());
    View.OnTouchListener gestureListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
           listView.onTouchEvent(event);
           gestureDetector.onTouchEvent(event);

            return true;
        }
    };
    listView.setOnTouchListener(gestureListener);

and make the following change

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
  return true;
}

EDIT: I saw the app wunderlist, if u want to simulate that functionality try out this approach,

Use onInterceptTouch for the root layout (of which listView is a child of), inside this method u have to check whteher the user has swiped or not like the following,

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_MOVE) {
    if(/* Check your condition here */) { 
      // User has swiped 
      return true;
    }
  }

  return false; // return false so that the event will be passed to child views.
}

for more info Android onInterceptTouchEvent

Influx answered 14/6, 2013 at 13:19 Comment(6)
hey thanks for the suggestion; unfortunately, I still occasional lose the ability to swipe the window shut.Worthy
In my experience working with Android, you can't expect the device to always provide the right ('expected' would be a better word) touch points for user interaction. It is up to us (The Developers) to handle such case, so why not play around the if conditions you have inside onFling() to select the best one where 8/10 times it would work. Hope it helpsInflux
I agree. If you see my updated question, I found a huge inconsistency. Though I know there is a way to do this, as Wunderlist executes it perfectly.Worthy
I'm learning more as read about this; in your answer above, you have /* Check your condition here */ - my condition requires specific onFling() attributes like two motionEvent values (not one) and velocity. How would I check my condition here?Worthy
There are many ways to do this, one being pointed out be @neil as 'Thirdly' in his answer but I would suggest going about this with the help of velocity tracker(developer.android.com/reference/android/view/…).Influx
Thanks for your help! You got me the information I need, now I need to just implement it! Marking correct.Worthy
P
2

change the onTouch this way:

@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
     boolean consumed = gestureDetector.onTouchEvent(event);
     if (event.getAction() == MotionEvent.ACTION_MOVE) {
                return false;
     }

    return consumed;
}
Poem answered 10/6, 2013 at 7:23 Comment(6)
Thanks. Will test... Question: I didn't think onTouch was part of this SimpleOnGesture methods?Worthy
still the gestureDetector is forwarding touch events to itPoem
this seems to have opposite effect. It kills the entire swipe-to-close ability; yet all three problems above remain an issue.Worthy
So you are able to scroll the ListView but the fling is not fired?Poem
Sorry, fling is back, but the scroll has the same issues.... (as does the fling) I do appreciate your help and look forward to giving these points away. This is along problem of mine in this app I am working on.Worthy
Well I fixed part of the issues, where now all of the native ListView gestures are in fact working; the remaining issue is: In some instances (especially when user scrolls slowly reading the rows, perhaps "starting and stopping" the scrolling, maybe going back up and then down again) the onFling doesn't respond. (as described above)Worthy
P
1

Don't really sure if this will help as god knows what's going on behind the scene with all this fragments issues but try this:

After onTouch has been called , before returning false check if gestureDetector is still alive (not null),(By Sending callback or something to the listview class) if not create new instance of it and call again to

listView.setOnTouchListener(gestureListener); 
Property answered 12/6, 2013 at 19:56 Comment(2)
Well, I Log.d gestureListener, and I don't believe it was ever null. I got my listView in position where my onFling would not work, and the listener was still logging something... if it was null it seemed the Log would show this?Worthy
Did my best to try using the slow as dirt debug system and difficult to tell if it was null... I did Log.d to see when the event and onFlingwas being called. Several times they were both called even when I was swiping and the Fragment was not reacting to the Fling. That almost indicates that my Fling code is wrong? But I beleive all swipes met the criteria in length and velocity.Worthy
G
1

First

I note that in your gesture listener you return true in onDown. If I've understood your question correctly you are leaving tap and vertical swipe events for the ListView, and so you are only interested in horizontal swipe events in your code.

Why not remove all the functions you are not interested in, thus leaving everything you aren't interested in to the ListView? If you have to have them, at least return false, which is the default (see here).

As an aside, the code for AbsListView (the parent of ListView) shows a pretty complex state system for handling taps and swipes, so it's probably best left as untocuhed as possible in my view

Secondly

Given your more recent observations, perhaps the key is to consider the ratio between X and Y displacement rather than absolute size of swipe. Whilst I can't explain the change in reported size of swipe, what we are really interested in is a movement which is predominantly horizontal, ie:

(change in X) >> (change in Y)

This can be achieved as follows:

if ((velocityY == 0.0f) || (Math.abs(velocityX/velocityY) > 3.0f) {
    // The movement is predominantly horizontal
    // Put other checks here (like direction and so on)
    // Then do the stuff
}

This has the advantage of not using e1 or e2, which brings us on to ...

Thirdly

As you note, the issue is when you have interacted with the listview. At this point, there may be multiple pointers being reported in the Event. It is not guaranteed that the default X and Y information returned by them relates to any particular pointer event. It is probably worth checking that you are comparing information from the same pointer event if you do want to do calculations involving X and Y, using something like:

int numPointers = e1.getPointerCount();
for (int pointerIndex1 = 0; pointerIndex1 < numPointers; pointerIndex1++) {
    int pointerId = e1.getPointerId(pointerIndex1);
    // find index for pointerId in e2.

    float deltaX = e2.getX(pointerIndex2) - e1.getX(pointerIndex1);
    // and so on
}
Gavrielle answered 16/6, 2013 at 21:3 Comment(2)
I have found something out after logging my actions; The lower I get in the list (and the more I interact with it), the measurements are not consistent. For example, if I move my finger a centimeter left and right - at very top of list, it will say I moved, for example, 200 pixels. But when I have the problem above, it is far lower, maybe 50, for the SAME 1 centimeter distance. So the window doesn't close because it's not meeting my if conditions.Worthy
Hey I appreciate the feedback again. I'll see what I figure out and keep you posted.Worthy
D
0

Firtly try to make OnTouchListener like this:

View.OnTouchListener gestureListener = new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
    gestureDetector.onTouchEvent(event);
        return true;
    }
};

Then - you're always returning false in onFling(). Return true in try/catch clause. And try to pass the same data to the onScroll method in else clause inside onFling.

Disinfest answered 16/6, 2013 at 14:18 Comment(2)
I have always had some confusion on that; What does returning false do as opposed to true? Also changing the FIRST false in onFling() (to true) basically affects the normal listView scroll. It will let you scroll the list, but it will only move as your finger touches...Worthy
The you must detect in onFling the horizontal fling and do the list scrolling by yourself I think. About true/false: while I was doing like documentation says my gestureListener wasn't working (I've written it not so far). So I've just adviced you to do what I've done.Disinfest

© 2022 - 2024 — McMap. All rights reserved.