简体   繁体   中英

Setting a spinner onClickListener() in Android

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.

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;
        }
    }
};

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.

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) {
        }
    });  

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
    }
});

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.

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.

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();
}

捕获关闭微调器的“点击”

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

If interested, looky here

Kind of hack, but enough for quick implementation

public class ClickableSpinner extends AppCompatSpinner {

private OnClickListener listener;

public interface OnClickListener {
    void onClick();
}
public ClickableSpinner(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public void setListener(OnClickListener listener) {
    this.listener = listener;
}

@Override
public boolean performClick() {
    listener.onClick();
    return true;
}

}

spinner.setListener(() -> {
       //show you custom dialog
    });

Of course don't forget to declare it in XML

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM