Domanda

Mattina tutto,

devo ammettere fuori rettilineo che Sono nuovo di sviluppo e di provare la mia mano a Android. Ho cercato di cercare il 'consiglio rete per trovare su come implementare alcuni 'pulsante HOLD per ripetere azione' - Ho creato un tastierino numerico personalizzato da pulsanti e voglio un comportamento backspace-like. Dopo aver ottenuto fino ad ora, ho invocato un amico che eccedente codificato Android prima, ma un sacco fatta di C # / Java e sembra sapere quello che sta facendo.

Il codice seguente funziona bene, ma sento che potrebbe essere fatto più ordinatamente. Mi scuso se ho perso i bit, ma si spera che questo spiega il mio approccio. Credo che l'onTouchListener è ok, ma il modo in cui sono gestite le discussioni sensazione doesnt destra.

C'è un modo migliore o più semplice per fare questo?

Grazie,

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
}
È stato utile?

Soluzione

Questa è l'attuazione più indipendente, utilizzabile con qualsiasi vista, che supporta tocco evento:

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

}

Utilizzo:

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

Altri suggerimenti

Ecco una semplice classe denominata AutoRepeatButton che può, in molti casi, essere usato come un rimpiazzo per la classe pulsante standard:

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

L'implementazione di base è il suono. Tuttavia, vorrei incapsulare la logica che in un'altra classe in modo che si può utilizzare in altri luoghi senza duplicare il codice. Vedi per esempio questo implementazione di classe "RepeatListener" che fa la stessa cosa che si vuole fare, ad eccezione di una barra di ricerca.

Ecco un altro thread con una soluzione alternativa, ma è molto simile al vostro primo.

RepeatListenerClass di Oliv è piuttosto buona, ma non gestisce "MotionEvent.ACTION_CANCEL", in modo da handler non rimuove chiamata di nuovo in quella azione. Questo rende i problemi nel PagerAdapter , e così via. Così ho aggiunto quel caso evento.

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

La classe di Carl è autonomo e funziona bene.

Vorrei fare ritardo iniziale e ripetere l'intervallo configurabile. Per fare ciò,

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

quindi è possibile utilizzare la classe come questo

        <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"
            />

Ecco una risposta basata su Oliv di con le seguenti modifiche:

  • Invece di prendere un click ascoltatore e chiamando direttamente onClick, chiama performClick o performLongClick sulla vista. In tal modo, il comportamento standard di clic, come feedback tattile sulla lunga clic.
  • Può essere configurato per l'incendio onClick immediatamente (come l'originale), o solo su ACTION_UP e solo se nessun evento click hanno sparato (più simile a come funziona onClick standard).
  • Alternate costruttore no-arg che i set immediateClick false e utilizza il sistema standard lunga pressione timeout per entrambi gli intervalli. Per me questo si sente più simile a quello che sarebbe stato uno standard di "premere a lungo repeat", se esistesse.

Qui è:

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

}

classe di Carl è abbastanza buono, qui è la modifica che ti permettono aumento di velocità (il più a lungo si tiene la funzione più veloce cliccare viene eseguito:

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

La classe di Carl è un bene per me. Ma è un po 'di premere il pulsante problema quando e trascinando. Nel caso in cui uscire di area del pulsante, ancora in corso evento click.

Si prega di aggiungere un codice su ACTION_MOVE come come ' Android: Rileva se tocchi utente e trascina fuori della regione di tasto '

?

Ecco una piccola soluzione diversa senza l'utilizzo di un clic ascoltatore nidificato.

Utilizzo:

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

E l'ascoltatore in sé:

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);
            }
        }
    };
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top