开发者

ScaleGestureDetector.onTouchEvent always returns 'true'

I thought that the method "onTouchEvent()" of a "ScaleGestureDetector" instance was supposed to return "true" only if it really handles the touch event, i. e. if it detects a multi-touch scaling gesture (with two fingers). Otherwise I tought that it was supposed to return "false" in order to let other handlers handle the event, e. g. a long press for triggering a context menu.

I observed something different: scaleGestureDetector.onTouchEvent() always returns "true" in my case. The following code snippet of my MyView class:

public boolean onTouchEvent(MotionEvent event) {
  boolean retval = scaleGestureDetector.onTouchEvent(event);
   Log.v("MyView.onTouchEvent()", "Action: " + event.getAction() +
         "; PointerCount: " + event.getPointerCount() +
         "; scaleGestureDetector.onTouchEvent() RetVal: " + retval);
   return(retval);
}

produced the follwing log output after I touched the view for approx 1 second with one finger, then performed a scale gesture with 2 fingers:

01-01 19:09:54.484: VERBOSE/MyView.onTouchEvent()(5930): Action: 0; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:54.510: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:54.541: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:54.580: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:54.820: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:54.910: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:55.050: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:55.350: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:55.400: VERBOSE/MyView.onTouchEvent()(5930): Action: 1; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:09:57.160: INFO/BatteryStatsImpl(96): notePhoneSignalStrengthLocked: 4->3
01-01 19:10:00.060: ERROR/ClockWidget(215): weatherClock onReceive~ mUseAnimation:false
01-01 19:10:00.060: ERROR/ClockWidget(215): handleUiMessage~ in pause. msg:36867
01-01 19:10:00.070: ERROR/ClockWidget(215): weatherClock onReceive~ mUseAnimation:false
01-01 19:10:00.090: INFO开发者_StackOverflow/PI.Alarms(699): Update Alarms start
01-01 19:10:00.090: INFO/PI.Alarms(699): Task Notifications: Already displaying the same alarms, no update
01-01 19:10:00.100: INFO/PI.Alarms(699): Event Notifications: Already displaying the same alarms, no update
01-01 19:10:00.830: VERBOSE/MyView.onTouchEvent()(5930): Action: 0; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:00.840: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:00.870: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:00.900: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:00.922: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:00.931: VERBOSE/MyView.onTouchEvent()(5930): Action: 261; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:00.950: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.002: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.030: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.060: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.090: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.120: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.140: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.172: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.200: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.230: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.252: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.280: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.310: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.342: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.370: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.390: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.424: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.450: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.480: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.510: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.530: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.580: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.690: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.780: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.815: VERBOSE/MyView.onTouchEvent()(5930): Action: 2; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.830: VERBOSE/MyView.onTouchEvent()(5930): Action: 262; PointerCount: 2; scaleGestureDetector.onTouchEvent() RetVal: true
01-01 19:10:01.840: VERBOSE/MyView.onTouchEvent()(5930): Action: 1; PointerCount: 1; scaleGestureDetector.onTouchEvent() RetVal: true

As I said: The return value is always "true"! Is this a bug of ScaleGestureDetector.onTouchEvent()? What can I do to let other handlers handle all non-scale-gesture events (e. g. long press with 1 finger)? Please help!

Nemax


Don't know if it's a bug or intentional, but that's definitely what the source does (ScaleGestureDetector.java:156):

public boolean onTouchEvent(MotionEvent event) {
   final int action = event.getAction();
   boolean handled = true;

   /* ... bunch of code that never updates 'handled' */

   return handled;
}

The way I solved this was to check all of the other types of touch events I might want to handle first, then invoke the gesture detector, e.g.

    if (mLongPressGestureDetector != null && mLongPressGestureDetector.onTouchEvent(event))
        return true;
    else if (mIsInMoveMode && mScaleGestureDetector != null) {
        // Check for a move
        if (action == MotionEvent.ACTION_MOVE && !mScaleGestureDetector.isInProgress()) {
            handleMove(event);
            return true;
        }

        // Now a scale
        mScaleGestureDetector.onTouchEvent(event);
        return true;
    }


The ScaleGestureDetector provides the method isInProgress() that might do what you want...

Here is an example of it in use:

public boolean onTouch(View v, MotionEvent event) {

    mScaleDetector.onTouchEvent(event);

    if (!mScaleDetector.isInProgress()) {
        if (event.getAction() == MotionEvent.ACTION_DOWN || (event.getAction() == MotionEvent.ACTION_MOVE)) {
            touchX = (int) event.getX();
            touchY = (int) event.getY();
            isTouched = true;
        }

        if (event.getAction() == MotionEvent.ACTION_UP) {
            isTouched = false;
        }
    } else {
        isTouched = false;
    }

    return true;
}


This is how I solved the problem: By overriding the Acitivity's onDispatchTouchEvent() method. Any other solution seemed not to work. The good thing about the onDispatchTouchEvent() method is that is always called prior to forwarding any touch events to any other receiver, so you can intercept every single touch event here.

If the event is handled somewhere in here (scale or swipe), I return immediately without forwarding the event to the super class, i. e. to the rest of the view hierarchy. If it is not, I forward it to the super class, so other views can handle it, e. g. to detect short or long clicks.

There were a few more problems to solve though: 1. If the user begins a scale gesture, I had to cancel any long click detection processes because the receiving view would get the first DOWN event, then nothing anymore (after the second finger comes down and the scaling started), and then spuriously think that a long press is being performed. 2. When a long press was performed and the context menu came up, I had to prevent swipe and scale gesture detection here in dispatchOnTouchEvent() until the next UP event, otherwise swiping and scaling would be performed even though the context menu is there.

Quite complicated, but I spent hours and hours and a lot of trial and error, and just could not find any simpler solution. Anyway, handling 1. scale gestures, 2. horizontal swipe gestures, 3. vertical scroll gestures, 4. long clicks and 5. short clicks, all on the same target view(s), is not quite simple a mission to accomplish...

Here is the code (relevant parts of it):

    @Override
public boolean dispatchTouchEvent(MotionEvent e) {
    if (eventInProgress) {
        // View shall only receive scale gesture event if visible
        if (targetView.isShown())
            scaleGestureDetector.onTouchEvent(e);
        if (scaleGestureDetector.isInProgress())
            motionEventConsumed = true;
    }

    if (motionEventConsumed) {
        if (e.getAction() == MotionEvent.ACTION_UP)
            motionEventConsumed = false;
        if (cancelLongPressEvent) {
            cancelLongPressEvent = false;
            targetView.cancelLongPress();
        }
        return (true);
    }

    // Get the action that was done on this touch event
    switch (e.getAction()) {
    case MotionEvent.ACTION_DOWN: {
        // store the X value when the user's finger was pressed down
        downXValue = e.getX();
        downYValue = e.getY();
        cancelLongPressEvent = true;
        eventInProgress = true;
        break;
    }

    case MotionEvent.ACTION_MOVE:
        // When having moved by too many x or y pixels, then
        // cancel any ongoing long klick events
        if (cancelLongPressEvent
                && Math.abs(e.getX() - downXValue)
                        + Math.abs(e.getY() - downYValue) > 40) {
            targetView.cancelLongPress();
            cancelLongPressEvent = false;
        }
        break;

    case MotionEvent.ACTION_UP: {
        if (eventInProgress) {
            // Get the X value when the user released his/her finger
            float deltaX = e.getX() - downXValue;
            float deltaY = e.getY() - downYValue;
            if (Math.abs(deltaX) > Math.abs(deltaY)
                    && Math.abs(deltaX) > 50) {
                // going backwards: pushing stuff to the right
                if (deltaX > 0) {
                    flipRight();
                    return (true);
                }
                // going forwards: pushing stuff to the left
                if (deltaX < 0) {
                    flipLeft();
                    return (true);
                }
                break;
            }
        }
    }
    }

    // If event was not handled here, then forward it to parent,
    // i. e. to view hierarchy
    return (super.dispatchTouchEvent(e));
}

[...]

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
        ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    MenuInflater mi = getMenuInflater();
    mi.inflate(R.menu.lztv_context_menu, menu);
    contextMenuTargetView = v;
    eventInProgress = false;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜