不能同时处理点击和触摸事件

Posted

技术标签:

【中文标题】不能同时处理点击和触摸事件【英文标题】:Can't handle both click and touch events simultaneously 【发布时间】:2011-07-06 18:32:54 【问题描述】:

我正在尝试处理按钮上的触摸事件和单击事件。我执行以下操作:

button.setOnClickListener(clickListener);
button.setOnTouchListener(touchListener);

当注册任何一个监听器时,一切正常,但当我尝试同时使用它们时,只会触发触摸事件。任何解决方法?我做错了什么?

【问题讨论】:

【参考方案1】:

这有点棘手。

如果您设置了onTouchListener,则需要在ACTION_DOWN 中返回true,以告诉系统我已经消费了该事件并且它不会传播到其他侦听器。

但是OnClickListener 不会被解雇。

所以你可能会想,我会在那里做我的事并返回false,这样我也可以获得点击。 如果你这样做,它会起作用,但你不会订阅其他即将到来的触摸事件(ACTION_MOVEACTION_UP) 因此,唯一的选择是在那里返回true,但是你不会像我们之前所说的那样收到任何点击事件。

所以你需要在ACTION_UPview.performClick()中手动执行点击

这会起作用。

【讨论】:

这里的关键部分是“您需要在ACTION_UP中手动执行点击”,谢谢先生 这确实是最好的答案。在我的 ACTION_MOVE 中,如果满足某个 deltaX 阈值,我将开始拦截触摸事件。如果我达到 ACTION_UP 并且从未达到 deltaX 阈值,我会触发 listView.performClick(),否则,我会执行滑动操作。 这显然应该是答案。 很好的答案。坚持下去【参考方案2】:

ClickListenerTouchListener 之间存在细微但非常重要的区别。 TouchListener 在视图可以响应事件之前执行。 ClickListener 只有在视图处理完事件后才会收到它的事件。

因此,当您触摸屏幕时,首先执行 TouchListener,当您为您的事件返回 true 时,ClickListener 将永远不会得到它。但是如果你按下设备的轨迹球,ClickListener 应该会被触发,因为TouchListener 不会响应它。

【讨论】:

【参考方案3】:

感谢@urSus 的精彩回答 但在那种情况下,每次触摸都会执行点击,即使是 ACTION_MOVE 假设您想将 move 事件和 click 事件分开,您可以使用一个小技巧 定义一个boolean 字段并像这样使用:

 @Override
        public boolean onTouch(View view, MotionEvent motionEvent)
        
            switch (motionEvent.getAction() & MotionEvent.ACTION_MASK)
            
                case MotionEvent.ACTION_DOWN:
                    shouldClick = true;
                    .
                    .
                    break;
                case MotionEvent.ACTION_UP:
                    if (shouldClick)
                        view.performClick();
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    break;
                case MotionEvent.ACTION_MOVE:
                    //Do your stuff
                    shouldClick = false;
                    break;
            
            rootLayout.invalidate();
            return true;
        

【讨论】:

很好的答案。坚持下去 执行点击是查看方式吗?或者我们必须执行点击的自定义方法 @user3475052 performClick() 是每个视图及其子类方法 有效,如果 return true; 在 MotionEvent.ACTION_DOWN 分支中,移除 MotionEvent.ACTION_MOVE 分支,在 MotionEvent.ACTION_UP 分支中获取运动差异。见***.com/questions/17831395/… 真的这个答案在所有类似问题中都是最好的【参考方案4】:

您应该在 OnTouchListener 中返回 false,然后您的 OnClickListener 也将被处理。

【讨论】:

【参考方案5】:

我想你在OnTouchListener 中返回true?这将消耗事件,因此它不会被发送到任何进一步的处理。

附带说明 - 同时拥有点击和触摸侦听器有什么意义?

【讨论】:

客户端希望在按下和释放按钮时播放声音,我在“ACTION_UP”和“ACTION_DOWN”事件中这样做。点击事件执行逻辑。现在我必须处理触摸事件本身的逻辑。 一个可以左右拖动,完成后点击的按钮?【参考方案6】:

以上所有答案都是说我们不能同时处理setOnTouchListenersetOnClickListener。 但是,我看到我们可以通过return falsesetOnTouchListener处理两者

示例

override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    button = findViewById(R.id.button)
    button.setOnClickListener 
        Log.i("TAG", "onClick")
    

    button.setOnTouchListener  v, event ->
        Log.i("TAG", "onTouch " + event.action)
        false
    

当我点击Button时,logcat会显示如下

I/TAG: onTouch 0
I/TAG: onTouch 1
I/TAG: onClick

【讨论】:

从onTouch返回false等于根本不设置onTouchListener。【参考方案7】:

这是一个不影响 ClickListener 的 TouchListener 示例。

import android.graphics.PointF
import android.view.MotionEvent
import android.view.MotionEvent.*
import android.view.View
import kotlin.math.abs

object TouchAndClickListener : View.OnTouchListener 

    /** Those are factors you can change as you prefer */
    private const val touchMoveFactor = 10
    private const val touchTimeFactor = 200


    private var actionDownPoint = PointF(0f, 0f)
    private var previousPoint = PointF(0f, 0f)
    private var touchDownTime = 0L

    override fun onTouch(v: View, event: MotionEvent) = when (event.action) 
        ACTION_DOWN -> PointF(event.x, event.y).let 

            actionDownPoint = it  // Store it to compare position when ACTION_UP
            previousPoint = it  // Store it to compare position when ACTION_MOVE
            touchDownTime = now() // Store it to compare time when ACTION_UP

            /* Do other stuff related to ACTION_DOWN you may whant here */

            true
        

        ACTION_UP -> PointF(event.x, event.y).let 

            val isTouchDuration = now() - touchDownTime < touchTimeFactor  // short time should mean this is a click
            val isTouchLength = abs(it.x - actionDownPoint.x) + abs(it.y - actionDownPoint.y) < touchMoveFactor  // short length should mean this is a click

            val shouldClick = isTouchLength && isTouchDuration  // Check both

            if (shouldClick) yourView.performClick() //Previously define setOnClickListener  on yourView, then performClick() will call it

            /* Do other stuff related to ACTION_UP you may whant here */

            true
        

        ACTION_MOVE -> PointF(event.x, event.y).let 

            /* Do other stuff related to ACTION_MOVE you may whant here */

            previousPoint = it
            true
        

        else -> false // Nothing particular with other event
    

    private fun now() = System.currentTimeMillis()

【讨论】:

很好的答案,但请它应该在 java 而不是 kotlin 中【参考方案8】:

感谢@Nicolas Duponchel,这就是我实现 onClick 和 onTouch 事件的方式

  //Define these globally e.g in your MainActivity class
  private short touchMoveFactor = 10;
  private short touchTimeFactor = 200;
  private PointF actionDownPoint = new PointF(0f, 0f);
  private long touchDownTime = 0L;
            
             @Override
                public boolean onTouchEvent(MotionEvent event) 
                    final int action = event.getAction();
                    switch (action) 
                     case MotionEvent.ACTION_DOWN: 
                            actionDownPoint.x = event.getX();
                            actionDownPoint.y = event.getY();
                            touchDownTime = System.currentTimeMillis();
                            break;
                            
                     case MotionEvent.ACTION_UP: 
                            //on touch released, check if the finger was still close to the point he/she clicked 
                            boolean isTouchLength = (Math.abs(event.getX() - actionDownPoint.x)+ Math.abs(event.getY() - actionDownPoint.y)) < touchMoveFactor;
                            boolean isClickTime = System.currentTimeMillis() - touchDownTime < touchTimeFactor;
        
                            //if it's been more than 200ms since user first touched and, the finger was close to the same place when released, consider it a click event
                            //Please note that this is a workaround :D
                            if (isTouchLength && isClickTime) 
                               //call this method on the view, e.g ivPic.performClick();
                               performClick();
                            break;
                            
                     
                

【讨论】:

【参考方案9】:
button.setOnTouchListener(this);

实现接口和代码在这里:

@Override
public boolean onTouch(View view, MotionEvent motionEvent) 
    switch (view.getId()) 
        case R.id.send:
            switch(motionEvent.getAction())
                case MotionEvent.ACTION_DOWN:
                    //when the user has pressed the button
                    //do the needful here
                    break;
                case MotionEvent.ACTION_UP:
                    //when the user releases the button
                    //do the needful here
                    break;
            
            break;
    
    return false;

【讨论】:

【参考方案10】:

要使这两个事件在 gridview 中成为可能,只能通过如下方式使触摸侦听器返回“false”,这对我有用。

**GridView myView = findViewById(R.id.grid_view);
myView.setOnTouchListener(new OnTouchListener() 
    public boolean onTouch(View v, MotionEvent event) 
        // ... Respond to touch events
        return false;
    
);**

这样两个事件都可以实现

【讨论】:

【参考方案11】:
## Exact working solution for both click action and touch listener(dragging) ##

private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
private float CLICK_ACTION_THRESHOLD = 0.5f;
private float startX;
private float startY;

 @Override
public boolean onTouch(View view, MotionEvent event) 
    switch (view.getId()) 
        case R.id.chat_head_profile_iv:
            switch (event.getAction()) 
                case MotionEvent.ACTION_DOWN:
                    //remember the initial position.
                    initialX = params.x;
                    initialY = params.y;
                    startX = event.getX();
                    startY = event.getY();
                    //get the touch location
                    initialTouchX = event.getRawX();
                    initialTouchY = event.getRawY();
                    return true;
                case MotionEvent.ACTION_UP:
                    float endX = event.getX();
                    float endY = event.getY();
                    if (shouldClickActionWork(startX, endX, startY, endY)) 
                        openScreen();// WE HAVE A CLICK!!
                    
                    return true;
                case MotionEvent.ACTION_MOVE:
                    //Calculate the X and Y coordinates of the view.
                    params.x = initialX + (int) (event.getRawX() - initialTouchX);
                    params.y = initialY + (int) (event.getRawY() - initialTouchY);

                    //Update the layout with new X & Y coordinate
                    mWindowManager.updateViewLayout(mChatHeadView, params);
                    return true;
            
            break;
    
    return true;


private boolean shouldClickActionWork(float startX, float endX, float startY, float endY) 
    float differenceX = Math.abs(startX - endX);
    float differenceY = Math.abs(startY - endY);
    if ((CLICK_ACTION_THRESHOLD > differenceX) && (CLICK_ACTION_THRESHOLD > differenceY))
        return true;
    else
        return false;

【讨论】:

什么是参数?【参考方案12】:

ACTION_UP 中使用条件手动执行onClick

boolean shouldClick = event.eventTime - event.downTime <= 200 // compares the time with some threshold

所以,试试MotionEvent.ACTION_UP

if(event.eventTime - event.downTime <= 200)  // case or when statement of action Touch listener
    view.performClick();

【讨论】:

【参考方案13】:

我知道为时已晚,但如果有人为此苦苦寻找干净的解决方案,那就是。

这些用于测量触摸和移开手指之间的时间。

    private long clickTime = 0;
    public static final long CLICK_TIMEOUT = 200; // 200ms

这是我的onTouchListner。像魅力一样工作

    private final View.OnTouchListener onTouchListener = (v, event) -> 
    if (event.getAction() == MotionEvent.ACTION_DOWN) 
        clickTime = System.currentTimeMillis();
        return true;
     else if(event.getAction() ==  MotionEvent.ACTION_UP) 
        if(System.currentTimeMillis()-clickTime < Constants.CLICK_TIMEOUT)
        
            Toast.makeText(getContext(), "clicked", Toast.LENGTH_SHORT).show();
            return true;
        
        return false;
    
    else if(event.getAction() == MotionEvent.ACTION_MOVE)
        if(System.currentTimeMillis()-clickTime > Constants.CLICK_TIMEOUT)
        
            ClipData data = ClipData.newPlainText("" , "");
            View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v);
            v.startDrag(data , shadowBuilder , v , 0);
            return false;
        
        return false;
    
    return false;
;

【讨论】:

以上是关于不能同时处理点击和触摸事件的主要内容,如果未能解决你的问题,请参考以下文章

在允许孩子捕捉触摸的同时防止父母被点击

cocos判断整个场景是不是有点击触摸事件

在 Android 中的多个视图上同时处理触摸事件

iOS触摸事件处理详解

响应者链条,如何获取最佳的点击view 以及内部实现

iOS之事件的传递和响应机制-原理篇