Vion
Vion

Reputation: 569

Increment/Decrement Counter while button is pressed

I want to rotate a picture (Bitmap within a ImageView) around his center. This works pretty well by using the preRotate method with the values width/2 and height/2 of the Bitmap and 1 or -1 degree as rotation factor.

But I implemented the rotation functions as Buttons. Everytime the Button "rotate right" is pressed the Imageview rotates one degree to the right and so on. It would be nice to press the Button and while the Button is pressed the picture starts rotating until the button is released.

I've read some threads here in which these feature is implemented as OnTouchListener instead of OnClickListener, but it does not work for me. If I implement loops within the MotionEvent.ACTION_DOWN event then they are infinite. If I don't use loops then the event is only handled once (like in OnClickListener).

So how can I increment/decrement the rotation factor while a button is pressed?

Upvotes: 2

Views: 7841

Answers (3)

six5536
six5536

Reputation: 968

I've extended Knickedi's answer to a more generic case. Hope that helps someone.

import android.view.MotionEvent;
import android.view.View;

public abstract class OnTouchContinuousListener implements View.OnTouchListener {

  private final int mInitialRepeatDelay;
  private final int mNormalRepeatDelay;
  private View mView;

  /**
   * Construct listener with default delays
   */
  public OnTouchContinuousListener() {
    this.mInitialRepeatDelay = 500;
    this.mNormalRepeatDelay = 200;
  }

  /**
   * 
   * Construct listener with configurable delays
   * 
   * 
   * @param initialRepeatDelay
   *          delay to the first repeat in millis
   * @param normalRepeatDelay
   *          delay to subsequent repeats in millis
   */
  public OnTouchContinuousListener(int initialRepeatDelay, int normalRepeatDelay) {
    this.mInitialRepeatDelay = initialRepeatDelay;
    this.mNormalRepeatDelay = normalRepeatDelay;
  }

  private final Runnable repeatRunnable = new Runnable() {
    @Override
    public void run() {

      // as long the button is press we continue to repeat
      if (mView.isPressed()) {

        // Fire the onTouchRepeat event
        onTouchRepeat(mView);

        // Schedule the repeat
        mView.postDelayed(repeatRunnable, mNormalRepeatDelay);
      }
    }
  };

  /**
   * Called when a touch event is dispatched to a view. This allows listeners to
   * get a chance to respond before the target view.
   * 
   * @param v
   *          The view the touch event has been dispatched to.
   * @param event
   *          The MotionEvent object containing full information about the
   *          event.
   * @return True if the listener has consumed the event, false otherwise.
   */
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      mView = v;

      // Fire the first touch straight away
      onTouchRepeat(mView);

      // Start the incrementing with the initial delay
      mView.postDelayed(repeatRunnable, mInitialRepeatDelay);
    }

    // don't return true, we don't want to disable buttons default behavior
    return false;
  }

  /**
   * Called when the target item should be changed due to continuous touch. This
   * happens at first press, and also after each repeat timeout. Releasing the
   * touch will stop the repeating.
   * 
   */
  public abstract void onTouchRepeat(View view);

}

Upvotes: 8

Knickedi
Knickedi

Reputation: 8787

Assume button is a private member so you can access it in the runnable member. This is what I would try. You can consider using System.currentTimeMillis() for an exact and time based rotation value calculation.

Here's the idea (caution, not tested and written without an IDE):

private Runnable rotationRunnable = new Runnable() {
    @Override
    public void run() {
        // perform rotation step here

        // as long the button is press we fire the rotation again
        if (button.isPressed()) {
            button.postDelayed(rotationRunnable, 40);
        }
    }
};

// in onCreate
button.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // inital start of rotation
            v.post(rotationRunnable);
        }

        // don't return ture, we don't want to disable buttons default behavior
        return false;
    }
});

Upvotes: 2

Kurtis Nusbaum
Kurtis Nusbaum

Reputation: 30825

Short answer: You need to implement a corresponding MotionEvent.ACTION_UP to stop the adding. ACTION_DOWN is only ever fired once, when the user presses down. That's why when you weren't looping you only got one increment. What you need is a seperate thread to start doing the increments when a MotionEvent.ACTION_DOWN is done and stop them when MotionEvent.ACTION_UP is fired. Something like this should work.

public MyActivity extends Activity{


  private bool continueIncrementing;
  private Runnable incrementerThread;

  //put this OnTouchListener on your button
   View.OnTouchListener downListener = new View.OnTouchListner(){
     public onTouch(View v, MotionEvent event){
       if(event == MotionEvent.ACTION_DOWN){
         startIncrmenting();
       }
       else if(event == MotionEvent.ACTION_UP){
         stopIncrementing();
       }
     }
   };

   private void startIncrmenting(){
     setIsIncrementing(true);
     new Thread(new Runnable() {
       public void run() {
         while(isIncrementing()){
          //do incrementing in here
         }
       }
     }).start();
   }

   sychronized private void stopIncrmenting(){
     setIsIncrementing(false);
   }


   sychronized private bool isIncrmenting(){
     return continueIncrementing;
   }

   synhronized void setIsIncrmenting(bool newSetting){
     continueIncrementing = newSetting;
   }


  //rest of your activity

}

Upvotes: 10

Related Questions