80

在我的应用程序中,我需要同时处理移动和点击事件。

点击是一个 ACTION_DOWN 动作、几个 ACTION_MOVE 动作和一个 ACTION_UP 动作的序列。理论上,如果您收到一个 ACTION_DOWN 事件,然后是一个 ACTION_UP 事件 - 这意味着用户刚刚单击了您的视图。

但实际上,此序列不适用于某些设备。在我的三星 Galaxy Gio 上,只需单击我的视图就会得到这样的序列:ACTION_DOWN,几次 ACTION_MOVE,然后是 ACTION_UP。即我得到了一些带有 ACTION_MOVE 动作代码的意外 OnTouchEvent 触发。我从来没有(或几乎从来没有)得到序列 ACTION_DOWN -> ACTION_UP。

我也不能使用 OnClickListener 因为它没有给出点击的位置。那么如何检测点击事件并将其与移动区别开来呢?

4

10 回答 10

119

这是另一个非常简单的解决方案,不需要您担心手指被移动。如果您将点击作为简单的移动距离,那么您如何区分点击和长点击。

您可以在其中加入更多智能并包括移动的距离,但我还没有遇到一个实例,当用户可以在 200 毫秒内移动的距离应该构成移动而不是点击时。

setOnTouchListener(new OnTouchListener() {
    private static final int MAX_CLICK_DURATION = 200;
    private long startClickTime;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                startClickTime = Calendar.getInstance().getTimeInMillis();
                break;
            }
            case MotionEvent.ACTION_UP: {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                if(clickDuration < MAX_CLICK_DURATION) {
                    //click event has occurred
                }
            }
        }
        return true;
    }
});
于 2013-04-03T22:30:11.117 回答
58

通过考虑以下因素,我得到了最好的结果:

  1. 主要是事件之间移动的距离。我想以与密度无关的像素而不是像素为单位指定最大允许距离,以更好地支持不同的屏幕。例如,15 DPACTION_DOWNACTION_UP
  2. 其次,事件之间的持续时间。一秒钟似乎很好。(有些人非常“彻底”地“点击”,即缓慢;我仍然想认识到这一点。)

例子:

/**
 * Max allowed duration for a "click", in milliseconds.
 */
private static final int MAX_CLICK_DURATION = 1000;

/**
 * Max allowed distance to move during a "click", in DP.
 */
private static final int MAX_CLICK_DISTANCE = 15;

private long pressStartTime;
private float pressedX;
private float pressedY;

@Override
public boolean onTouchEvent(MotionEvent e) {
     switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            pressStartTime = System.currentTimeMillis();                
            pressedX = e.getX();
            pressedY = e.getY();
            break;
        }
        case MotionEvent.ACTION_UP: {
            long pressDuration = System.currentTimeMillis() - pressStartTime;
            if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, e.getX(), e.getY()) < MAX_CLICK_DISTANCE) {
                // Click event has occurred
            }
        }     
    }
}

private static float distance(float x1, float y1, float x2, float y2) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
    return pxToDp(distanceInPx);
}

private static float pxToDp(float px) {
    return px / getResources().getDisplayMetrics().density;
}

这里的想法与Gem 的解决方案相同,但有以下区别:

更新(2015 年):还可以查看Gabriel 的微调版本

于 2013-11-05T12:18:39.683 回答
32

在Jonik的带领下,我构建了一个稍微微调的版本,如果您移动手指然后在松开之前返回到该位置,它不会注册为点击:

所以这是我的解决方案:

/**
 * Max allowed duration for a "click", in milliseconds.
 */
private static final int MAX_CLICK_DURATION = 1000;

/**
 * Max allowed distance to move during a "click", in DP.
 */
private static final int MAX_CLICK_DISTANCE = 15;

private long pressStartTime;
private float pressedX;
private float pressedY;
private boolean stayedWithinClickDistance;

@Override
public boolean onTouchEvent(MotionEvent e) {
     switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            pressStartTime = System.currentTimeMillis();                
            pressedX = e.getX();
            pressedY = e.getY();
            stayedWithinClickDistance = true;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            if (stayedWithinClickDistance && distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) {
                stayedWithinClickDistance = false;
            }
            break;
        }     
        case MotionEvent.ACTION_UP: {
            long pressDuration = System.currentTimeMillis() - pressStartTime;
            if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) {
                // Click event has occurred
            }
        }     
    }
}

private static float distance(float x1, float y1, float x2, float y2) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
    return pxToDp(distanceInPx);
}

private static float pxToDp(float px) {
    return px / getResources().getDisplayMetrics().density;
}
于 2015-04-29T02:08:04.737 回答
25

使用探测器,有效,拖拽不升

场地:

private GestureDetector mTapDetector;

初始化:

mTapDetector = new GestureDetector(context,new GestureTap());

内部类:

class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onDoubleTap(MotionEvent e) {

        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // TODO: handle tap here
        return true;
    }
}

触摸:

@Override
public boolean onTouch(View v, MotionEvent event) {
    mTapDetector.onTouchEvent(event);
    return true;
}

享受 :)

于 2016-08-04T13:50:07.150 回答
6

为了获得对点击事件的最优化识别,我们必须考虑两件事:

  1. ACTION_DOWN 和 ACTION_UP 之间的时间差。
  2. 用户触摸和松开手指时 x、y 之间的差异。

实际上我结合了 Stimsoni 和 Neethirajan 给出的逻辑

所以这是我的解决方案:

        view.setOnTouchListener(new OnTouchListener() {

        private final int MAX_CLICK_DURATION = 400;
        private final int MAX_CLICK_DISTANCE = 5;
        private long startClickTime;
        private float x1;
        private float y1;
        private float x2;
        private float y2;
        private float dx;
        private float dy;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            // TODO Auto-generated method stub

                    switch (event.getAction()) 
                    {
                        case MotionEvent.ACTION_DOWN: 
                        {
                            startClickTime = Calendar.getInstance().getTimeInMillis();
                            x1 = event.getX();
                            y1 = event.getY();
                            break;
                        }
                        case MotionEvent.ACTION_UP: 
                        {
                            long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                            x2 = event.getX();
                            y2 = event.getY();
                            dx = x2-x1;
                            dy = y2-y1;

                            if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) 
                                Log.v("","On Item Clicked:: ");

                        }
                    }

            return  false;
        }
    });
于 2013-07-28T19:12:42.320 回答
6

使用 Gil SH 答案,我通过实施onSingleTapUp()而不是onSingleTapConfirmed(). 如果拖动/移动,它会更快,并且不会单击视图。

手势点击:

public class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        button.performClick();
        return true;
    }
}

像这样使用它:

final GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureTap());
button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        gestureDetector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_UP:
                return true;
            case MotionEvent.ACTION_MOVE:
                return true;
        }
        return false;
    }
});
于 2017-01-08T23:53:34.103 回答
4

下面的代码将解决您的问题

    @覆盖
        公共布尔 onTouchEvent(MotionEvent 事件){
            开关(事件.getAction()){
                案例(MotionEvent.ACTION_DOWN):
                    x1 = event.getX();
                    y1 = event.getY();
                    休息;
                案例(MotionEvent.ACTION_UP):{
                    x2 = event.getX();
                    y2 = event.getY();
                    dx = x2-x1;
                    dy = y2-y1;

                如果(数学.abs(dx)>数学.abs(dy))
                {
                    如果(dx>0)移动(1);//正确的
                    否则 if (dx == 0) move(5); //点击
                    否则移动(2);//剩下
                }
                别的
                {
                    如果(dy>0)移动(3);// 下
                    否则 if (dy == 0) move(5); //点击
                    否则移动(4);//向上
                }
                }
            }
            返回真;
        }

于 2013-02-20T14:39:16.510 回答
3

在没有 ACTION_MOVE 发生的情况下发生 ACTION_DOWN 是非常困难的。手指在屏幕上与第一次触摸发生的位置不同的位置稍稍抽动就会触发 MOVE 事件。另外,我相信手指压力的变化也会触发 MOVE 事件。我会在 Action_Move 方法中使用 if 语句来尝试确定移动发生与原始 DOWN 运动的距离。如果移动发生在某个设定的半径之外,你的 MOVE 动作就会发生。这可能不是最好的、资源有效的方式来做你尝试的事情,但它应该可以工作。

于 2012-04-01T15:46:06.953 回答
2

添加到上述答案中,如果您想同时实现 onClick 和 Drag 操作,那么我下面的代码可以。从@Stimsoni 获得一些帮助:

     // assumed all the variables are declared globally; 

    public boolean onTouch(View view, MotionEvent event) {

      int MAX_CLICK_DURATION = 400;
      int MAX_CLICK_DISTANCE = 5;


        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN: {
                long clickDuration1 = Calendar.getInstance().getTimeInMillis() - startClickTime;


                    startClickTime = Calendar.getInstance().getTimeInMillis();
                    x1 = event.getX();
                    y1 = event.getY();


                    break;

            }
            case MotionEvent.ACTION_UP:
            {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                x2 = event.getX();
                y2 = event.getY();
                dx = x2-x1;
                dy = y2-y1;

                if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
                    Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
                    Log.d("clicked", "On Item Clicked:: ");

               //    imageClickAction((ImageView) view,rl);
                }

            }
            case MotionEvent.ACTION_MOVE:

                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                x2 = event.getX();
                y2 = event.getY();
                dx = x2-x1;
                dy = y2-y1;

                if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
                    //Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
                  //  Log.d("clicked", "On Item Clicked:: ");

                    //    imageClickAction((ImageView) view,rl);
                }
                else {
                    ClipData clipData = ClipData.newPlainText("", "");
                    View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);

                    //Toast.makeText(getApplicationContext(), "item dragged", Toast.LENGTH_SHORT).show();
                    view.startDrag(clipData, shadowBuilder, view, 0);
                }
                break;
        }

        return  false;
    }
于 2016-12-02T06:11:32.873 回答
1

如果您只想对点击做出反应,请使用:

if (event.getAction() == MotionEvent.ACTION_UP) {

}
于 2016-05-11T10:27:01.700 回答