检测触摸按压与长按与移动?

Posted

技术标签:

【中文标题】检测触摸按压与长按与移动?【英文标题】:Detect touch press vs long press vs movement? 【发布时间】:2011-05-18 11:37:34 【问题描述】:

我目前正在摆弄 android 编程,但是我在检测不同的触摸事件时遇到了一个小问题,即正常的触摸按下(按下屏幕并立即释放)、长按(触摸屏幕并按住手指放在上面)和移动(在屏幕上拖动)。

我想做的是在我的屏幕上显示一个(圆形的)图像,我可以拖动它。然后,当我按一次(短按/正常按)时,Toast 会提供一些有关它的基本信息。当我长按它时,会出现一个带有列表的 AlertDialog 以选择不同的图像(圆形、矩形或三角形)。

我用自己的 OnTouchListener 制作了一个自定义 View 来检测事件并在 onDraw 中绘制图像。 OnTouchListener.onTouch 是这样的:

// has a touch press started?
private boolean touchStarted = false;
// co-ordinates of image
private int x, y;

public boolean onTouch(View v, MotionEvent event) 
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) 
        touchStarted = true;
    
    else if (action == MotionEvent.ACTION_MOVE) 
        // movement: cancel the touch press
        touchStarted = false;

        x = event.getX();
        y = event.getY();

        invalidate(); // request draw
    
    else if (action == MotionEvent.ACTION_UP) 
        if (touchStarted) 
            // touch press complete, show toast
            Toast.makeText(v.getContext(), "Coords: " + x + ", " + y, 1000).show();
        
    

    return true;

问题在于,按压并没有按预期工作,因为当我随意触摸屏幕时,它也会检测到一点点移动,然后取消触摸按压并在图像周围移动。

我“破解”了这一点,我引入了一个新变量“mTouchDelay”,我在 ACTION_DOWN 上将其设置为 0,在 MOVE 中增加,如果在 MOVE 中 >= 3,我执行我的“移动”代码。但我有一种感觉,这不是真正要走的路。

我还没有发现如何检测长按。罪魁祸首确实是似乎总是触发的 MOVE。

有关我大致想要的示例,请参阅 Android 应用程序“DailyStrip”:它显示漫画的图像。如果屏幕太大,您可以拖动它。您可以点击一次以弹出某些控件,然后长按它以打开选项菜单。

PS。我正在尝试让它在 Android 1.5 上运行,因为我的手机只能在 1.5 上运行。

【问题讨论】:

这需要一个Java标签。但是,如果您曾经在一个可通过 jQuery 访问的 Web 应用程序中解决此问题,我正在尝试找出如何拦截长按(长按、长按)。 【参考方案1】:

此代码可以区分单击和移动(拖动、滚动)。在onTouchEvent 中设置一个标志isOnClick,并将初始X、Y 坐标放在ACTION_DOWN 上。清除 ACTION_MOVE 上的标志(注意经常会检测到无意的移动,这可以通过 THRESHOLD const 解决)。

private float mDownX;
private float mDownY;
private final float SCROLL_THRESHOLD = 10;
private boolean isOnClick;

@Override
public boolean onTouchEvent(MotionEvent ev) 
    switch (ev.getAction() & MotionEvent.ACTION_MASK) 
        case MotionEvent.ACTION_DOWN:
            mDownX = ev.getX();
            mDownY = ev.getY();
            isOnClick = true;
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            if (isOnClick) 
                Log.i(LOG_TAG, "onClick ");
                //TODO onClick code
            
            break;
        case MotionEvent.ACTION_MOVE:
            if (isOnClick && (Math.abs(mDownX - ev.getX()) > SCROLL_THRESHOLD || Math.abs(mDownY - ev.getY()) > SCROLL_THRESHOLD)) 
                Log.i(LOG_TAG, "movement detected");
                isOnClick = false;
            
            break;
        default:
            break;
    
    return true;

对于上面建议的 LongPress,GestureDetector 是可行的方法。检查此问答:

Detecting a long press with Android

【讨论】:

随时! :) 感谢您的承认。 ;) & MotionEvent.ACTION_MASK 有什么作用? MotionEvent.ACTION_MASK 是按位与运算应用的位掩码。它过滤掉指针索引,只显示 MotionEvent 动作。 这很好用。对我来说唯一的事情是,我还需要在拖动过程中获取事件(当手指移动时)。此代码仅在整个拖动过程中提供一个移动事件。要获得所需的事件,删除“isOnClick &&”部分就可以了.. 我只是想把这段时间从我的 Android 工作室会议中抽出,并亲自感谢您提供上述代码。它为我节省了似乎一整天的野鹅追逐。非常感谢。【参考方案2】:

来自 Android 文档 -

onLongClick()

来自 View.OnLongClickListener。当用户触摸并按住项目(处于触摸模式时)或使用导航键或轨迹球关注项目并按住合适的“输入”键或按住轨迹球时调用此方法(一秒钟)。

onTouch()

来自 View.OnTouchListener。当用户执行符合触摸事件的操作时调用此方法,包括按下、释放或屏幕上的任何移动手势(在项目范围内)。

至于“即使我触摸也会发生移动”,我会设置一个增量,并确保视图在启动移动代码之前至少移动了增量。如果还没有,请启动触摸代码。

【讨论】:

使用 onLongClick 长按“有点”有效,但我仍然面临两个问题。首先,onLongClick 一次长按会触发两次(我只是显示一个 Toast 并返回 true,然后我得到 2 个 Toast)。此外,当我按住时,我会在按住时长按一下,但是当我松开时,我仍然会从 onTouch 获得 ACTION_UP。 onLongClick 触发后如何防止 ACTION_UP onTouch? 双重 onLongClick 让我担心,我不知道为什么它会触发两次。至于仍在触发的 ACTION_UP,我会问您为什么需要跟踪 ACTION_UP 和 ACTION_DOWN。测试 onTouch() 是否在 onLongClick() 被触发时被触发。如果不是,我不确定您是否需要捕获这些。您应该只需要 onTouch() 来处理实际触摸项目和移动项目的时间。您还可以查看将 onClick() 用于“onTouch()”并将 onTouch() 仅用于移动。不过,不确定这是否符合您的要求。【参考方案3】:

经过大量实验,我发现了这一点。

在您的活动初始化中:

setOnLongClickListener(new View.OnLongClickListener() 
  public boolean onLongClick(View view) 
    activity.openContextMenu(view);  
    return true;  // avoid extra click events
  
);
setOnTouch(new View.OnTouchListener()
  public boolean onTouch(View v, MotionEvent e)
    switch(e.getAction & MotionEvent.ACTION_MASK)
      // do drag/gesture processing. 
    
    // you MUST return false for ACTION_DOWN and ACTION_UP, for long click to work
    // you can return true for ACTION_MOVEs that you consume. 
    // DOWN/UP are needed by the long click timer.
    // if you want, you can consume the UP if you have made a drag - so that after 
    // a long drag, no long-click is generated.
    return false;
  
);
setLongClickable(true);

【讨论】:

“消费 UP”是什么意思?对不起,这里的新手。 哦 - 如果我收到一个事件,我可以“消费”它,这意味着监听同一事件的其他钩子也不会被调用。例如,当我将手从屏幕上抬起时,除非我使用它,否则可能会沿着组件层次结构发送“UP”消息。 我明白了。我原则上理解您写的内容,但是您实际上如何“消费”,例如“UP”事件? 啊 - 使用事件只需从 onTouchonLongClick 函数返回 true 即可。调用此处理程序方法的窗口线程在将事件分派给您的函数后检查您是否返回 T/F。【参考方案4】:

我一直在寻找类似的解决方案,这就是我的建议。 在 OnTouch 方法中,记录 MotionEvent.ACTION_DOWN 事件的时间,然后为 MotionEvent.ACTION_UP 重新记录时间。这样您也可以设置自己的阈值。尝试几次后,您将知道记录一次简单触摸所需的最长时间(以毫秒为单位),您可以在移动或其他方法中随意使用它。

希望这会有所帮助。如果您使用不同的方法并解决了您的问题,请发表评论。

【讨论】:

【参考方案5】:

如果您需要区分单击、长按和滚动,请使用 GestureDetector

Activity 实现GestureDetector.OnGestureListener

然后在 onCreate 中创建检测器,例如

mDetector = new GestureDetectorCompat(getActivity().getApplicationContext(),this);

然后可以选择在您的视图(例如 webview)上设置 OnTouchListener

onTouch(View v, MotionEvent event) 
return mDetector.onTouchEvent(event);

现在您可以使用 Override onScroll、onFling、showPress(检测长按)或 onSingleTapUp(检测点击)

【讨论】:

【参考方案6】:

我认为你应该实现 GestureDetector.OnGestureListener 如Using GestureDetector to detect Long Touch, Double Tap, Scroll or other touch events in Android 和 androidsnippets 然后在 onSingleTapUp 中实现点击逻辑,在 onScroll 事件中移动逻辑

【讨论】:

【参考方案7】:

在希望 longclick 不以点击事件结束之后,我只是在处理这个烂摊子。

这就是我所做的。

public boolean onLongClick(View arg0) 
    Toast.makeText(getContext(), "long click", Toast.LENGTH_SHORT).show();
    longClicked = true;
    return false;


public void onClick(View arg0) 
    if(!longClicked)
        Toast.makeText(getContext(), "click", Toast.LENGTH_SHORT).show();
    
    longClick = false; // sets the clickability enabled


boolean longClicked = false;

这有点小技巧,但确实有效。

【讨论】:

很晚了,但也许其他人读到了:如果你在onLongClick中返回true,不会调用onClick,所以不需要额外的变量【参考方案8】:

它只需要覆盖类中的 ontouch 事件。检查一下,它对我有帮助

http://mirnauman.wordpress.com/2012/04/16/android-google-maps-tutorial-part-6-getting-the-location-that-is-touched/

【讨论】:

【参考方案9】:

GestureDetector.SimpleOnGestureListener 有方法可以帮助解决这 3 种情况;

   GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() 

        //for single click event.
        @Override
        public boolean onSingleTapUp(MotionEvent motionEvent) 
            return true;
        

        //for detecting a press event. Code for drag can be added here.
        @Override
        public void onShowPress(MotionEvent e) 
            View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
            ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
            ClipData clipData = ClipData.newPlainText("..", "...");
            clipboardManager.setPrimaryClip(clipData);

            ConceptDragShadowBuilder dragShadowBuilder = new CustomDragShadowBuilder(child);
            // drag child view.
            child.startDrag(clipData, dragShadowBuilder, child, 0);
        

        //for detecting longpress event
        @Override
        public void onLongPress(MotionEvent e) 
            super.onLongPress(e);
        
    );

【讨论】:

以上是关于检测触摸按压与长按与移动?的主要内容,如果未能解决你的问题,请参考以下文章

Vue 移动端的长按与触摸事件

如何用Arduino检测长按按键的按下和弹起,包括去抖动

长按和触摸开始

CC2530强化实训03定时器间隔定时实现按键长按与短按

如何检测从长按开始的水龙头?

2016.11.22经验积累(1.item长按与单机,2调用联系人.3.LayoutInflater获取方式)