38

I'm new to Android so sorry if the question is easy to answer. I have two buttons, a decrease and an increase button, and in the middle of them a TextView which displays a value.

When I hit the decrease button, the value in the TextView decreases and increases when I hit the increase button, no problem with that, I got that working but the problem is the value only increases/decreases by 1 on a single click. What I'm trying to achieve is that as I continuously press the button (the increase button for example), the value is also continuously increasing and only stops when I release the increase button.

Is that possible? If so, can you show some sample code or references on how to implement that? Thanks!

Here is my main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center" >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="44dp"
        android:gravity="center_horizontal" >

        <Button android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:text="&lt;" />

        <TextView android:id="@+id/textView1"
            android:layout_width="50dp"
            android:layout_height="fill_parent"
            android:layout_alignBottom="@+id/button1"
            android:layout_toRightOf="@+id/button1"
            android:gravity="center"
            android:text="45" />

        <Button android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_toRightOf="@+id/textView1"
            android:text="&gt;" />

     </RelativeLayout>   

</RelativeLayout>

and here is my Main.java

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class Main extends Activity {

    private Button _decrease;
    private Button _increase;
    private TextView _value;
    private static int _counter = 45;
    private String _stringVal;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        _decrease = (Button) findViewById(R.id.button1);
        _increase = (Button) findViewById(R.id.button2);
        _value = (TextView) findViewById(R.id.textView1);

        _decrease.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                Log.d("src", "Decreasing value...");
                _counter--;
                _stringVal = Integer.toString(_counter);
                _value.setText(_stringVal);
            }
        });

        _increase.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                Log.d("src", "Increasing value...");
                _counter++;
                _stringVal = Integer.toString(_counter);
                _value.setText(_stringVal);
            }
        });

    }
}
src
  • 469
  • 1
  • 6
  • 12

12 Answers12

92

For that to work, you need a thread that will update the integer value when you long press on a button.

Create a handler in your activity:

private Handler repeatUpdateHandler = new Handler();

And 2 vars which will state: is it increment or decrement? Only one set at a time.

private boolean mAutoIncrement = false;
private boolean mAutoDecrement = false;

And the present number value

public int mValue;

And a class that will run in another thread:

class RptUpdater implements Runnable {
    public void run() {
        if( mAutoIncrement ){
            increment();
            repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
        } else if( mAutoDecrement ){
            decrement();
            repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
        }
    }
}

Add a long press listener to your button:

mBTIncrement.setOnLongClickListener( 
            new View.OnLongClickListener(){
                public boolean onLongClick(View arg0) {
                    mAutoIncrement = true;
                    repeatUpdateHandler.post( new RptUpdater() );
                    return false;
                }
            }
    );   

mBTIncrement.setOnTouchListener( new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            if( (event.getAction()==MotionEvent.ACTION_UP || event.getAction()==MotionEvent.ACTION_CANCEL) 
                    && mAutoIncrement ){
                mAutoIncrement = false;
            }
            return false;
        }
    });  

In the above case the button is the increment one. Create another button which will set mAutoDecrement to true.

And decrement() will be a function, which will set your instance int variable like this:

public void decrement(){
    mValue--;
    _value.setText( ""+mValue );
}

You figure the increment out. Oh and REP_DELAY is a static int variable set to 50.

I see this is an excerpt from Jeffrey Cole's open source NumberPicker available at http://www.technologichron.net/ Proper author's attribution must be added.

Yar
  • 4,543
  • 2
  • 35
  • 42
  • i followed your instructions, created the `increment()` function and declared the `REP_DELAY`, no errors but when I click the increment button, the value increases but even if I release it, the value is still increasing and doesn't stop. Same goes with the decrease button. Am I missing something? – src Oct 29 '11 at 12:15
  • 2
    Sorry, forgot about adding the onTouchListener, which will clear the increment flag (same for decrement) - please see updated code. – Yar Oct 29 '11 at 12:34
  • 1
    added the onTouchListener for increment and decrement and now it works. Thank you! Greatly appreciated! :) – src Oct 29 '11 at 12:42
  • Thanks for your assistance. Any ideas what to do when I need to do this increment/decrement thing for multiple values? It makes a lot of repetition. – MikkoP Jun 24 '17 at 19:19
  • you don't need another thread just use ``handler post runnables``... remove `runnable callbacks` from `handler` on `touch up` – user924 Apr 14 '18 at 12:03
  • While this answer is totally correct, it can be optimized a bit. Check my answer for a simplified version. – jmart Aug 14 '19 at 16:36
10

While the accepted answer is totally correct, it can be simplified a bit.

Basically, we can optimize two things:

  • We don't need the OnTouchListener.
  • We can instantiate the runnable object just once instead of creating multiple objects.

So this is my version:

// global variables
Handler handler = new Handler();
Runnable runnable;

increaseView.setOnLongClickListener(new View.OnLongClickListener() {

    @Override
    public boolean onLongClick(View v) {

        runnable = new Runnable() {
            @Override
            public void run() {
                if (!increaseView.isPressed()) return;
                increaseValue();
                handler.postDelayed(runnable, DELAY);
            }
        };

        handler.postDelayed(runnable, DELAY);
        return true;

    }

});

Here the runnable object is reused. And when the view is not pressed anymore, it will stop calling itself.

The decrease view or button can be defined in a similar way.

jmart
  • 2,769
  • 21
  • 36
9

I am late to answer this question but it may help any one who's looking for a better answer.

I created a CounterHandler class and its insanely easy to use to achieve the above mentioned continuous counter functionality.

You can find the class in following gist with a "how to use" example. https://gist.github.com/nomanr/d142f4ccaf55ceba22e7f7122b55b9b6

Sample code

    new CounterHandler.Builder()
            .incrementalView(buttonPlus)
            .decrementalView(buttonMinus)
            .minRange(-50) // cant go any less than -50
            .maxRange(50) // cant go any further than 50
            .isCycle(true) // 49,50,-50,-49 and so on
            .counterDelay(200) // speed of counter
            .counterStep(2)  // steps e.g. 0,2,4,6...
            .listener(this) // to listen counter results and show them in app
            .build();

Thats all. :)

Noman Rafique
  • 3,735
  • 26
  • 29
5

My way to increment value on long click is to use Timer used to check periodically if button is still pressed and than increase value, otherwise cancel timer. To update UI use Handler.

vh.bttAdd.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {

            final Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                 if(vh.bttAdd.isPressed()) {
                     final int track = ((ChannelAudioTrack) channels.get(vh.getAdapterPosition())).goToNextTrack();
                  updateUI(vh,track);
                 }
                else
                timer.cancel();
            }
            },100,200);

            return true;
        }
    });

Handler:

private void updateUI(final TrackViewHolder vh, final int track)
 {
new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                                  vh.tvTrackNumber.setText(Integer.toString(track));
                        }
                    }) ;
}
2

Just wanna share my own solution that worked out for me really well.

First, create a handler in your activity

private Handler mHandler = new Handler();

Then, create the runnables that will increment/decrement and display your number. In here, we will check if your button is still in its pressed state and increment then re-run the runnable if it is.

private Runnable incrementRunnable = new Runnable() {
    @Override
    public void run() {
        mHandler.removeCallbacks(incrementRunnable); // remove our old runnable, though I'm not really sure if this is necessary
        if(IncrementButton.isPressed()) { // check if the button is still in its pressed state
            // increment the counter
            // display the updated value here, if necessary
            mHandler.postDelayed(incrementRunnable, 100); // call for a delayed re-check of the button's state through our handler. The delay of 100ms can be changed as needed.
        }
    }
}

Finally, use it in our button's onLongClickListener

IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View view) {
        mHandler.postDelayed(incrementRunnable, 0); // initial call for our handler.
        return true;
    }
});

That's it!


Another way of doing it is declaring both the handler and runnable inside the OnLongClickListener itself although I myself am not sure if this is a good practice.

IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
    private Handler mHandler = Handler();
    private Runnable incrementRunnable = new Runnable() {
        @Override
        public void run() {
            mHandler.removeCallbacks(incrementRunnable);
            if(IncrementButton.isPressed()) {
                // increment the counter
                // display the updated value here, if necessary
                mHandler.postDelayed(incrementRunnable, 100);
            }
        }
    };

    @Override
    public boolean onLongClick(View view) {
        mHandler.postDelayed(incrementRunnable, 0);
        return true;
    }
});

When doing this continuous increment, I would suggest to increase the increment value after a certain time/number of increments. E.g. If the number_of_increment made is less than 10, we increment by 1. Otherwise, we increment by 3.

Gama the Great
  • 225
  • 4
  • 16
1

It seems there is no perfect solution to this question and there will always be some complexity involved.

This is my attempt, which incorporates Wiktor's answer, but gives a whole MainActivity that you can cut/paste.

In my example, the complex part is the onLongClickListener, and how deep it goes and how many levels of anonymous classes there are.

However, on the other hand, the simplicity is that everything is included in one relatively short class (MainActivity), and there is only one major block of code -- the onLongClickListener -- which is defined only once and it's very clear where the "action" code is:

package com.example.boober.aalongclickoptimizationunit;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    TextView valueDisplay;
    Button minusButton;
    Button plusButton;
    Button[] arrayOfControlButtons;

    Integer currentDisplayValue = 500;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        valueDisplay = findViewById(R.id.value);
        minusButton = findViewById(R.id.minusButton);
        plusButton = findViewById(R.id.plusButton);

        arrayOfControlButtons = new Button[]{plusButton, minusButton}; // this could be a large set of buttons

        updateDisplay(); // initial setting of display

        for (Button b : arrayOfControlButtons) {

            b.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(final View v) {

                    final Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            if (v.isPressed()) { // important: checking if button still pressed
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        // --------------------------------------------------
                                        // this is code that runs each time the
                                        // long-click timer "goes off."
                                        switch (v.getId()) {

                                            // which button was pressed?
                                            case R.id.plusButton: {
                                                currentDisplayValue = currentDisplayValue + 10;
                                                break;
                                            }

                                            case R.id.minusButton: {
                                                currentDisplayValue = currentDisplayValue - 10;
                                                break;
                                            }
                                        }
                                        updateDisplay();
                                        // --------------------------------------------------
                                    }
                                });
                            } else
                                timer.cancel();
                        }
                    }, 100, 200);
                    // if set to false, then long clicks will propagate into single-clicks
                    // also, and we don't want that.
                    return true;
                }
            });

        }


    }

    // ON-CLICKS (referred to from XML)

    public void minusButtonPressed(View ignored) {
        currentDisplayValue--;
        updateDisplay();
    }

    public void plusButtonPressed(View ignored) {
        currentDisplayValue++;
        updateDisplay();
    }

    // INTERNAL

    private void updateDisplay() {
        valueDisplay.setText(currentDisplayValue.toString());
    }


}
Nerdy Bunz
  • 6,040
  • 10
  • 41
  • 100
1

for kotlin user

    myButton.setOnLongClickListener {
        val handler = Handler(Looper.myLooper()!!)
        val runnable : Runnable = object : Runnable {
            val number = 0
            override fun run() {
                handler.removeCallbacks(this)
                if (myButton.isPressed) {
                    val newNumber= number + 1
                    textView.text = "$newNumber Items"
                    handler.postDelayed(this, 100)
                }
            }
        }
        handler.postDelayed(runnable,0)
        true
    }
Tarif Chakder
  • 1,708
  • 1
  • 11
  • 10
1

Version for Kotlin and Coroutines users (replace GlobalScope with any other scope if you'd like):

    var job: Job? = null

    viewClickable.setOnClickListener {
        // single click
    }

    viewClickable.setOnLongClickListener {
        if (job == null || !job!!.isActive) {
            job = GlobalScope.launch(Dispatchers.Main.immediate) {
                while (it.isPressed) {
                    // long press
                    delay(100)
                }
            }
        }
        true
    }
Vasiliy
  • 16,221
  • 11
  • 71
  • 127
0

import java.awt.; import java.awt.event.; public class LabelNumber extends Frame implements ActionListener {

    Button badd,bsub;
    TextField t1;
    
void display()
{
    setTitle("Label number change");
    setSize(400,500);
    setLayout(new FlowLayout());
    setVisible(true);

    badd=new Button("+");
    t1=new TextField("0",6);

    bsub= new Button("-");
    add(bsub);add(t1);add(badd);
  

badd.addActionListener(this);
bsub.addActionListener(this);

addWindowListener(new WindowAdapter()
{
    public void windowClosing(WindowEvent e)
    {
        System.exit(0);
    }
}
);

}

public void actionPerformed(ActionEvent e)
    {
    int count=0,add=0,sub,n1,n2;
        count=Integer.parseInt(t1.getText());
            
    if(e.getSource()==badd)
    {
        if(count<10)
        {
            count=count+1;
            t1.setText(""+count);
        }
        else
        {
            t1.setText("limit:10");
        }               
    }
    
    if(e.getSource()==bsub)
    {
        if(count<10)
        {
            count=count-1;
            t1.setText(""+count);
        }
        else
        {
            t1.setText("limit:10");
        }               
    }
}


public static void main(String args[])
{
    LabelNumber obj =new  LabelNumber();
    obj.display();

}

}

  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Dima Kozhevin Jul 29 '20 at 15:23
0

initlize and call method

int speed = 0;
 button = findViewById(R.id.button);
    button.setOnTouchListener((v, event) -> {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            v.setPressed(true);
                    increment();
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            v.setPressed(false);
            speed = 0;
         }
        return true;
    });

This is increase method

    public void increment() {
        new Handler().postDelayed(() -> {
            Toast.makeText(FrequencyActivity.this, String.valueOf(speed), Toast.LENGTH_SHORT).show();
            if (button.isPressed()) {
                speed += 1;
                increment();
            }
        }, 200); //200 ms for fast incrementing
    }
Meet Bhavsar
  • 442
  • 6
  • 12
0

To break this task into its basic requirements, this is what we need:

  1. Function to be executed
  2. Condition for re-execution (as function that can be called each time to check condition)
  3. Delay before re-execution

Here is a function that can be copied into Utils class, and be called for anything that matches these requirements:

/**
 * Execute given function, and if condition is met, re-execute recursively after delay
 * @param function: function to be executed
 * @param conditionToRepeat: condition to re-execute function
 * @param delayMillis: delay after which function should be re-executed (if condition was met)
 */
fun executeRecursively(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long) {
    function()
    if (conditionToRepeat())
        Handler(Looper.getMainLooper()).postDelayed(
               { executeRecursively(function, conditionToRepeat, delayMillis) }, 
               delayMillis)

}

Example of usage for requested use-case:

binding.button.setOnLongClickListener {
                executeRecursively( { increase() }, // function
                                    { binding.button.isPressed }, // condition to re-execute
                                    DELAY // delay in millis before re-execution
                                  )
                true
            }

This is all what you need. But many cases, you may want to decrease delay when long clicking, so the number will be increased/decreased faster. Here is an extended function for that case:

/**
 * Execute given function, and if condition is met, re-execute recursively after delay
 * @param function: function to be executed
 * @param conditionToRepeat: condition to re-execute function
 * @param delayMillis: delay after which function should be re-executed (if condition was met)
 * @param minDelayMillis: minimal delay in milliseconds
 * @param decreasingDelayMillis: amount to decrease delay for next re-execution (if minimal delay has not been reached)
 */
fun executeRecursivelyWithDecreasingDelay(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long, minDelayMillis: Long, decreasingDelayMillis: Long) {
    function()
    if (conditionToRepeat()) {
        val delay = if (delayMillis <= minDelayMillis) minDelayMillis else delayMillis - decreasingDelayMillis
        Handler(Looper.getMainLooper()).postDelayed(
            { executeRecursivelyWithDecreasingDelay(function, conditionToRepeat, delay, minDelayMillis, decreasingDelayMillis) },
            delayMillis
        )
    }
}

In this function, just add minimal delay (for example 2 millis), and the rate in which delay should be decreased each time (for example 2 millis). decreasingDelayMillis is like delay negative velocity.

Amir Golan
  • 378
  • 3
  • 8
-1

Best and easy solution i created ,check it out it works for me

public void increment() {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            check_amount = check_amount + 100;// increment by 100
            tv_balance.setText("" + check_amount); // show continues incrementing value
            if (check_amount != 5000) {
                increment();
            }
        }
    }, 1); //1 ms for fast incrementing
}