如何在onTouchEvent()中区分移动和单击



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

单击是一个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,因为它没有给出点击的位置。那么,我如何检测点击事件并将其与移动区别开来呢?

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

你可以在这方面投入更多的智慧,并包括移动的距离,但我还没有遇到一个例子,当用户在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;
    }
});

我通过考虑获得了最好的结果

  1. 主要是,距离ACTION_DOWNACTION_UP事件之间移动。我想指定最大允许距离的密度独立像素,而不是像素,以更好地支持不同的屏幕。例如,15 DP
  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的解决方案相同,但有以下区别:

  • 这将计算两个点之间的实际欧几里得距离
  • 这使用dp而不是px

更新(2015):也可以看看加布里埃尔的微调版本。

这是我的解决方案:

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

使用检测器,它可以工作,在拖动的情况下不会上升

字段:

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

onTouch:

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

享受:)

要获得点击事件的最佳识别,我们必须考虑两件事:

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

使用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;
    }
});

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

@Override公共布尔值onTouchEvent(MotionEvent事件){开关(event.getAction()){case(MotionEvent.ACTION_DOWN):x1=event.getX();y1=event.getY();打破case(MotionEvent.ACTION_UP):{x2=event.getX();y2=event.getY();dx=x2-x1;dy=y2-y1;if(Math.abs(dx)>Math.abs{如果(dx>0)移动(1)//正确的否则,如果(dx==0)移动(5)//点击否则移动(2)//左边}其他的{如果(dy>0)移动(3);//向下否则,如果(dy==0)移动(5)//点击否则移动(4)//向上的}}}返回true;}

如果没有ACTION_MOVE,ACTION_DOWN很难发生。你的手指在屏幕上与第一次触摸发生的地方不同的地方最轻微的抽动都会触发MOVE事件。此外,我相信手指压力的变化也会触发MOVE事件。我会在Action_Move方法中使用if语句来尝试确定移动与原始DOWN运动之间的距离。如果移动发生在某个设定的半径之外,则会发生move操作。这可能不是最好的、资源高效的方式来做你尝试的事情,但它应该有效。

除了上面的答案之外,如果你想同时实现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;
    }

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

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

您可以将GestureDetectorsetOnTouchListener结合使用,而不是基于距离/时间差异的方法。GestureDetector将检测点击,而您可以将OnTouchListener用于其他基于触摸的事件,例如检测拖动。

这里有一个示例代码供参考:

Class MyCustomView() {
    fun addClickAndTouchListener() {
        val gestureDetector = GestureDetector(
            context,
            object : GestureDetector.SimpleOnGestureListener() {
                override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
                    // Add your onclick logic here
                    return true
                }
            }
        )
        setOnTouchListener { view, event ->
            when {
                gestureDetector.onTouchEvent(event) -> {
                    // Your onclick logic would be triggered through SimpleOnGestureListener
                    return@setOnTouchListener true
                }
                event.action == MotionEvent.ACTION_DOWN -> {
                    // Handle touch event
                    return@setOnTouchListener true
                }
                event.action == MotionEvent.ACTION_MOVE -> {
                    // Handle drag
                    return@setOnTouchListener true
                }
                event.action == MotionEvent.ACTION_UP -> {
                    // Handle Drag over
                    return@setOnTouchListener true
                }
                else -> return@setOnTouchListener false
            }
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新