Search code examples
androidandroid-studiokotlinontouchlistener

Increase value of text when button is hold


I have a plus and min button that work when pressed. Now I want to make them when you hold/press it down it goes up/down more then 1 at a time.

This is one of my regular buttons:

plusBtn.setOnClickListener {
        if(isEmpty(PenaltyTimeInputTxt.text))
        {
            PenaltyTimeInputTxt.setText("0")
        }
        penaltyInput = PenaltyTimeInputTxt.text.toString().toInt()
        if(penaltyInput < 99){
            penaltyInput++
            PenaltyTimeInputTxt.setText(penaltyInput.toString())
        }
        else {
            Toast.makeText(this, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
        }
    }

is there a simple way of doing this? I saw something about onTouchListener.

EDIT --- End result. Thanks to Tenfour04: include the whole fun view.doWhileHeld + this:

plusBtn.doWhileHeld(this.lifecycleScope) {
        if(isEmpty(PenaltyTimeInputTxt.text)) {
            PenaltyTimeInputTxt.setText("0")
        }
        penaltyInput = PenaltyTimeInputTxt.text.toString().toInt()
        while (isActive) {
            if(penaltyInput < 99) {
                penaltyInput++
                PenaltyTimeInputTxt.setText(penaltyInput.toString())
            }
            else {
                Toast.makeText(this@PenaltyConfigureActivity, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
                break
            }
            delay(200)
        }
    }

Solution

  • Here is a helper class and function for this, which lets you do whatever you want while the button is held down:

    fun View.doWhileHeld(
        coroutineScope: CoroutineScope,
        block: suspend CoroutineScope.() -> Unit
    ) = setOnTouchListener(object : View.OnTouchListener {
        var job: Job? = null
        var pointerInBounds = false
    
        @SuppressLint("ClickableViewAccessibility")
        override fun onTouch(view: View, event: MotionEvent): Boolean {
            if (!isEnabled) {
                job?.cancel()
                return false
            }
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    job = coroutineScope.launch(block = block)
                    pointerInBounds = true
                }
                MotionEvent.ACTION_MOVE -> {
                    val movedInBounds = event.x.roundToInt() in 0..view.width
                            && event.y.roundToInt() in 0..view.height
                    if (pointerInBounds != movedInBounds) {
                        pointerInBounds = movedInBounds
                        if (pointerInBounds) {
                            job = coroutineScope.launch(block = block)
                        } else {
                            job?.cancel()
                        }
                    }
                }
                MotionEvent.ACTION_UP -> {
                    job?.cancel()
                }
            }
            return false // allow click interactions
        }
    })
    

    It runs a coroutine that restarts every time you click and hold. It also stops the coroutine and restarts it if you drag off the button and then back on, which is a conventional UI behavior.

    To use it for your behavior, you can use a while loop:

    plusBtn.doWhileHeld(viewLifecycleOwner.lifecycleScope) {
        if(isEmpty(PenaltyTimeInputTxt.text)) {
            PenaltyTimeInputTxt.setText("0")
        }
        penaltyInput = PenaltyTimeInputTxt.text.toString().toInt()
        while (isActive) {
            if(penaltyInput < 99) {
                penaltyInput++
                PenaltyTimeInputTxt.setText(penaltyInput.toString())
                if (penaltyInput == 99) { // optional, might be nicer than showing toast
                    plusBtn.isEnabled = false 
                    break
                }
            }
            else {
                Toast.makeText(this, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
                break
            }
            delay(500) // adjust for how fast to increment the value
        }
    }