Android button - Hold to repeat action

Morning everything

I immediately admit that I am new to development and trying my best on Android. I am trying to find a "network" to find advice on how to implement some "Hold" button to repeat the action. " I created a custom numpad with buttons and want behavior similar to backspace. So far, I turned to a friend who had not previously encoded Android, but had done a lot of C # / Java and seems to know what he is doing.

The code below works very well, but I feel it can be done more accurately. I apologize if I missed a bit, but hopefully this explains my approach. I think onTouchListener is fine, but the way the thread is handled does not feel right.

Is there a better or easier way to do this?

Thank,

M

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 } 
+49
android handler ontouchlistener
Nov 26 2018-10-11T00:
source share
9 answers

This is a more independent implementation that can be used with any view that supports a 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 (eg 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; } } 

Using:

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

Here is a simple class called AutoRepeatButton, which in many cases can be used as a 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(); } } 
+14
Dec 11 '11 at 12:29
source share

Your main implementation sounds. However, I would include this logic in another class so that you can use it in other places without duplicating the code. See this implementation of the "RepeatListener" class, which does the same thing you want to do, with the exception of the search string.

Here is another thread with an alternative solution , but it is very similar to your first one.

+7
Nov 26 '10 at 13:41
source share

Oliv RepeatListenerClass is pretty good, but it does not handle "MotionEvent.ACTION_CANCEL", so the handler does not delete the call in this action. This creates problems in the PagerAdapter , etc. So I added this case.

 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; } 
+7
Jul 08 '14 at 23:13
source share

The Carl class is self-contained and works great.

I would make an initial delay and adjust the repeat interval. For this

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

then you can use a class like this

  <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" /> 
+4
Dec 14 2018-11-11T00:
source share

Here is an answer based on Olive with the following settings:

  • Instead of directly listening to the click and calling onClick , it calls performClick or performLongClick in the view. This will lead to standard clicks, such as tactile feedback with a long press.
  • It can be configured either to launch onClick immediately (for example, the original), or only to ACTION_UP , and only if no click events have occurred (more like the standard onClick ).
  • An alternative no-arg constructor that sets immediateClick to false and uses the system standard long wait interval for both intervals. For me, this is most like the standard "re-long print" if it existed.

There he is:

 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 (eg 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; } } 
+4
Jul 10 '15 at 2:01
source share

The Carl class is pretty good, here is a modification that will speed up (the longer you perform the quick click function:

 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(); } } 
+2
08 Feb '14 at 9:41
source share

Carl class is good for me. But this is a problem when you click and drag. If you exit the button area, the click event still occurs.

Please add the code ACTION_MOVE, for example, like " Android: find out if the user touches and drags from the button area?

0
Mar 02 '15 at 3:49
source share

Here is a slightly different solution without using a nested click listener.

Using:

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

And the listener himself:

 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); } } }; } 
0
Jul 09 '18 at 13:47
source share



All Articles