12

The default spinner behavior is that when it's "closed", longpressing on it will "open" it and show you the dropdown view. I find this behavior to be potentially very problematic for the user. For example, if they're trying to scroll something on the screen, and happen to "grab" a spot that has a spinner, then instead of scrolling, it'll open the dropdown view after a second or so, and the user is basically left with their finger on one of the dropdown options (which they can now accidentally press).

So, I'd like to disable that long-press behavior, and have the spinner "open" when only it's clicked, and not long-pressed. Is that possible?

user496854
  • 6,461
  • 10
  • 47
  • 84

4 Answers4

8

If you only need to restore the standard click behaviour, this is a simpler and more compact version of Jordi's answer:

spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                v.performClick();
            }
            return true;
        }
    });

Further thoughts

This behaviour has also been annoying me. After investigation, it seems like it has a name: drag to open. You can see how it's defined in the source of the AppCompatSpinner#onTouchEvent() method.

Some problems I see with this forced behaviour (and the reason for which some people want to disable it):

  1. It allows the user to selects disabled values in the spinner. By long-pressing, dragging and releasing, you can select values which would not even be selectable with a normal interaction (click-to-open + click-to-select)
  2. As a consequence of #1, it can also make Espresso test fail very easily. Indeed, in Espresso, the duration of a click is rather unstable and one tap can quickly turn into a long-press-and-select-interaction.
  3. Finally, the biggest problem here is that there is no method / XML attribute to disable the "drag to open" behaviour...

Let's have it fixed!

I've opened a ticket related to this on the AOSP issue tracker: #228953. Feel free to follow it, or to comment if I missed anything.

Community
  • 1
  • 1
David Ferrand
  • 5,357
  • 1
  • 33
  • 43
3

So, I've figured out a relatively easy way to do this, even though it's not very elegant. Basically, I created a transparent overlay view on top of the Spinner, and set it to have an OnClickListener that just triggers a Spinner's click.

XML:

    <Spinner 
        android:id="@+id/spinner"
        android:layout_width="match_parent" 
        android:layout_height="40dip" />

    <View android:id="@+id/spinner_overlay"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/spinner" 
        android:layout_alignRight="@id/spinner"
        android:layout_alignTop="@id/spinner" 
        android:layout_alignBottom="@id/spinner"  />

Java:

        View spinnerOverlay = findViewById(R.id.spinner_overlay);
        spinnerOverlay.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                spinner.performClick();
            }

        });
user496854
  • 6,461
  • 10
  • 47
  • 84
0

Have you tried android:longClickable="false" in xml yet?

or try something like this:

spinner.setOnTouchListener(new View.OnTouchListener() {
        private static final int MAX_CLICK_DISTANCE = 15;
        private static final int MAX_CLICK_DURATION = 1000;
        private long pressStartTime;
        private float pressedX;
        private float pressedY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    pressStartTime = System.currentTimeMillis();
                    pressedX = event.getX();
                    pressedY = event.getY();
                    break;
                }
                case MotionEvent.ACTION_UP: {
                long pressDuration = System.currentTimeMillis() - pressStartTime;
                if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, event.getX(), event.getY()) < MAX_CLICK_DISTANCE) {
                     //when long clicked.
                }
                break;
            }
        }
        return false;
}
Jordi Sipkens
  • 595
  • 1
  • 9
  • 25
  • If you override a spinner's OnTouchListener, it stops working altogether – user496854 Jan 13 '15 at 18:36
  • Not with mine, I use this function on a imageslider. The sliding controls from the super still work, only when I click under 1 second and x range it will trigger an onClick. If it doesn't fit this range it will just use the parent function. **Try pressDuration > MAX_CLICK_DURATION. This will do nothing when you press longer then 1 second (LongClick).** – Jordi Sipkens Jan 14 '15 at 08:49
  • it has nothing to do with duration -- you're overriding the OnTouchListener, and you're not processing any events that would normally be there. – user496854 Jan 14 '15 at 15:56
0

A little late, but maybe still useful for someone:

Setting an OnLongClickListener that returns true in onLongClick prevents an (unintentional) opening of the Spinner, because it works as if the callback consumed the long click as stated in the documentation.

spinner.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View view) {
        return true;
    }
});

The only downside is, that the spinner would still have the onTouch animation when long clicked.

c7n
  • 1,131
  • 16
  • 29