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