【问题标题】:Detecting a long press in Android使用 Android 检测长按
【发布时间】:2011-12-16 17:15:19
【问题描述】:

我正在使用

onTouchEvent(MotionEvent event){
}

检测用户何时按下我的 glSurfaceView 有一种方法可以检测何时进行长按。我猜如果我在开发文档中找不到太多东西,那么这将是某种解决方法。类似于注册 ACTION_DOWN 并查看 ACTION_UP 之前的时间。

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

【问题讨论】:

    标签: java android touchscreen long-click


    【解决方案1】:

    GestureDetector 是最好的解决方案。

    这是一个有趣的选择。在每个 ACTION_DOWNonTouchEvent 中安排一个 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);
    }
    

    【讨论】:

    • 您可以拨打android.view.ViewConfiguration.getLongPressTimeout()获取系统长按超时时间。
    • 我不知道为什么,但这两种解决方案都不适合我。
    • ACTION_CANCEL 也应该被处理
    • 谢谢你,@MSquare。这是一个好主意。请注意,对于我正在使用的设备,我只能在响应 ACTION_UP 时删除回调;这是因为它对运动非常敏感,我无法阻止 ACTION_MOVE 事件。另请注意,您的方法通过递归调用 Runnable 提供了一种实现重复操作的自然方法。
    • @stevehs 你需要测试触摸溢出。
    【解决方案2】:

    试试这个:

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

    【讨论】:

    • 手势检测器也能处理标准点击吗?
    • 注意:如果您已经有一个 OnClickListener,添加 OnLongClickListener 比使用 GestureRecognizer 容易得多(您可以避免一些特定于 GR 的问题)。参考文献stackoverflow.com/a/4402854/153422
    • 请注意,您应该提供创建 GestureDetector 的上下文作为第一个参数。该示例已弃用。
    • 要避免子类化视图,请使用View#setOnTouchListener()。请参阅Detecting Common Gestures 中的“捕获单个视图的触摸事件”。
    • @Modge 只有两个 GestureDetector 构造函数被弃用 - 你可以使用其余的构造函数。
    【解决方案3】:

    我有一个检测点击、长按和移动的代码。 这完全是上面给出的答案和我通过窥视每个文档页面所做的更改的组合。

    //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 中将goneFlag设置为false,否则第一次长按后的每次点击都被视为长按。
    • initialTouchX 是否在某处自动定义?我很困惑。
    【解决方案4】:

    我创建了一个snippet - 受实际 View 源的启发 - 可以可靠地检测带有自定义延迟的长点击/按下。但它在 Kotlin 中:

    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
    }
    

    【讨论】:

      【解决方案5】:

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

      因此,如果是点击,您应该使用 onClickListener。

      你的答案是类似的:使用 onLongClickListener。

      【讨论】:

      • 我可以将 onClickListener 与 GLSurfaceView 一起使用吗?我的印象是您只能使用带有按钮等 UI 小部件的 onClickListener 用户。不过可能是错的。
      • 啊,我觉得很酷,但是有没有办法通过 onClickListener 获取点击的坐标
      • onClickListener 似乎不适用于 GLSurfaceView,即使我将它包装在 FrameLayout 中。我尝试在表面视图本身和框架布局上设置侦听器,但它不起作用 =/
      • 这个问题似乎是 GLSurfaceView 特有的,但是,一般来说,对于其他 View,OnLongClickListener 是要走的路。
      【解决方案6】:

      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。
      • 啊,我什至不记得 this 是如何跟踪“如果长按屏幕上的任意位置然后”
      【解决方案7】:

      这个想法是创建一个Runnable 以在将来执行长点击,但是这个执行可以因为点击或移动而被取消。

      您还需要知道,什么时候消耗了长按,什么时候因为手指移动太多而取消。我们使用initialTouchX & initialTouchY 来检查用户是否退出了一个 10 像素的正方形区域,每边 5 个。

      这是我将 Click & LongClickListView 中的Cell 委托给ActivityOnTouchListener 的完整代码:

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

      ClickDelegate是一个interface,用于像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
              }
          }
      }
      

      【讨论】:

        【解决方案8】:
        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;
                    }
                });
        

        【讨论】:

          【解决方案9】:

          这是一种基于 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");   
          }
          

          【讨论】:

          • 这很聪明,但它也删除了状态动画,即点击时的波纹等。
          【解决方案10】:

          选项:自定义检测器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*/
            }
          });
          

          【讨论】:

            【解决方案11】:

            我找到了一种解决方案,它不需要定义可运行或其他东西,它工作正常。

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

            【讨论】:

            • 不检测长按手势,只会在事后告诉你这是一个长按。
            • 它非常适合检测长按。请仔细检查。
            • @kinjalpatel 不,它没有。它不会在事件发生时触发,它会在事件结束后触发(MotionEvent.ACTION_UP)。
            猜你喜欢
            • 2018-08-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-06-22
            • 2016-04-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多