touch – Android SimpleOnGestureListener.onFling getting a null MotionEvent-ThrowExceptions

Exception or error:

I am trying to detect a fling event on a simple Android application, but the first MotionEvent argument is always null. Why is the onFling method being called with a null argument? The Android documentation says that it gets called when a fling event occurs with the initial on down MotionEvent and the matching up MotionEvent.

class MyGestureDetector extends SimpleOnGestureListener {
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        // e1 always == null, return early a null reference exception
        if (e1 == null) {
            return false;
        }
        if (e1.getY() - e2.getY() > 120) {
            handleFlingEvent();
            return true;
        }
        return false;
    }
}

The main Activity has this onTouchEvent method:

GestureDetector flingDetector = new GestureDetector(new MyGestureDetector());

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (flingDetector.onTouchEvent(event)) {
        return true;
    }
    return super.onTouchEvent(event);
}
How to solve:

I have got the similar problem. It happens when the GestureDetector and the onFling method was in a custom view class, which is a part of the activity UI. After a series of testing I found that only the main Activity can detect the swipe gesture well. So I solve the problem by replace them in the activity. But I didn’t find the root cause of this problem.

###

I first ran into this issue with Android 4.0 as my code works fine in all the prior versions. After taking a cue from the suggestions here, I’ve found an easy workaround is to just keep track of the onDown event and use it in place of the first param of onFling in case it happens to be null.

public class MySimpleGestureListener extends SimpleOnGestureListener {
    protected MotionEvent mLastOnDownEvent = null;

    @Override
    public boolean onDown(MotionEvent e) {
        mLastOnDownEvent = e;
        return true;//super.onDown(e);
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if (e1==null)
            e1 = mLastOnDownEvent;
        if (e1==null || e2==null)
            return false;
        float dX = e2.getX()-e1.getX();
        float dY = e2.getY()-e1.getY();
        ...rest of code
    }

    ...rest of class
}

###

I’ve got the same problem with custom view and OnGestureListener onFling. Found out this:

Whether or not you use GestureDetector.SimpleOnGestureListener, you
must always implement an onDown() method that returns true.

Sourced from http://developer.android.com/training/custom-views/making-interactive.html

###

In custom view group class you need override 2 methods: onInterceptTouchEvent and onTouchEvent. After that you need to call GestureDetector.onTouchEvent() in both methods. Event ACTION_DOWN is passed to onInterceptTouchEvent only.

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if(mDetector.onTouchEvent(event)) {
        return true;
    }
    return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if(mDetector.onTouchEvent(event)) {
        return true;
    }
    return super.onTouchEvent(event);
}

###

I had 3 buttons in horizonal linear layout and this layout in turn surrounded by horizontalscrollview.

Then I set simplegesturelistener on the horizontalscrollview and got the same error as above. Then i also set simplegesturelistener for all the buttons in the layout and it worked.

###

I think the onDown event has been resumed by the scrollview, so we can’t get the event in the activity. If you custom a ScrollView and override the dispatchTouchEvent() and onTouchEvent(), the problem will be solved.

Activity:

public class TestActivity extends Activity {
     private MyScrollView scrollView;
     private TestActivity me;
     private GestureDetector detector; 

     protected void onCreate(Bundle savedInstanceState) {
            this.me = this;
            MySimpleGestureListener gestureListener = new MySimpleGestureListener();
            this.detector = new GestureDetector(gestureListener);
            this.scrollView.setOnTouchListener(onTouchListerner);
            this.scrollView.setDetector(detector);
     }

     private View.OnTouchListener onTouchListener = new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return me.detector.onTouchEvent(event);
    }
 };
}

MySimpleGestureListener:

public class MySimpleGestureListener extends SimpleOnGestureListener {
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

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

    @Override
    public void onLongPress(MotionEvent e) {
    }


    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
                    // TODO do your onFling here

    }
}

At last, MyScrollView

public class MyScrollView extends ScrollView {
    private GestureDetector detector;

    @Override
public boolean dispatchTouchEvent(MotionEvent ev){
    if (this.detector!= null) {
            detector.onTouchEvent(ev);
        super.dispatchTouchEvent(ev);
        return true;
    }
    return super.dispatchTouchEvent(ev);
} 

@Override
public boolean onTouchEvent(MotionEvent ev) {
    super.onTouchEvent(ev);
    //
    if (this.detector != null) {
        return this.detector.onTouchEvent(ev);
    } 
    return false;
}

    // here is the getter and setter
}

Hope this help.

Leave a Reply

Your email address will not be published. Required fields are marked *