Android:检测用户是否触摸并拖出按钮区域?

时间:2011-06-20 11:03:14

标签: android button gestures

在Android中,我们如何检测用户是否触摸按钮并拖出此按钮的区域?

9 个答案:

答案 0 :(得分:86)

检查MotionEvent.MOVE_OUTSIDE:检查MotionEvent.MOVE:

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    }
    if(event.getAction() == MotionEvent.ACTION_MOVE){
        if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
            // User moved outside bounds
        }
    }
    return false;
}

注意:如果您想要定位Android 4.0,则会打开全新的可能性: http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER

答案 1 :(得分:21)

Entreco发布的答案在我的案例中需要稍微调整一下。我不得不替换:

if(!rect.contains((int)event.getX(), (int)event.getY()))

代表

if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY()))

因为event.getX()event.getY()仅适用于ImageView本身,而不适用于整个屏幕。

答案 2 :(得分:5)

我在OnTouch中添加了一些日志记录,发现diff --git a/tornado/gen.py b/tornado/gen.py index aa931b4..b348f21 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -91,6 +91,12 @@ from tornado.concurrent import Future, TracebackFuture from tornado.ioloop import IOLoop from tornado.stack_context import ExceptionStackContext, wrap +def _is_generator(obj): + # cython generates a new generator type for each module without a + # common base class :( + return (isinstance(obj, types.GeneratorType) or + str(type(obj)) == "<type 'generator'>") + class KeyReuseError(Exception): pass @@ -147,7 +153,7 @@ def engine(func): except (Return, StopIteration) as e: result = getattr(e, 'value', None) else: - if isinstance(result, types.GeneratorType): + if _is_generator(result): def final_callback(value): if value is not None: raise ReturnValueIgnoredError( @@ -219,7 +225,7 @@ def coroutine(func): future.set_exc_info(sys.exc_info()) return future else: - if isinstance(result, types.GeneratorType): + if _is_generator(result): def final_callback(value): deactivate() future.set_result(value) 被点击了。这对我来说已经足够了......

答案 3 :(得分:4)

我遇到了与OP相同的问题,我想知道何时(1)特定View被触及以及(2)当View上的向下触摸被释放时或(3)当向下触摸移动到View的范围之外时。我在这个帖子中汇集了各种答案,以创建View.OnTouchListener(名为SimpleTouchListener)的简单扩展,以便其他人不必使用MotionEvent对象。可以在here或本答案的底部找到课程的来源。

要使用此类,只需将其设置为View.setOnTouchListener(View.OnTouchListener)方法的单个参数:

myView.setOnTouchListener(new SimpleTouchListener() {

    @Override
    public void onDownTouchAction() {
        // do something when the View is touched down
    }

    @Override
    public void onUpTouchAction() {
        // do something when the down touch is released on the View
    }

    @Override
    public void onCancelTouchAction() {
        // do something when the down touch is canceled
        // (e.g. because the down touch moved outside the bounds of the View
    }
});

以下是您可以添加到项目中的类的来源:

public abstract class SimpleTouchListener implements View.OnTouchListener {

    /**
     * Flag determining whether the down touch has stayed with the bounds of the view.
     */
    private boolean touchStayedWithinViewBounds;

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchStayedWithinViewBounds = true;
                onDownTouchAction();
                return true;

            case MotionEvent.ACTION_UP:
                if (touchStayedWithinViewBounds) {
                    onUpTouchAction();
                }
                return true;

            case MotionEvent.ACTION_MOVE:
                if (touchStayedWithinViewBounds
                        && !isMotionEventInsideView(view, event)) {
                    onCancelTouchAction();
                    touchStayedWithinViewBounds = false;
                }
                return true;

            case MotionEvent.ACTION_CANCEL:
                onCancelTouchAction();
                return true;

            default:
                return false;
        }
    }

    /**
     * Method which is called when the {@link View} is touched down.
     */
    public abstract void onDownTouchAction();

    /**
     * Method which is called when the down touch is released on the {@link View}.
     */
    public abstract void onUpTouchAction();

    /**
     * Method which is called when the down touch is canceled,
     * e.g. because the down touch moved outside the bounds of the {@link View}.
     */
    public abstract void onCancelTouchAction();

    /**
     * Determines whether the provided {@link MotionEvent} represents a touch event
     * that occurred within the bounds of the provided {@link View}.
     *
     * @param view  the {@link View} to which the {@link MotionEvent} has been dispatched.
     * @param event the {@link MotionEvent} of interest.
     * @return true iff the provided {@link MotionEvent} represents a touch event
     * that occurred within the bounds of the provided {@link View}.
     */
    private boolean isMotionEventInsideView(View view, MotionEvent event) {
        Rect viewRect = new Rect(
                view.getLeft(),
                view.getTop(),
                view.getRight(),
                view.getBottom()
        );

        return viewRect.contains(
                view.getLeft() + (int) event.getX(),
                view.getTop() + (int) event.getY()
        );
    }
}

答案 4 :(得分:2)

前2个答案很好,除非视图位于滚动视图内:当您移动手指进行滚动时,它仍然会注册为触摸事件,但不会注册为MotionEvent.ACTION_MOVE事件。所以要改进答案(仅当你的视图在一个scroll元素中时才需要):

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    } else if(rect != null && !rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
        // User moved outside bounds
    }
    return false;
}

我在Android 4.3和Android 4.4上测试了这个

我没有注意到莫里茨的答案与前2名之间存在任何差异,但这也适用于他的答案:

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    } else if (rect != null){
        v.getHitRect(rect);
        if(rect.contains(
                Math.round(v.getX() + event.getX()),
                Math.round(v.getY() + event.getY()))) {
            // inside
        } else {
            // outside
        }
    }
    return false;
}

答案 5 :(得分:1)

虽然@FrostRocket的答案是正确的,但你应该使用view.getX()和Y来解释翻译的变化:

 view.getHitRect(viewRect);
 if(viewRect.contains(
         Math.round(view.getX() + event.getX()),
         Math.round(view.getY() + event.getY()))) {
   // inside
 } else {
   // outside
 }

答案 6 :(得分:1)

以下是View.OnTouchListener,您可以使用MotionEvent.ACTION_UP查看当用户的手指在视图之外时是否发送了private OnTouchListener mOnTouchListener = new View.OnTouchListener() { private Rect rect; @Override public boolean onTouch(View v, MotionEvent event) { if (v == null) return true; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); return true; case MotionEvent.ACTION_UP: if (rect != null && !rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { // The motion event was outside of the view, handle this as a non-click event return true; } // The view was clicked. // TODO: do stuff return true; default: return true; } } };

{{1}}

答案 7 :(得分:1)

view.setClickable(true);
view.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (!v.isPressed()) {
            Log.e("onTouch", "Moved outside view!");
        }
        return false;
    }
});

view.isPressed使用view.pointInView并包含一些触摸污点。如果您不想要slop,只需从内部view.pointInView复制逻辑(这是公开的,但是隐藏,因此它不是官方API的一部分,并且可能随时消失)。

view.setClickable(true);
view.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            v.setTag(true);
        } else {
            boolean pointInView = event.getX() >= 0 && event.getY() >= 0
                    && event.getX() < (getRight() - getLeft())
                    && event.getY() < (getBottom() - getTop());
            boolean eventInView = ((boolean) v.getTag()) && pointInView;
            Log.e("onTouch", String.format("Dragging currently in view? %b", pointInView));
            Log.e("onTouch", String.format("Dragging always in view? %b", eventInView));
            v.setTag(eventInView);
        }
        return false;
    }
});

答案 8 :(得分:1)

可重复使用的Kotlin解决方案

我从两个自定义扩展功能开始:

val MotionEvent.up get() = action == MotionEvent.ACTION_UP

fun MotionEvent.isIn(view: View): Boolean {
    val rect = Rect(view.left, view.top, view.right, view.bottom)
    return rect.contains((view.left + x).toInt(), (view.top + y).toInt())
}

然后聆听视图上的触摸。仅当ACTION_DOWN最初在视图中时才会触发。当您松开手指时,它将检查它是否仍在视图中。

myView.setOnTouchListener { view, motionEvent ->
    if (motionEvent.up && !motionEvent.isIn(view)) {
        // Talk your action here
    }
    false
}