Setting a spinner onClickListener() in Android
Asked Answered
L

7

41

I'm trying to get an onClickListener to fire on a Spinner, but I get the following error:

Java.lang.RuntimeException is "Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead,"

I'm sure I want to call onClickListener and NOT onItemClickListener. I found a question asked by someone else on Stack Overflow, Is there a way to use setOnClickListener with an Android Spinner?

The answer stated there is:

You will have to set the Click listener on the underlying view (normally a TextView with id: android.R.id.text1) of the spinner. To do so:

Create a custom Spinner In the constructor (with attributes) create the spinner by supplying the layout android.R.layout.simple_spinner_item Do a findViewById(android.R.id.text1) to get the TextView Now set the onClickListener to the TextView

I have tried the answer noted there, but it doesn't seem to work. I get a null pointer to the TextView after I do the findViewById().

This is what I'm doing:

Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.layoutspinner,dataArray);

spinner.setAdapter(adapter);

TextView SpinnerText = (TextView)findViewById(R.id.spinnerText);
if (SpinnerText == null) {
    System.out.println("Not found");
}
else {
    SpinnerText.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View arg0) {
            //Do something
        }
    });
}

File layoutspinner.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
                  android:id="@+id/spinnerText"
                  android:singleLine ="true"
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:textSize="6pt"
                  android:gravity="right"/>

What am I doing wrong?

I'm new to Stack Overflow, I didn't find any way to post an aditional question to the other thread (or comment since I have to little rep) so I started a new question.

Per recomendation I tried this:

int a = spinnerMes.getCount();
int b = spinnerMes.getChildCount();
System.out.println("Count = " + a);
System.out.println("ChildCount = " + b);
for (int i = 0; i < b; i++) {
    View v = spinnerMes.getChildAt(i);
    if (v == null) {
        System.out.println("View not found");
    }
    else {
        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Click code
            }
        });
    }
}

But LogCat isn't showing promising results.

10-14 16:09:08.127: INFO/System.out(3116): Count = 7
10-14 16:09:08.127: INFO/System.out(3116): ChildCount = 0

I have tested this on API levels 7 and 8 with the same results.

Lifelong answered 13/10, 2010 at 21:15 Comment(0)
C
5

The following works how you want it, but it is not ideal.

public class Tester extends Activity {

    String[] vals = { "here", "are", "some", "values" };
    Spinner spinner;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        spinner = (Spinner) findViewById(R.id.spin);
        ArrayAdapter<String> ad = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, vals);
        spinner.setAdapter(ad);
        Log.i("", "" + spinner.getChildCount());
        Timer t = new Timer();
        t.schedule(new TimerTask() {

            @Override
            public void run() {
                int a = spinner.getCount();
                int b = spinner.getChildCount();
                System.out.println("Count =" + a);
                System.out.println("ChildCount =" + b);
                for (int i = 0; i < b; i++) {
                    View v = spinner.getChildAt(i);
                    if (v == null) {
                        System.out.println("View not found");
                    } else {
                        v.setOnClickListener(new View.OnClickListener() {

                            @Override
                            public void onClick(View v) {
                                        Log.i("","click");
                                        }
                        });
                    }
                }
            }
        }, 500);
    }
}

Let me know exactly how you need the spinner to behave, and we can work out a better solution.

Cornetist answered 13/10, 2010 at 21:47 Comment(11)
I belive I tried this today with no success, but I'll report back once I try again just to be sure.Lifelong
I just checked the code, I tried that and im still getting a null reference in return. Any ideas?Lifelong
Edited my question, not quite there yet, but thanx for helping out, im really lost here.Lifelong
the problem with the code i posted is that the spinner doesn't actual get any items until the screen renders which is after onCreate is executed. if you move the loop into a timer which fires after the view is rendered it works. however the problem is that the spinner only ever has 1 child. when you select the spinner a dialog os spawned so that you can select a value. when the value is changed a new child is created and the event you added wil be lost. If you could tell me specifically how you need the apinner to behave we may be able to come up with a more elegant solution.Cornetist
That does work, but the problem you are describing is a deal breaker. Let me go back to the problem that brought me to needing this. I have a screen with 2 spinners, as I'm pretty sure you are aware off, spinners fire off the onItemSelected when created. Both onItemSelected on this activity start network request, so my solution was to use a simple boolean flag, lets call it spinnerClicked. The onItemClickedListener looks if the spinnerClicked is true, and does the data fetch, otherwise it just ignores it.Lifelong
The idea was to set the onClick for the spinner to set the flag to true, and after the onItemSelected fired, set it to false. This solves not firing 2 worker threads, and also keeps from re-downloading the data on screen rotation. So basically I just wanted to, onClick, set a flag to true.Lifelong
why not just initialize the boolean to true and then set it to false after the first click?Cornetist
I want it to load the data when ever a new options is selected from either spinner, but keep it from firing upon rotation. A "obtain data" button would solve this but its really a last resort thing.Lifelong
create a dictionary of booleans in oncreate with a value for each item in the spinner. in the onitemSelect listener get the value of the selected item and then check the dictionary to see if the download has been completedCornetist
I did something similar to a dictionary, and while the solution is far from elegant it works, thaks for the help Dave. I would up this, but im still a couple of points away from being able.Lifelong
Using count() as the indexing basis gets the elements, but they're coming back as strings so you can't instrument the clicking.Vibrations
S
63

Here is a working solution:

Instead of setting the spinner's OnClickListener, we are setting OnTouchListener and OnKeyListener.

spinner.setOnTouchListener(Spinner_OnTouch);
spinner.setOnKeyListener(Spinner_OnKey);

and the listeners:

private View.OnTouchListener Spinner_OnTouch = new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            doWhatYouWantHere();
        }
        return true;
    }
};
private static View.OnKeyListener Spinner_OnKey = new View.OnKeyListener() {
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            doWhatYouWantHere();
            return true;
        } else {
            return false;
        }
    }
};
Sammer answered 15/12, 2011 at 13:24 Comment(4)
When I ran this code I could not access the selected item - it would only give me the previously selected item??? Did this happen to you?Madaih
WaterBoy, This behavior is as expected since these callbacks are executed before Android processes the events. For my app, I didn't need the selected item, so I can't help you here. Sorry.Sammer
You may want to always return false if the spinner has to show the dropdown items anyway.Delaware
That's all good, but by registering the OnTouchListener you seem to lose the focus event on the Spinner, which means you don't see the nice underline highlighting etc. Is there a way to retain that while still applying your solution?Sleekit
P
32

Whenever you have to perform some action on the click of the Spinner in Android, use the following method.

mspUserState.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            doWhatIsRequired();
        }
        return false;
    }
});

One thing to keep in mind is always to return False while using the above method. If you will return True then the dropdown items of the spinner will not be displayed on clicking the Spinner.

Porshaport answered 4/10, 2013 at 7:34 Comment(1)
I understand what you say. Yet if you are trying for example to display an alert dialog instead of the dropdown items, you have to return trueSonora
M
15

Personally, I use that:

    final Spinner spinner = (Spinner) (view.findViewById(R.id.userList));
    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {                

            userSelectedIndex = position;
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });  
Marris answered 2/4, 2014 at 9:9 Comment(0)
M
6

First of all, a spinner does not support item click events. Calling this method will raise an exception.

You can use setOnItemSelectedListener:

Spinner s1;
s1 = (Spinner)findViewById(R.id.s1);
int selectionCurrent = s1.getSelectedItemPosition();

spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
            if (selectionCurrent != position){
                // Your code here
            }
            selectionCurrent= position;
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parentView) {
        // Your code here
    }
});
Millsaps answered 25/9, 2012 at 10:1 Comment(0)
C
5

The following works how you want it, but it is not ideal.

public class Tester extends Activity {

    String[] vals = { "here", "are", "some", "values" };
    Spinner spinner;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        spinner = (Spinner) findViewById(R.id.spin);
        ArrayAdapter<String> ad = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, vals);
        spinner.setAdapter(ad);
        Log.i("", "" + spinner.getChildCount());
        Timer t = new Timer();
        t.schedule(new TimerTask() {

            @Override
            public void run() {
                int a = spinner.getCount();
                int b = spinner.getChildCount();
                System.out.println("Count =" + a);
                System.out.println("ChildCount =" + b);
                for (int i = 0; i < b; i++) {
                    View v = spinner.getChildAt(i);
                    if (v == null) {
                        System.out.println("View not found");
                    } else {
                        v.setOnClickListener(new View.OnClickListener() {

                            @Override
                            public void onClick(View v) {
                                        Log.i("","click");
                                        }
                        });
                    }
                }
            }
        }, 500);
    }
}

Let me know exactly how you need the spinner to behave, and we can work out a better solution.

Cornetist answered 13/10, 2010 at 21:47 Comment(11)
I belive I tried this today with no success, but I'll report back once I try again just to be sure.Lifelong
I just checked the code, I tried that and im still getting a null reference in return. Any ideas?Lifelong
Edited my question, not quite there yet, but thanx for helping out, im really lost here.Lifelong
the problem with the code i posted is that the spinner doesn't actual get any items until the screen renders which is after onCreate is executed. if you move the loop into a timer which fires after the view is rendered it works. however the problem is that the spinner only ever has 1 child. when you select the spinner a dialog os spawned so that you can select a value. when the value is changed a new child is created and the event you added wil be lost. If you could tell me specifically how you need the apinner to behave we may be able to come up with a more elegant solution.Cornetist
That does work, but the problem you are describing is a deal breaker. Let me go back to the problem that brought me to needing this. I have a screen with 2 spinners, as I'm pretty sure you are aware off, spinners fire off the onItemSelected when created. Both onItemSelected on this activity start network request, so my solution was to use a simple boolean flag, lets call it spinnerClicked. The onItemClickedListener looks if the spinnerClicked is true, and does the data fetch, otherwise it just ignores it.Lifelong
The idea was to set the onClick for the spinner to set the flag to true, and after the onItemSelected fired, set it to false. This solves not firing 2 worker threads, and also keeps from re-downloading the data on screen rotation. So basically I just wanted to, onClick, set a flag to true.Lifelong
why not just initialize the boolean to true and then set it to false after the first click?Cornetist
I want it to load the data when ever a new options is selected from either spinner, but keep it from firing upon rotation. A "obtain data" button would solve this but its really a last resort thing.Lifelong
create a dictionary of booleans in oncreate with a value for each item in the spinner. in the onitemSelect listener get the value of the selected item and then check the dictionary to see if the download has been completedCornetist
I did something similar to a dictionary, and while the solution is far from elegant it works, thaks for the help Dave. I would up this, but im still a couple of points away from being able.Lifelong
Using count() as the indexing basis gets the elements, but they're coming back as strings so you can't instrument the clicking.Vibrations
D
2

I suggest that all events for Spinner are divided on two types:

  1. User events (you meant as "click" event).

  2. Program events.

I also suggest that when you want to catch user event you just want to get rid off "program events". So it's pretty simple:

private void setSelectionWithoutDispatch(Spinner spinner, int position) {
    AdapterView.OnItemSelectedListener onItemSelectedListener = spinner.getOnItemSelectedListener();
    spinner.setOnItemSelectedListener(null);
    spinner.setSelection(position, false);
    spinner.setOnItemSelectedListener(onItemSelectedListener);
}

There's a key moment: you need setSelection(position, false). "false" in animation parameter will fire event immediately. The default behaviour is to push event to event queue.

Dutcher answered 15/2, 2015 at 22:37 Comment(0)
G
1

The Spinner class implements DialogInterface.OnClickListener, thereby effectively hijacking the standard View.OnClickListener.

If you are not using a sub-classed Spinner or don't intend to, choose another answer.

Otherwise just add the following code to your custom Spinner:

@Override
/** Override triggered on 'tap' of closed Spinner */
public boolean performClick() {
    // [ Do anything you like here ]
    return super.performClick();
}

Example: Display a pre-supplied hint via Snackbar whenever the Spinner is opened:

private String sbMsg=null;      // Message seen by user when Spinner is opened.
public void setSnackbarMessage(String msg) { sbMsg=msg; }
@Override
/** Override triggered on 'tap' of closed Spinner */
public boolean performClick() {
    if (sbMsg!=null && !sbMsg.isEmpty()) { /* issue Snackbar */ }
    return super.performClick();
}

Trapping 'click' of closed Spinner

A custom Spinner is a terrific starting point for programmatically standardising Spinner appearance throughout your project.

If interested, looky here

Gerrit answered 30/10, 2018 at 3:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.