【问题标题】:Android - Hold Button to Repeat ActionAndroid - 按住按钮重复操作
【发布时间】:2010-11-26 09:58:13
【问题描述】:

我会直接承认,我是开发新手,并且正在尝试使用 Android。我一直在尝试搜索“网络”以找到有关如何实现一些“按住按钮以重复操作”的建议 - 我已经从按钮创建了一个自定义小键盘并想要一个类似退格的行为。到目前为止,我拜访了一位以前没有编写过 Android 代码但做过很多 C#/Java 并且似乎知道他在做什么的朋友。

下面的代码工作得很好,但我觉得它可以做得更整洁。如果我遗漏了一些内容,我深表歉意,但希望这能解释我的方法。我觉得onTouchListener还可以,但是Threads的处理方式感觉不太对。

有没有更好或更简单的方法来做到这一点?

public class MyApp extends Activity {

private boolean deleteThreadRunning = false;
private boolean cancelDeleteThread = false;
private Handler handler = new Handler();
    
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    
    //May have missed some declarations here...
        
    Button_Del.setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            
           switch (event.getAction())
           {
               case MotionEvent.ACTION_DOWN:
               {
                   handleDeleteDown();
                   return true;
               }
               
               case MotionEvent.ACTION_UP:
               {
                   handleDeleteUp();
                   return true;
               }
               
               default:
                   return false;
           }
        }

        private void handleDeleteDown() {

            if (!deleteThreadRunning)
                startDeleteThread();
        }

        private void startDeleteThread() {

            Thread r = new Thread() {
                
                @Override
                public void run() {
                    try {
                        
                        deleteThreadRunning = true;
                        while (!cancelDeleteThread) {
                            
                            handler.post(new Runnable() {   
                                @Override
                                public void run() {
                                    deleteOneChar();
                                }
                            });
                            
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(
                                    "Could not wait between char delete.", e);
                            }
                        }
                    }
                    finally
                    {
                        deleteThreadRunning = false;
                        cancelDeleteThread = false;
                    }
                }
            };
            
            // actually start the delete char thread
            r.start();
        }
    });
}

private void handleDeleteUp() {
    cancelDeleteThread = true;
}

private void deleteOneChar()
{
    String result = getNumberInput().getText().toString();
    int Length = result.length();
    
    if (Length > 0)
        getNumberInput().setText(result.substring(0, Length-1));
        //I've not pasted getNumberInput(), but it gets the string I wish to delete chars from
}

【问题讨论】:

  • 这看起来不像是个问题。代码看起来没问题。不过。
  • 同意,问题是是否有更好的、Android 特定的方法来做到这一点。感觉像很多代码来实现如此微不足道的事情。

标签: android handler ontouchlistener


【解决方案1】:

这是更独立的实现,可用于任何支持触摸事件的视图:

import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;

/**
 * A class, that can be used as a TouchListener on any view (e.g. a Button).
 * It cyclically runs a clickListener, emulating keyboard-like behaviour. First
 * click is fired immediately, next one after the initialInterval, and subsequent
 * ones after the normalInterval.
 *
 * <p>Interval is scheduled after the onClick completes, so it has to run fast.
 * If it runs slow, it does not generate skipped onClicks. Can be rewritten to
 * achieve this.
 */
public class RepeatListener implements OnTouchListener {

    private Handler handler = new Handler();

    private int initialInterval;
    private final int normalInterval;
    private final OnClickListener clickListener;
    private View touchedView;

    private Runnable handlerRunnable = new Runnable() {
        @Override
        public void run() {
            if(touchedView.isEnabled()) {
                handler.postDelayed(this, normalInterval);
                clickListener.onClick(touchedView);
            } else {
                // if the view was disabled by the clickListener, remove the callback
                handler.removeCallbacks(handlerRunnable);
                touchedView.setPressed(false);
                touchedView = null;
            }
        }
    };

    /**
     * @param initialInterval The interval after first click event
     * @param normalInterval The interval after second and subsequent click 
     *       events
     * @param clickListener The OnClickListener, that will be called
     *       periodically
     */
    public RepeatListener(int initialInterval, int normalInterval, 
            OnClickListener clickListener) {
        if (clickListener == null)
            throw new IllegalArgumentException("null runnable");
        if (initialInterval < 0 || normalInterval < 0)
            throw new IllegalArgumentException("negative interval");

        this.initialInterval = initialInterval;
        this.normalInterval = normalInterval;
        this.clickListener = clickListener;
    }

    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
        case MotionEvent.ACTION_DOWN:
            handler.removeCallbacks(handlerRunnable);
            handler.postDelayed(handlerRunnable, initialInterval);
            touchedView = view;
            touchedView.setPressed(true);
            clickListener.onClick(view);
            return true;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            handler.removeCallbacks(handlerRunnable);
            touchedView.setPressed(false);
            touchedView = null;
            return true;
        }

        return false;
    }

}

用法:

Button button = new Button(context);
button.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() {
  @Override
  public void onClick(View view) {
    // the code to execute repeatedly
  }
}));

【讨论】:

  • 我喜欢这个实现。然而,其中有一个错误。 onTouch 函数应该返回 true,至少在 ACTION_DOWN 的情况下,否则不会检测到其他触摸事件(这种情况下的 ACTION_UP 永远不会被调用)
  • 这是一个非常好的解决方案。但请确保也像 sephiron 的回答那样实施 ACTION_CANCEL。
  • 很好的解决方案,非常感谢。此外,在 ACTION_DOWN 中添加 downView.setPressed(true); 并在 ACTION_CANCEL/ACTION_UP 部分添加 downView.setPressed(false); 将使侦听器响应选择器(如果有)。
  • 感谢 cmets,已添加 setPressed 电话。
  • 如果您在RepeatListener 构造函数中使用传递(View view) -&gt; view.performClick(),则可以使用普通的View.setOnClickListener(或android:onClick)进一步解耦。作为奖励,每次重复发生时它都会触发一个可访问性事件(和点击声音)。
【解决方案2】:

这是一个名为 AutoRepeatButton 的简单类,在许多情况下,它可以用作标准 Button 类的替代品:

package com.yourdomain.yourlibrary;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

public class AutoRepeatButton extends Button {

  private long initialRepeatDelay = 500;
  private long repeatIntervalInMilliseconds = 100;

  private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
    @Override
    public void run() {
      //Perform the present repetition of the click action provided by the user
      // in setOnClickListener().
      performClick();

      //Schedule the next repetitions of the click action, using a faster repeat
      // interval than the initial repeat delay interval.
      postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalInMilliseconds);
    }
  };

  private void commonConstructorCode() {
    this.setOnTouchListener(new OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction(); 
                if(action == MotionEvent.ACTION_DOWN) 
                {
                  //Just to be sure that we removed all callbacks, 
                  // which should have occurred in the ACTION_UP
                  removeCallbacks(repeatClickWhileButtonHeldRunnable);

                  //Perform the default click action.
                  performClick();

                  //Schedule the start of repetitions after a one half second delay.
                  postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
                }
                else if(action == MotionEvent.ACTION_UP) {
                  //Cancel any repetition in progress.
                  removeCallbacks(repeatClickWhileButtonHeldRunnable);
                }

                //Returning true here prevents performClick() from getting called 
                // in the usual manner, which would be redundant, given that we are 
                // already calling it above.
                return true;
      }
    });
  }

    public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        commonConstructorCode();
    }


    public AutoRepeatButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        commonConstructorCode();
    }

  public AutoRepeatButton(Context context) {
    super(context);
    commonConstructorCode();
  }
}

【讨论】:

  • 我认为这是一个很好的解决方案。我将 performClick() 更改为 performLongClick() 并将 performClick() 移动到 ACTION_UP 条件中。我唯一的问题是我的按钮现在没有动画。
【解决方案3】:

Oliv's RepeatListenerClass 很好,但它不处理“MotionEvent.ACTION_CANCEL”,因此处理程序不会删除该操作中的回调。这会在PagerAdapter 中产生问题,依此类推。所以我添加了那个事件案例。

private Rect rect; // Variable rect to hold the bounds of the view

public boolean onTouch(View view, MotionEvent motionEvent) {
    switch (motionEvent.getAction()) {
    case MotionEvent.ACTION_DOWN:
        handler.removeCallbacks(handlerRunnable);
        handler.postDelayed(handlerRunnable, initialInterval);
        downView = view;
        rect = new Rect(view.getLeft(), view.getTop(), view.getRight(),
                view.getBottom());
        clickListener.onClick(view);
        break;
    case MotionEvent.ACTION_UP:
        handler.removeCallbacks(handlerRunnable);
        downView = null;
        break;
    case MotionEvent.ACTION_MOVE:
        if (!rect.contains(view.getLeft() + (int) motionEvent.getX(),
                view.getTop() + (int) motionEvent.getY())) {
            // User moved outside bounds
            handler.removeCallbacks(handlerRunnable);
            downView = null;
            Log.d(TAG, "ACTION_MOVE...OUTSIDE");
        }
        break;
    case MotionEvent.ACTION_CANCEL:
        handler.removeCallbacks(handlerRunnable);
        downView = null;
        break;  
    }
    return false;
}

【讨论】:

    【解决方案4】:

    您的基本实现是合理的。但是,我会将该逻辑封装到另一个类中,以便您可以在其他地方使用它而无需复制代码。参见例如this“RepeatListener”类的实现,除了搜索栏外,它执行您想做的相同事情。

    这里是another thread with an alternative solution,但它与您的第一个非常相似。

    【讨论】:

    • 感谢 I82Much,他们的代码看起来可以完成类似的工作。我看看能不能带上它:)
    • Repeat Listener 是一个不错的选择。 Oliv 对它的实现比较完整。
    【解决方案5】:

    这是一个基于 Oliv 的答案,经过以下调整:

    • 而不是获取点击监听器并直接调用onClick,而是在视图上调用performClickperformLongClick。这将触发标准点击行为,例如长按时的触觉反馈。
    • 可以将其配置为立即触发onClick(与原始版本一样),或者仅在没有触发点击事件的情况下触发ACTION_UP(更像标准onClick 的工作方式)。
    • immediateClick 设置为false 并使用系统标准long press timeout 用于两个间隔的替代无参数构造函数。对我来说,这感觉最像标准的“重复长按”(如果存在的话)。

    这里是:

    import android.os.Handler;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.View.OnTouchListener;
    
    /**
     * A class that can be used as a TouchListener on any view (e.g. a Button).
     * It either calls performClick once, or performLongClick repeatedly on an interval.
     * The performClick can be fired either immediately or on ACTION_UP if no clicks have
     * fired.  The performLongClick is fired once after initialInterval and then repeatedly
     * after normalInterval.
     *
     * <p>Interval is scheduled after the onClick completes, so it has to run fast.
     * If it runs slow, it does not generate skipped onClicks.
     *
     * Based on http://stackoverflow.com/a/12795551/642160
     */
    public class RepeatListener implements OnTouchListener {
    
        private Handler handler = new Handler();
    
        private final boolean immediateClick;
        private final int initialInterval;
        private final int normalInterval;
        private boolean haveClicked;
    
        private Runnable handlerRunnable = new Runnable() {
            @Override
            public void run() {
                haveClicked = true;
                handler.postDelayed(this, normalInterval);
                downView.performLongClick();
            }
        };
    
        private View downView;
    
        /**
         * @param immediateClick Whether to call onClick immediately, or only on ACTION_UP
         * @param initialInterval The interval after first click event
         * @param normalInterval The interval after second and subsequent click
         *       events
         * @param clickListener The OnClickListener, that will be called
         *       periodically
         */
        public RepeatListener(
            boolean immediateClick,
            int initialInterval,
            int normalInterval)
        {
            if (initialInterval < 0 || normalInterval < 0)
                throw new IllegalArgumentException("negative interval");
    
            this.immediateClick = immediateClick;
            this.initialInterval = initialInterval;
            this.normalInterval = normalInterval;
        }
    
        /**
         * Constructs a repeat-listener with the system standard long press time
         * for both intervals, and no immediate click.
         */
        public RepeatListener()
        {
            immediateClick = false;
            initialInterval = android.view.ViewConfiguration.getLongPressTimeout();
            normalInterval = initialInterval;
        }
    
        public boolean onTouch(View view, MotionEvent motionEvent) {
            switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handler.removeCallbacks(handlerRunnable);
                handler.postDelayed(handlerRunnable, initialInterval);
                downView = view;
                if (immediateClick)
                    downView.performClick();
                haveClicked = immediateClick;
                return true;
            case MotionEvent.ACTION_UP:
                // If we haven't clicked yet, click now
                if (!haveClicked)
                    downView.performClick();
                // Fall through
            case MotionEvent.ACTION_CANCEL:
                handler.removeCallbacks(handlerRunnable);
                downView = null;
                return true;
            }
    
            return false;
        }
    
    }
    

    【讨论】:

    • 老兄,这是完美的答案。因为它让我可以使用 single action 执行单击,并使用 repeat action 执行长按谢谢 +1
    【解决方案6】:

    Carl 的类是独立的并且工作正常。

    我会让初始延迟和重复间隔可配置。 为此,

    attrs.xml

    <resources>
    <declare-styleable name="AutoRepeatButton">
        <attr name="initial_delay"  format="integer" />
        <attr name="repeat_interval"  format="integer" />
    </declare-styleable>
    </resources>
    

    AutoRepeatButton.java

        public AutoRepeatButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoRepeatButton);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
    
            switch (attr) {
            case R.styleable.AutoRepeatButton_initial_delay:
                initialRepeatDelay = a.getInt(attr, DEFAULT_INITIAL_DELAY);
                break;
            case R.styleable.AutoRepeatButton_repeat_interval:
                repeatIntervalInMilliseconds = a.getInt(attr, DEFAULT_REPEAT_INTERVAL);
                break;
            }
        }
        a.recycle();
        commonConstructorCode();
    }
    

    那么你可以像这样使用类

            <com.thepath.AutoRepeatButton
                xmlns:repeat="http://schemas.android.com/apk/res/com.thepath"
                android:id="@+id/btn_delete"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/selector_btn_delete"
                android:onClick="onBtnClick"
                android:layout_weight="1"
                android:layout_margin="2dp"
    
                repeat:initial_delay="1500"
                repeat:repeat_interval="150"
                />
    

    【讨论】:

      【解决方案7】:

      Carl's class 不错,这里修改一下可以加速(按住的时间越长越快的点击功能执行:

      package com.yourdomain.yourlibrary;
      
      import android.content.Context;
      import android.util.AttributeSet;
      import android.view.MotionEvent;
      import android.view.View;
      import android.widget.Button;
      
      public class AutoRepeatButton extends Button {
          private long initialRepeatDelay = 500;
          private long repeatIntervalInMilliseconds = 100;
      
          // speedup
          private long repeatIntervalCurrent = repeatIntervalInMilliseconds;
          private long repeatIntervalStep = 2;
          private long repeatIntervalMin = 10;
      
          private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
              @Override
              public void run() {
                  // Perform the present repetition of the click action provided by the user
                  // in setOnClickListener().
                  performClick();
      
                  // Schedule the next repetitions of the click action, 
                  // faster and faster until it reaches repeaterIntervalMin
                  if (repeatIntervalCurrent > repeatIntervalMin)
                      repeatIntervalCurrent = repeatIntervalCurrent - repeatIntervalStep;
      
                  postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalCurrent);
              }
          };
      
          private void commonConstructorCode() {
              this.setOnTouchListener(new OnTouchListener() {
                  @Override
                  public boolean onTouch(View v, MotionEvent event) {
                      int action = event.getAction();
                      if (action == MotionEvent.ACTION_DOWN) {
                          // Just to be sure that we removed all callbacks,
                          // which should have occurred in the ACTION_UP
                          removeCallbacks(repeatClickWhileButtonHeldRunnable);
      
                          // Perform the default click action.
                          performClick();
      
                          // Schedule the start of repetitions after a one half second delay.
                          repeatIntervalCurrent = repeatIntervalInMilliseconds;
                          postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
                      } else if (action == MotionEvent.ACTION_UP) {
                          // Cancel any repetition in progress.
                          removeCallbacks(repeatClickWhileButtonHeldRunnable);
                      }
      
                      // Returning true here prevents performClick() from getting called
                      // in the usual manner, which would be redundant, given that we are
                      // already calling it above.
                      return true;
                  }
              });
          }
      
          public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
              super(context, attrs, defStyle);
              commonConstructorCode();
          }
      
           public AutoRepeatButton(Context context, AttributeSet attrs) {
           super(context, attrs);
           commonConstructorCode();
           }
      
          public AutoRepeatButton(Context context) {
              super(context);
              commonConstructorCode();
          }
      }
      

      【讨论】:

      • 如果它适用于所有视图,而不仅仅是按钮,那就更好了。
      【解决方案8】:

      Oliv'ssephiron's 的答案非常好,但我想将重复操作与常规 View.OnClickListener 一起使用,以及移动操作处理。

      View view = //take somewhere    
      view.setOnClickListener(v -> { /*do domething*/ });
      view.setOnTouchListener(new RepeatListener(/*almost the same as in Oliv's answer*/); 
      
      //if you want to use touch listener without click listener,  
      //make sure that view has setClickable(true)
      

      所以可以这样不冲突:

      import android.graphics.Rect;
      import android.os.Handler;
      import android.os.Looper;
      import android.view.MotionEvent;
      import android.view.View;
      import android.view.View.OnTouchListener;
      
      public class RepeatListener implements OnTouchListener {
      
          private final static int TOUCH_OFFSET = 20;
      
          private final Handler handler = new Handler(Looper.getMainLooper());
      
          private final int initialInterval;
          private final int normalInterval;
          private final Runnable startListener;
          private final Runnable actionListener;
      
          private final Rect touchHoldRect = new Rect();
      
          private View touchedView;
          private boolean calledAtLeastOnce = false;
      
          private final Runnable handlerRunnable = new Runnable() {
              @Override
              public void run() {
                  if (touchedView.isEnabled()) {
                      handler.postDelayed(this, normalInterval);
                      actionListener.run();
                      if (!calledAtLeastOnce && startListener != null) {
                          startListener.run();
                      }
                      calledAtLeastOnce = true;
                  } else {
                      handler.removeCallbacks(handlerRunnable);
                      touchedView.setPressed(false);
                      touchedView = null;
                      calledAtLeastOnce = false;
                  }
              }
          };
      
          public RepeatListener(int initialInterval,
                                int normalInterval,
                                Runnable startListener,
                                Runnable actionListener) {
              if (actionListener == null) {
                  throw new IllegalArgumentException("null runnable");
              }
              if (initialInterval < 0 || normalInterval < 0) {
                  throw new IllegalArgumentException("negative interval");
              }
      
              this.initialInterval = initialInterval;
              this.normalInterval = normalInterval;
              this.startListener = startListener;
              this.actionListener = actionListener;
          }
      
          public boolean onTouch(View view, MotionEvent motionEvent) {
              switch (motionEvent.getAction()) {
                  case MotionEvent.ACTION_DOWN: {
                      handler.removeCallbacks(handlerRunnable);
                      calledAtLeastOnce = false;
                      handler.postDelayed(handlerRunnable, initialInterval);
                      touchedView = view;
                      touchHoldRect.set(view.getLeft() - TOUCH_OFFSET,
                              view.getTop() - TOUCH_OFFSET,
                              view.getRight() + TOUCH_OFFSET,
                              view.getBottom() + TOUCH_OFFSET);
                      return false;
                  }
                  case MotionEvent.ACTION_UP:
                  case MotionEvent.ACTION_CANCEL: {
                      handler.removeCallbacks(handlerRunnable);
                      if (calledAtLeastOnce) {
                          touchedView.setPressed(false);
                      }
                      touchedView = null;
                      boolean processed = calledAtLeastOnce;
      
                      calledAtLeastOnce = false;
                      return processed;
                  }
                  case MotionEvent.ACTION_MOVE: {
                      if (!touchHoldRect.contains(
                              view.getLeft() + (int) motionEvent.getX(),
                              view.getTop() + (int) motionEvent.getY())) {
                          handler.removeCallbacks(handlerRunnable);
                      }
                      break;
                  }
              }
              return false;
          }
      }
      

      【讨论】:

        【解决方案9】:

        这是一个 Kotlin 版本:

        1. 结合benkcnikib3ro 的答案(以加快连续长按的操作);

        2. 如果视图(例如按钮)在此过程中被禁用,则防止可运行对象无限期地运行。

          import android.os.Handler
          import android.os.Looper
          import android.view.MotionEvent
          import android.view.View
          import android.view.View.OnTouchListener
          
          
          /**
           * A class that can be used as a TouchListener on any view (e.g. a Button).
           * It either calls performClick once, or performLongClick repeatedly on an interval.
           * The performClick can be fired either immediately or on ACTION_UP if no clicks have
           * fired.  The performLongClick is fired once after repeatInterval.
           *
           * Continuous LongClick can be speed-up by setting intervalAcceleration
           *
           * Interval is scheduled after the onClick completes, so it has to run fast.
           * If it runs slow, it does not generate skipped onClicks.
           *
           * Based on:
           * https://stackoverflow.com/a/31331293/8945452
           * https://stackoverflow.com/a/21644404/8945452
           *
           * @param immediateClick Whether to call onClick immediately, or only on ACTION_UP
           * @param interval The interval after first click event
           * @param intervalAcceleration The amount of time reduced from interval to speed-up the action
           *
           */
          class RepeatListener(private val immediateClick: Boolean, private val interval: Int, private val intervalAcceleration: Int) : OnTouchListener {
          
              private var activeView: View? = null
              private val handler = Handler(Looper.getMainLooper())
              private var handlerRunnable: Runnable
              private var haveClicked = false
          
              private var repeatInterval: Int = interval
              private val repeatIntervalMin: Int = 100
          
              init {
                  handlerRunnable = object : Runnable {
                      override fun run() {
          
                          if(activeView!!.isEnabled) {
                              haveClicked = true
          
                              // Schedule the next repetitions of the click action,
                              // faster and faster until it reaches repeaterIntervalMin
                              if (repeatInterval > repeatIntervalMin) {
                                  repeatInterval -= intervalAcceleration
                              }
          
                              handler.postDelayed(this, repeatInterval.toLong())
                              activeView!!.performLongClick()
          
                          }else{
                              clearHandler() //stop the loop if the view is disabled during the process
                          }
                      }
                  }
              }
          
              override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
                  when (motionEvent.action) {
                      MotionEvent.ACTION_DOWN -> {
                          handler.removeCallbacks(handlerRunnable)
                          handler.postDelayed(handlerRunnable, repeatInterval.toLong())
                          activeView = view
                          if (immediateClick) activeView!!.performClick()
                          haveClicked = immediateClick
                          return true
                      }
                      MotionEvent.ACTION_UP -> {
                          // If we haven't clicked yet, click now
                          if (!haveClicked) activeView!!.performClick()
                          clearHandler()
                          return true
                      }
                      MotionEvent.ACTION_CANCEL -> {
                          clearHandler()
                          return true
                      }
                  }
                  return false
              }
          
              private fun clearHandler(){
                  handler.removeCallbacks(handlerRunnable)
                  activeView = null
          
                  //reset the interval to avoid starting with the sped up interval
                  repeatInterval = interval
              }
          }
          

        【讨论】:

          【解决方案10】:

          卡尔的课对我有好处。 但是按下按钮并拖动时会出现一些问题。 万一离开按钮区域,仍然会发生点击事件。

          请添加关于 ACTION_MOVE 的代码,例如“Android: Detect if user touches and drags out of button region?

          【讨论】:

            【解决方案11】:

            这是一个不使用嵌套点击侦听器的稍微不同的解决方案。

            用法:

             view.setOnTouchListener(new LongTouchIntervalListener(1000) {
                    @Override
                    public void onTouchInterval() {
                        // do whatever you want
                    }
             });
            

            还有听者本身:

            public abstract class LongTouchIntervalListener implements View.OnTouchListener {
            
                private final long touchIntervalMills;
                private long touchTime;
                private Handler handler = new Handler();
            
                public LongTouchIntervalListener(final long touchIntervalMills) {
                    if (touchIntervalMills <= 0) {
                        throw new IllegalArgumentException("Touch touch interval must be more than zero");
                    }
                    this.touchIntervalMills = touchIntervalMills;
                }
            
                public abstract void onTouchInterval();
            
                @Override
                public boolean onTouch(final View v, final MotionEvent event) {
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            onTouchInterval();
                            touchTime = System.currentTimeMillis();
                            handler.postDelayed(touchInterval, touchIntervalMills);
                            return true;
                        case MotionEvent.ACTION_UP:
                        case MotionEvent.ACTION_CANCEL:
                            touchTime = 0;
                            handler.removeCallbacks(touchInterval);
                            return true;
                        default:
                            break;
                    }
                    return false;
                }
            
                private final Runnable touchInterval = new Runnable() {
                    @Override
                    public void run() {
                        onTouchInterval();
                        if (touchTime > 0) {
                            handler.postDelayed(this, touchIntervalMills);
                        }
                    }
                };
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-03-18
              • 2023-03-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-10-03
              相关资源
              最近更新 更多