Android - Hold Button to Repeat Action Android - Hold Button to Repeat Action android android

Android - Hold Button to Repeat Action


This is more independent implementation, usable with any View, that supports touch event:

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

Usage:

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


Here is a simple class called AutoRepeatButton which can, in many instances, be used as a drop-in replacement for the standard Button 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;  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();  }}


Oliv's RepeatListenerClass is pretty good, but it does not handle "MotionEvent.ACTION_CANCEL", so handler does not remove call back in that action. This makes problems in PagerAdapter, and so on. So I added that event case.

private Rect rect; // Variable rect to hold the bounds of the viewpublic 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;}