Home > database >  How to handle simultaneous Click Events from multiple Views?
How to handle simultaneous Click Events from multiple Views?

Time:08-31

I have multiple views with Click Listeners and if user clicks on two more views at a time then all actions get performed. I want one action to perform and throttle all other events.

What could be the best practice to do that?

Currently I am handling by firing events to RxJava2 Subject and use debounce(700ms) with observer and perform action based on events.

CodePudding user response:

Unless I'm missing something it just sounds like an organisational thing really, that you'd have to design yourself. It's just about designing the behaviour of your app - if you click a thing, some kind of navigation event happens. You want to disable that behaviour after the first click (and enable it again sometime later).

The most basic version of this is making your click listeners conditional

// in your Fragment or whatever
private var navigationAllowed = true

// setting up your click listeners
button1.setOnClickListener {
    if (navigationAllowed) {
        navigationAllowed = false
        doNavigation1()
    }
}

button1.setOnClickListener {
    if (navigationAllowed) {
        navigationAllowed = false
        doNavigation2()
    }
}

so as soon as one is clicked, the latch sets and further clicks won't do anything.

You can be a bit smarter about it and disable the buttons, which is better from a usability/accessibility perspective (since you want the user to understand the button isn't clickable anymore):

// group all your buttons for convenience
val buttons = listOf(button1, button2)

// a function that handles all the buttons
fun handleClick(button: Button) {
    if (!navigationAllowed) return
    if (button !in buttons) return

    // it's a valid click, so disable everything
    navigationAllowed = false
    buttons.forEach { it.enabled = false }
    when(button) {
        button1 -> doNavigation1()
        button2 -> doNavigation2()
    }
}

// set up your click listeners
buttons.forEach { button ->
    button.setOnClickListener { handleClick(button) }
}

There are more elegant ways to do it but that should give you the idea anyway.


A better way to do it is with a ViewModel that handles the click events, and publishes the current state. That way it can drop click events if appropriate

class ScreenViewModel : ViewModel() {

    private val _currentScreen = MutableLiveData<Screen>()
    val currentScreen: LiveData<Screen> get() = _currentScreen

    private var allowNavigation = true

    fun navigateToScreen(screen: Screen) {
        // this can hold an internal flag, or whatever state you want
        // to represent that further clicks should be ignored.
        if (allowNavigation) {
            allowNavigation = false
            // update the current state, so observers will see the event
            _currentScreen.value = screen
        }
    }

    enum class Screen { SCREEN1, SCREEN2 }
}

Then you just have to wire that up

// let's assume the Activity is responsible for handling navigation / showing screens
val screenModel: ScreenViewModel by viewModels()

screenModel.observe(this) { screen -> // do whatever navigation }

// in your Fragment or whatever has the buttons
val screenModel: ScreenModel by activityViewModels()

button1.setOnClickListener { screenModel.navigateToScreen(SCREEN1) }
button2.setOnClickListener { screenModel.navigateToScreen(SCREEN2) }

So the buttons push an event, the ViewModel publishes it or drops it, and the thing that does the navigating only gets events when they're allowed through, so it just reacts to all of them. This is similar to what you can use Rx for, so if this makes sense maybe you can wire it into what you've got

CodePudding user response:

You can't click two or more views at the same time- the way that touch event handling works is that when the first touch goes down, the touch event is dispatched to that view. After that, all future touch events also go to that view, until the touch is ended (all fingers are up). This means only 1 view gets all those events, so only one view will be clicked.

You may have meant multiple views being clicked in quick succession, in which case there's nothing built in to prevent that, and you'd need to add that logic yourself. How you'd do that really depends on what you want the end effect to be- handle the first and drop all the others? If so a simple variable keeping the time of the last click and ignoring anything for X ms afterwards is enough. Handle each one in the order received? Adding the events to a queue would work, popping it as you handle each one. Something else? We'd need to know what.

  • Related