使用Android检测长按


76

我目前正在使用

onTouchEvent(MotionEvent event){
}

检测用户何时按下我的glSurfaceView,有一种方法可以检测何时长按。我猜如果我在开发文档中找不到很多东西,那将是围绕方法的某种变通。例如注册ACTION_DOWN并查看ACTION_UP之前需要多长时间。

如何使用opengl-es在android上检测长按?

Answers:


112

试试这个:

final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
    public void onLongPress(MotionEvent e) {
        Log.e("", "Longpress detected");
    }
});

public boolean onTouchEvent(MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
};

手势检测器也可以处理标准点击吗?
杰克

1
这是最好的答案。
伊恩

25
注意:如果您已经拥有OnClickListener,则添加OnLongClickListener比使用GestureRecognizer要容易得多(可以避免某些特定于GR的问题)。cf stackoverflow.com/a/4402854/153422
Adam

9
请注意,您应该提供有关创建GestureDetector的上下文作为第一个参数。该示例已弃用。
Antzi 2013年

1
@Modge仅GestureDetector弃用了两个构造函数-您可以使用其余的构造函数。
Eido95 '16

157

GestureDetector是最好的解决方案。

这是一个有趣的选择。在每个ACTION_DOWN计划的onTouchEvent中,一个Runnable将在1秒内运行。在每个ACTION_UPACTION_MOVE上,取消计划的Runnable。如果在ACTION_DOWN事件中取消发生的时间少于1,则Runnable将不会运行。

final Handler handler = new Handler(); 
Runnable mLongPressed = new Runnable() { 
    public void run() { 
        Log.i("", "Long press!");
    }   
};

@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView){
    if(event.getAction() == MotionEvent.ACTION_DOWN)
        handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
    if((event.getAction() == MotionEvent.ACTION_MOVE)||(event.getAction() == MotionEvent.ACTION_UP))
        handler.removeCallbacks(mLongPressed);
    return super.onTouchEvent(event, mapView);
}

17
您可以通过致电来长按系统超时android.view.ViewConfiguration.getLongPressTimeout()
2014年

1
我不知道为什么,但是两种解决方案都不适合我。
Eido95 '16

2
ACTION_CANCEL也应被处理
基里尔Vashilo

2
谢谢@MSquare。这是一个好主意。请注意,对于我正在使用的设备,我只能响应A​​CTION_UP才能删除回调。这是因为它对移动非常敏感,并且我无法阻止ACTION_MOVE事件。还要注意,您的方法通过递归调用Runnable提供了一种自然的方式来执行重复操作。
stevehs17 '19

1
@stevehs您需要测试触摸倾斜度。

4

我有一个检测点击,长按和移动的代码。这完全是以上给出的答案与我从浏览每个文档页面所做的更改的组合。

//Declare this flag globally
boolean goneFlag = false;

//Put this into the class
final Handler handler = new Handler(); 
    Runnable mLongPressed = new Runnable() { 
        public void run() { 
            goneFlag = true;
            //Code for long click
        }   
    };

//onTouch code
@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {    
        case MotionEvent.ACTION_DOWN:
            handler.postDelayed(mLongPressed, 1000);
            //This is where my code for movement is initialized to get original location.
            break;
        case MotionEvent.ACTION_UP:
            handler.removeCallbacks(mLongPressed);
            if(Math.abs(event.getRawX() - initialTouchX) <= 2 && !goneFlag) {
                //Code for single click
                return false;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            handler.removeCallbacks(mLongPressed);
            //Code for movement here. This may include using a window manager to update the view
            break;
        }
        return true;
    }

我确认它已经在我自己的应用程序中使用过,因此可以正常工作。


我同意了这一点,但在调用handler.postDelayed之前,必须在ACTION_DOWN中将gotFlag设置为false,否则将第一次长按之后的每次单击都视为长按。
吉姆·吉姆森

是否initialTouchX在某处自动定义?我糊涂了。
Divya Mamgai

3

当您指用户按下时,是指单击吗?单击是指用户按下然后立即抬起手指。因此,它包含两个onTouch事件。您应该将onTouchEvent的使用保留给初次触摸或发布后发生的事情。

因此,如果是单击,则应使用onClickListener。

您的答案类似:使用onLongClickListener。


1
我可以只将onClickListener与GLSurfaceView一起使用吗?我的印象是,您只能使用带有按钮之类的UI小部件的onClickListener用户。可能是错误的。
杰克

啊我看到它冷静的人,但有什么办法让通过onClickListener点击的坐标
杰克

即使我将其包装在FrameLayout中,onClickListener似乎也不适用于GLSurfaceView。我尝试将侦听器设置为表面视图本身和框架布局,但没有成功= /
Jack Jack

这个问题似乎特定于GLSurfaceView,但是,一般而言,对于其他视图,OnLongClickListener是解决之道。
萨朗2014年

3

我创建了一个受实际“视图”源启发的代码段,该代码段可以自定义延迟来可靠地检测长按/按下。但这是在科特林:

val LONG_PRESS_DELAY = 500

val handler = Handler()
var boundaries: Rect? = null

var onTap = Runnable {
    handler.postDelayed(onLongPress, LONG_PRESS_DELAY - ViewConfiguration.getTapTimeout().toLong())
}

var onLongPress = Runnable {

    // Long Press
}

override fun onTouch(view: View, event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            boundaries = Rect(view.left, view.top, view.right, view.bottom)
            handler.postDelayed(onTap, ViewConfiguration.getTapTimeout().toLong())
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            handler.removeCallbacks(onLongPress)
            handler.removeCallbacks(onTap)
        }
        MotionEvent.ACTION_MOVE -> {
            if (!boundaries!!.contains(view.left + event.x.toInt(), view.top + event.y.toInt())) {
                handler.removeCallbacks(onLongPress)
                handler.removeCallbacks(onTap)
            }
        }
    }
    return true
}

1

MSquare的解决方案仅在您拥有特定像素时才有效,但是对于最终用户而言,这是不合理的期望,除非他们使用鼠标(他们不使用鼠标,而是使用手指)。

因此,我为DOWN和UP动作之间的距离添加了一些阈值,以防它们之间存在MOVE动作。

final Handler longPressHandler = new Handler();
Runnable longPressedRunnable = new Runnable() {
    public void run() {
        Log.e(TAG, "Long press detected in long press Handler!");
        isLongPressHandlerActivated = true;
    }
};

private boolean isLongPressHandlerActivated = false;

private boolean isActionMoveEventStored = false;
private float lastActionMoveEventBeforeUpX;
private float lastActionMoveEventBeforeUpY;

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        longPressHandler.postDelayed(longPressedRunnable, 1000);
    }
    if(event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
        if(!isActionMoveEventStored) {
            isActionMoveEventStored = true;
            lastActionMoveEventBeforeUpX = event.getX();
            lastActionMoveEventBeforeUpY = event.getY();
        } else {
            float currentX = event.getX();
            float currentY = event.getY();
            float firstX = lastActionMoveEventBeforeUpX;
            float firstY = lastActionMoveEventBeforeUpY;
            double distance = Math.sqrt(
                    (currentY - firstY) * (currentY - firstY) + ((currentX - firstX) * (currentX - firstX)));
            if(distance > 20) {
                longPressHandler.removeCallbacks(longPressedRunnable);
            }
        }
    }
    if(event.getAction() == MotionEvent.ACTION_UP) {
        isActionMoveEventStored = false;
        longPressHandler.removeCallbacks(longPressedRunnable);
        if(isLongPressHandlerActivated) {
            Log.d(TAG, "Long Press detected; halting propagation of motion event");
            isLongPressHandlerActivated = false;
            return false;
        }
    }
    return super.dispatchTouchEvent(event);
}

一个对我有用的简单方法就是忽略ACTION_MOVE。
stevehs17 '19

嗯,我什至不记得是我跟踪“如果长按屏幕上的任何位置,那么”的方式
EpicPandaForce

1

这个想法是Runnable在将来创建一个用于执行的长单击,但是由于单击或移动,该执行可以被取消。

您还需要知道何时消耗了长按以及何时由于手指移动过多而取消了该单击。我们使用initialTouchXinitialTouchY来检查用户是否退出10像素(每侧5像素)的正方形区域。

这里是我的委托完整的代码的Click&LongClickCellListViewActivityOnTouchListener

    ClickDelegate delegate; 
    boolean goneFlag = false;
    float initialTouchX;
    float initialTouchY;
    final Handler handler = new Handler();
    Runnable mLongPressed = new Runnable() {
        public void run() {
            Log.i("TOUCH_EVENT", "Long press!");
            if (delegate != null) {
                goneFlag = delegate.onItemLongClick(index);
            } else {
                goneFlag = true;
            }
        }
    };

    @OnTouch({R.id.layout})
    public boolean onTouch (View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
                initialTouchX = motionEvent.getRawX();
                initialTouchY = motionEvent.getRawY();
                return true;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_CANCEL:
                if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
                    handler.removeCallbacks(mLongPressed);
                    return true;
                }
                return false;
            case MotionEvent.ACTION_UP:
                handler.removeCallbacks(mLongPressed);
                if (goneFlag || Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
                    goneFlag = false;
                    return true;
                }
                break;
        }
        Log.i("TOUCH_EVENT", "Short press!");
        if (delegate != null) {
            if (delegate.onItemClick(index)) {
                return false;
            }
        }
        return false;
    }

ClickDelegateinterface用于发送点击事件处理程序类像Activity

    public interface ClickDelegate {
        boolean onItemClick(int position);
        boolean onItemLongClick(int position);
    }

如果需要委派行为,那么您所需要做的就是在您Activity或父母中实现它View

public class MyActivity extends Activity implements ClickDelegate {

    //code...
    //in some place of you code like onCreate, 
    //you need to set the delegate like this:
    SomeArrayAdapter.delegate = this;
    //or:
    SomeViewHolder.delegate = this;
    //or:
    SomeCustomView.delegate = this;

    @Override
    public boolean onItemClick(int position) {
        Object obj = list.get(position);
        if (obj) {
            return true; //if you handle click
        } else {
            return false; //if not, it could be another event
        }
    }

    @Override
    public boolean onItemLongClick(int position) {
        Object obj = list.get(position);
        if (obj) {
            return true; //if you handle long click
        } else {
            return false; //if not, it's a click
        }
    }
}

0
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {

                int action = MotionEventCompat.getActionMasked(event);


                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        longClick = false;
                        x1 = event.getX();
                        break;

                    case MotionEvent.ACTION_MOVE:
                        if (event.getEventTime() - event.getDownTime() > 500 && Math.abs(event.getX() - x1) < MIN_DISTANCE) {
                            longClick = true;
                        }
                        break;

                    case MotionEvent.ACTION_UP:
                                if (longClick) {
                                    Toast.makeText(activity, "Long preess", Toast.LENGTH_SHORT).show();
                                } 
                }
                return true;
            }
        });

0

这是一种基于MSquare的检测长按按钮的好主意的方法,它具有一个附加功能:不仅响应长按执行了操作,而且重复该操作,直到收到MotionEvent.ACTION_UP消息为止收到。在这种情况下,长按和短按动作相同,但是可以不同。

请注意,正如其他人所报告的那样,删除响应于MotionEvent.ACTION_MOVE消息的回调使回调无法执行,因为我的手指无法保持足够的静止。我通过忽略该消息来解决该问题。

private void setIncrementButton() {
    final Button btn = (Button) findViewById(R.id.btn);
    final Runnable repeater = new Runnable() {
        @Override
        public void run() {
            increment();
            final int milliseconds = 100;
            btn.postDelayed(this, milliseconds);
        }
    };
    btn.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent e) {
            if (e.getAction() == MotionEvent.ACTION_DOWN) {
                increment();
                v.postDelayed(repeater, ViewConfiguration.getLongPressTimeout());
            } else if (e.getAction() == MotionEvent.ACTION_UP) {
                v.removeCallbacks(repeater);
            }
            return true;
        }
    });
}

private void increment() {
    Log.v("Long Press Example", "TODO: implement increment operation");   
}

这是聪明的,但它也消除了状态动画,即纹波点击等
约翰·萨尔迪尼亚

0

选项:自定义检测器 class

abstract public class
Long_hold
extends View.OnTouchListener
{
  public@Override boolean
  onTouch(View view, MotionEvent touch)
  {
    switch(touch.getAction())
    {
    case ACTION_DOWN: down(touch); return true;
    case ACTION_MOVE: move(touch);
    }
    return true;
  }

  private long
  time_0;
  private float
  x_0, y_0;

  private void
  down(MotionEvent touch)
  {
    time_0= touch.getEventTime();
    x_0= touch.getX();
    y_0= touch.getY();
  }

  private void
  move(MotionEvent touch)
  {
    if(held_too_short(touch) {return;}
    if(moved_too_much(touch)) {return;}

    long_press(touch);
  }
  abstract protected void
  long_hold(MotionEvent touch);
}

使用

private double
moved_too_much(MotionEvent touch)
{
  return Math.hypot(
      x_0 -touch.getX(),
      y_0 -touch.getY()) >TOLERANCE;
}

private double
held_too_short(MotionEvent touch)
{
  return touch.getEventTime()-time_0 <DOWN_PERIOD;
}

哪里

  • TOLERANCE 是最大容许运动

  • DOWN_PERIOD 是时候按下了

import

static android.view.MotionEvent.ACTION_MOVE;
static android.view.MotionEvent.ACTION_DOWN;

在代码中

setOnTouchListener(new Long_hold()
  {
  protected@Override boolean
  long_hold(MotionEvent touch)
  {
    /*your code on long hold*/
  }
});

-2

我找到了一个解决方案,它不需要定义可运行的东西或其他东西,并且运行良好。

    var lastTouchTime: Long = 0

    // ( ViewConfiguration.#.DEFAULT_LONG_PRESS_TIMEOUT =500)
    val longPressTime = 500

    var lastTouchX = 0f
    var lastTouchY = 0f

    view.setOnTouchListener { v, event ->

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                lastTouchTime = SystemClock.elapsedRealtime()
                lastTouchX = event.x
                lastTouchY = event.y
                return@setOnTouchListener true
            }
            MotionEvent.ACTION_UP -> {
                if (SystemClock.elapsedRealtime() - lastTouchTime > longPressTime
                        && Math.abs(event.x - lastTouchX) < 3
                        && Math.abs(event.y - lastTouchY) < 3) {
                    Log.d(TAG, "Long press")
                }
                return@setOnTouchListener true
            }
            else -> {
                return@setOnTouchListener false
            }
        }

    }

不会检测到长按手势,只会在事实之后告诉您这是长按。
daedsidog

它非常适合检测长按。请仔细检查。
Kinjal patel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.