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

Android:检测用户是否触摸并拖出按钮区域?,android,button,gestures,Android,Button,Gestures,在Android系统中,我们如何检测用户是否触摸了按钮并将其拖出该按钮的区域?检查MotionEvent.MOVE\u 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

在Android系统中,我们如何检测用户是否触摸了按钮并将其拖出该按钮的区域?

检查MotionEvent.MOVE\u 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;
}
注意:如果你想以安卓4.0为目标,一个全新的可能性世界将开启:

在我的情况下,Enterco发布的答案需要稍微调整一下。我必须替换:

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


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

虽然@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
 }

这是一个
视图。OnTouchListener
,您可以使用它查看当用户的手指在视图之外时是否发送了
MotionEvent.ACTION\u 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;
        }
    }
};

前2个答案很好,除非视图位于scrollview中:当由于移动手指而进行滚动时,它仍然注册为触摸事件,但不注册为MotionEvent.ACTION\u move事件。因此,要改进答案(仅当您的视图位于滚动元素内时才需要):

我在安卓4.3和安卓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;
}

我在OnTouch中添加了一些日志记录,发现有人点击了
MotionEvent.ACTION\u CANCEL
。这对我来说已经足够好了

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,只需从internal
视图.pointInView
复制逻辑即可(该视图是公共的,但隐藏的,因此它不是官方API的一部分,可能随时消失)

view.setClickable(true);
view.setOnTouchListener(新的OnTouchListener(){
@凌驾
公共布尔onTouch(视图v,运动事件){
if(event.getAction()==MotionEvent.ACTION\u向下){
v、 setTag(真);
}否则{
布尔点视图=event.getX()>=0&&event.getY()>=0
&&event.getX()<(getRight()-getLeft())
&&event.getY()<(getBottom()-getTop());
布尔eventInView=((布尔)v.getTag())&&pointInView;
Log.e(“onTouch”,String.format(“当前正在视图中拖动?%b”,pointInView));
Log.e(“onTouch”,String.format(“始终在视图中拖动?%b”,eventInView));
v、 setTag(eventInView);
}
返回false;
}
});

我遇到了与OP相同的问题,我想知道(1)某个特定的
视图
何时被触碰,以及(2a)在
视图
上释放触碰,或者(2b)当触碰移动到
视图
的边界之外时。我在这个线程中汇集了各种答案,创建了一个简单的
View.OnTouchListener
(名为
SimpleTouchListener
)扩展,这样其他人就不必摆弄
MotionEvent
对象了。这个类的源代码可以在这个答案的底部找到

要使用此类,只需将其设置为
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()
        );
    }
}
可重用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
}

对不起,它坏了。我只能捕捉动作上升、动作下降和动作移动,但不能捕捉动作外部。从API级别3开始,它就可以使用:根据这个答案()我相信我们只能使用动作外部来检测触摸是否在活动外部,不是视图。有关rect.contains测试的更新,请参阅FrostRocket的答案,以使其适用于未定位在原点的单个视图。我相信,如果要在外部获取
ACTION\u
,则需要使用
getActionMasked()
。或者您可以比较掩码:
if((event.getAction()&MotionEvent.ACTION\u OUTSIDE)==MotionEvent.ACTION\u OUTSIDE)
。这是因为
ACTION\u OUTSIDE
不会在没有其他操作的情况下触发,因此它将被屏蔽。+1谢谢,这是一种更通用的方法,适用于自定义视图实现。我刚刚在一个按钮上实现了此代码,该按钮包含在RecyclerView的一个项目中,效果非常好。我只是想说,如果你在getHitRect之前添加新的Rect,你可以在ACTION_DOWN上去掉它。Rect Rect=新的Rect();getHitRect(rect);如果您的
视图
包含在父
视图组
中(如
ScrollView
),则您将获得一个
MotionEvent.ACTION\u CANCEL
回调,该父视图组有兴趣从其子视图中窃取移动触动,但不能保证您在视图中获得
MotionEvent.ACTION\u CANCEL
回调
查看
。对我来说唯一可行的解决方案,谢谢。这是一个很好的答案,特别是对于处理问题的人来说,当操作取消时,不会触发它。是的,这是一个很好的答案!
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()
        );
    }
}
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())
}
myView.setOnTouchListener { view, motionEvent ->
    if (motionEvent.up && !motionEvent.isIn(view)) {
        // Talk your action here
    }
    false
}