无法同时处理点击和触摸事件

时间:2011-03-01 19:32:02

标签: android listeners ontouchlistener

我正在尝试处理触摸事件并单击按钮上的事件。我做了以下事情:

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

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

13 个答案:

答案 0 :(得分:66)

它有点棘手。

如果设置onTouchListener,则需要在true中返回ACTION_DOWN,告诉系统我已经消耗了该事件,并且不会向其他侦听器发送消息。

但是OnClickListener不会被解雇。

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

因此,您需要使用ACTION_UP

view.performClick()中手动执行点击

这样可行。

答案 1 :(得分:39)

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

因此,当您触摸屏幕时,TouchListener会先执行,当您为活动返回true时,ClickListener将永远无法获取。但是,如果您按下设备的轨迹球,则ClickListener应该被触发,因为TouchListener不会响应它。

答案 2 :(得分:16)

感谢@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;
        }

答案 3 :(得分:5)

我想你在true回复了OnTouchListener?这将消耗该事件,因此不会被发送以进行任何进一步处理。

侧面说明 - 同时拥有点击和触摸监听器有什么意义?

答案 4 :(得分:5)

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

答案 5 :(得分:1)

以上所有答案均表示我们不能同时处理setOnTouchListenersetOnClickListener
但是,我看到我们可以通过return false

中的setOnTouchListener处理

示例

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

答案 6 :(得分:1)

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

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: {
            Log.d(TAG, "onTouch: _____________ACTION_DOWN");
                    actionDownPoint.x = event.getX();
                    actionDownPoint.y = event.getY();
                    touchDownTime = System.currentTimeMillis();
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    Log.d(TAG, "onTouch: _____________ACTION_UP");
           //check if the user's finger is still close to the point he/she clicked 
                        boolean isTouchLength = (Math.abs(event.getX() - actionDownPoint.x)
                                + Math.abs(event.getY() - actionDownPoint.y)) < touchMoveFactor;
        //check if it's not been more than @touchTimeFactor=200 ms
                        boolean isClickTime = System.currentTimeMillis() - touchDownTime < touchTimeFactor;
                             if (isTouchLength && isClickTime) performClick();
    //ACTION_UP logic goes below this line
     .....
     .....

                    break;
                }

                case MotionEvent.ACTION_MOVE: {
//move method should not work if the click was too short i.e click time +100 ms
                if (System.currentTimeMillis() - touchDownTime < touchTimeFactor+100) return true;
                Log.d(TAG, "onTouch: _____________ACTION_MOVE");

// move method logic goes below this line
  .....
  .....
    }}}

答案 7 :(得分:0)

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;
}

答案 8 :(得分:0)

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

ShowCardAction
以这种方式可以实现两个事件

答案 9 :(得分:0)

## 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;
}

答案 10 :(得分:0)

这是一个不会隐藏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()
}

答案 11 :(得分:0)

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();
}

答案 12 :(得分:0)

我知道为时已晚,但是如果有人正在为此苦苦挣扎以寻求干净的解决方案,那么就在这里。

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

    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;
};