GDanger
GDanger

Reputation: 1671

MotionEvent is recycled while still in use

I'm using a SurfaceView to draw touches as an overlay over my app. I would like my other Views to be able to respond to touch events while at the same time as the SurfaceView so in the ViewGroup that contains them both I added

MyViewGroup:

@Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent){
    mSurfaceView.onTouchEvent(motionEvent);
    return false;
}

Since the SurfaceView sits behind the other views, this works great. Both the SurfaceView and the View on top of it receive the MotionEvent.

The SurfaceView then further forwards the event to the thread rendering into it.

MySurfaceView:

@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
    if (mThreadHandler != null){
        mThreadHandler.sendMotionEvent(motionEvent);
    }
    return true;
}

Then when my rendering thread receives the message it runs:

MyRenderThread:

private void handleMotionEvent(MotionEvent motionEvent){
    switch (motionEvent.getAction()){
        case MotionEvent.ACTION_DOWN:
            float x = motionEvent.getX();                          // Breakpoint 1
            float x2 = motionEvent.getX();                         // Breakpoint 2
            float x3 = motionEvent.getX();                         // Breakpoint 3
            path.moveTo(motionEvent.getX(), motionEvent.getY());
            break;
        case MotionEvent.ACTION_MOVE:
            path.lineTo(motionEvent.getX(),motionEvent.getY());
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
                // do nothing
            break;
        default:
            break;
   }
}

When I run this code in debug mode and swipe my finger across the ViewGroup the values for x, x2, and x3 all differ. I suspect this is because the UI thread is re-using the MotionEvent before it has been consumed by MyRenderThread. How can I fix this?

The bad solution would be to copy each MotionEvent as a new object and pass that to MyRenderThread, but this requires a lot of object creation/destruction.

Upvotes: 3

Views: 2270

Answers (1)

GDanger
GDanger

Reputation: 1671

MotionEvent.obtain(MotionEvent ev) will return a MotionEvent from the pool that is a copy of the input event.

MyViewGroup:

@Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent){
    mSurfaceView.fowardTouchEvent(MotionEvent.obtain(motionEvent); 
    return false;
}

MySurfaceView:

public void forwardTouchEvent(MotionEvent motionEvent){
    if (scratchpadThreadHandler != null){
        scratchpadThreadHandler.sendMotionEvent(motionEvent);
    }
}

MyRenderThread:

private void handleMotionEvent(MotionEvent motionEvent){
    switch (motionEvent.getAction()){
        case MotionEvent.ACTION_DOWN:
            float x = motionEvent.getX();                          // Breakpoint 1
            float x2 = motionEvent.getX();                         // Breakpoint 2
            float x3 = motionEvent.getX();                         // Breakpoint 3
            path.moveTo(motionEvent.getX(), motionEvent.getY());
            break;
        case MotionEvent.ACTION_MOVE:
            path.lineTo(motionEvent.getX(),motionEvent.getY());
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
                // do nothing
            break;
        default:
            break;
   }
    motionEvent.recycle();  // Mark this MotionEvent as available for recycling.
}

This way each event is still copied (not sure what to do about that) but at least the objects are reused.

Upvotes: 2

Related Questions