问题
Is it possible to know whether a Spinner
is open or closed? It would even be better if there was some sort of onOpenListener for Spinners.
I've tried using an OnItemSelectedListener like this:
spinnerType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
executeSomething();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
Log.d("nothing" , "selected");
}
});
I can know that the window will close if something is selected (in executeSomething()). But I don't get notified if I click outside of the Dialog, which also closes the spinner
回答1:
Another option to watch for those events is to extend the Spinner
class and use one of its methods(performClick()
which will trigger its dialog/popup) followed by monitoring the focus of the window holding this custom Spinner
. This should provide you with the wanted closed event for all the possible finishing possibilities(for either the dialog or dropdown mode).
The custom Spinner
class:
public class CustomSpinner extends Spinner {
/**
* An interface which a client of this Spinner could use to receive
* open/closed events for this Spinner.
*/
public interface OnSpinnerEventsListener {
/**
* Callback triggered when the spinner was opened.
*/
void onSpinnerOpened(Spinner spinner);
/**
* Callback triggered when the spinner was closed.
*/
void onSpinnerClosed(Spinner spinner);
}
private OnSpinnerEventsListener mListener;
private boolean mOpenInitiated = false;
// implement the Spinner constructors that you need
@Override
public boolean performClick() {
// register that the Spinner was opened so we have a status
// indicator for when the container holding this Spinner may lose focus
mOpenInitiated = true;
if (mListener != null) {
mListener.onSpinnerOpened(this);
}
return super.performClick();
}
@Override
public void onWindowFocusChanged (boolean hasFocus) {
if (hasBeenOpened() && hasFocus) {
performClosedEvent();
}
}
/**
* Register the listener which will listen for events.
*/
public void setSpinnerEventsListener(
OnSpinnerEventsListener onSpinnerEventsListener) {
mListener = onSpinnerEventsListener;
}
/**
* Propagate the closed Spinner event to the listener from outside if needed.
*/
public void performClosedEvent() {
mOpenInitiated = false;
if (mListener != null) {
mListener.onSpinnerClosed(this);
}
}
/**
* A boolean flag indicating that the Spinner triggered an open event.
*
* @return true for opened Spinner
*/
public boolean hasBeenOpened() {
return mOpenInitiated;
}
}
回答2:
based on @Luksprog wonderful solution,i just want to add a small change which will be very helpful in case someone is using the CustomSpinner inside a fragment.
instead of using the Activity.onWindowFocusChanged
function, we override the View.onWindowFocusChanged
function. thus the whole CustomSpinner class become
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Spinner;
public class CustomSpinner extends Spinner {
private static final String TAG = "CustomSpinner";
private OnSpinnerEventsListener mListener;
private boolean mOpenInitiated = false;
public CustomSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
super(context, attrs, defStyleAttr, mode);
}
public CustomSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSpinner(Context context, int mode) {
super(context, mode);
}
public CustomSpinner(Context context) {
super(context);
}
public interface OnSpinnerEventsListener {
void onSpinnerOpened();
void onSpinnerClosed();
}
@Override
public boolean performClick() {
// register that the Spinner was opened so we have a status
// indicator for the activity(which may lose focus for some other
// reasons)
mOpenInitiated = true;
if (mListener != null) {
mListener.onSpinnerOpened();
}
return super.performClick();
}
public void setSpinnerEventsListener(OnSpinnerEventsListener onSpinnerEventsListener) {
mListener = onSpinnerEventsListener;
}
/**
* Propagate the closed Spinner event to the listener from outside.
*/
public void performClosedEvent() {
mOpenInitiated = false;
if (mListener != null) {
mListener.onSpinnerClosed();
}
}
/**
* A boolean flag indicating that the Spinner triggered an open event.
*
* @return true for opened Spinner
*/
public boolean hasBeenOpened() {
return mOpenInitiated;
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
android.util.Log.d(TAG, "onWindowFocusChanged");
super.onWindowFocusChanged(hasWindowFocus);
if (hasBeenOpened() && hasWindowFocus) {
android.util.Log.i(TAG, "closing popup");
performClosedEvent();
}
}
}
回答3:
Hi friends I am struggling on this issue from last two days and finally I got following solution which done my job. I tried and it worked perfectly. Thanks
mSpinner.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
Toast.makeText(MapActivity.this,"down",Toast.LENGTH_LONG).show();
// Load your spinner here
}
return false;
}
});
回答4:
There's no built in function but it's pretty easy to do with an OnTouchListener
and OnItemSelectedListener
.
abstract class OnOpenListener implements OnTouchListener, OnItemSelectedListener {
public OnOpenListener(Spinner spinner) {
spinner.setOnTouchListener(this);
spinner.setOnItemSelectedListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
onOpen();
}
return false;
}
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
onClose();
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
onClose();
}
abstract public void onOpen();
abstract public void onClose();
}
And then assign the appropriate listeners:
OnOpenListener onOpenListener = new OnOpenListener(mySpinner) {
@Override
public void onOpen() {
// spinner was opened
}
@Override
public void onClose() {
// spinner was closed
}
};
回答5:
I think the best way to find when it got opened and closed is this way:
If it was closed, and now it calls "getDropDownView" in the adapter, it can be assumed that it got opened.
If "onItemSelected" or "onNothingSelected" are called, now it got closed.
EDIT: here's a sample code
public class MainActivity extends AppCompatActivity {
boolean isSpinnerClosed = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatSpinner spinner2 = (AppCompatSpinner) findViewById(R.id.spinner2);
List<String> list = new ArrayList<String>();
list.add("list 1");
list.add("list 2");
list.add("list 3");
Log.d("AppLog", "started");
//spinner2.setondi
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list) {
@Override
public View getDropDownView(final int position, @Nullable final View convertView, @NonNull final ViewGroup parent) {
if (isSpinnerClosed) {
Log.d("AppLog", "closed->open");
isSpinnerClosed = false;
}
return super.getDropDownView(position, convertView, parent);
}
};
spinner2.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(final AdapterView<?> adapterView, final View view, final int i, final long l) {
Log.d("AppLog", "onItemSelected");
if (!isSpinnerClosed) {
Log.d("AppLog", "open->closed");
isSpinnerClosed = true;
}
}
@Override
public void onNothingSelected(final AdapterView<?> adapterView) {
Log.d("AppLog", "onNothingSelected");
if (!isSpinnerClosed) {
Log.d("AppLog", "open->closed");
isSpinnerClosed = true;
}
}
});
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner2.setAdapter(dataAdapter);
}
@Override
public void onWindowFocusChanged(final boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && isSpinnerClosed) {
Log.d("AppLog", "open->closed");
isSpinnerClosed = true;
}
}
}
回答6:
I could not find a way to get this behaviour with the spinner so the only thing that worked for me was to use the spinner (custom) adapter instead:
public interface SpinnerListener {
void onSpinnerExpanded();
void onSpinnerCollapsed();
}
Then a custom adapter can be written that just grabs the “spinner expanded” view and adds a listener to it to listen for “expand” and “collapse” events. The custom adapter I used is:
public class ListeningArrayAdapter<T> extends ArrayAdapter<T> {
private ViewGroup itemParent;
private final Collection<SpinnerListener> spinnerListeners = new ArrayList<SpinnerListener>();
public ListeningArrayAdapter(Context context, int resource, T[] objects) {
super(context, resource, objects);
}
// Add the rest of the constructors here ...
// Just grab the spinner view (parent of the spinner item view) and add a listener to it.
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
if (isParentTheListView(parent)) {
itemParent = parent;
addFocusListenerAsExpansionListener();
}
return super.getDropDownView(position, convertView, parent);
}
// Assumes the item view parent is a ListView (which it is when a Spinner class is used)
private boolean isParentTheListView(ViewGroup parent) {
return (parent != itemParent && parent != null && ListView.class.isAssignableFrom(parent.getClass()));
}
// Add a focus listener to listen to spinner expansion and collapse events.
private void addFocusListenerAsExpansionListener() {
final View.OnFocusChangeListener listenerWrapper = new OnFocusChangeListenerWrapper(itemParent.getOnFocusChangeListener(), spinnerListeners);
itemParent.setOnFocusChangeListener(listenerWrapper);
}
// Utility method.
public boolean isExpanded() {
return (itemParent != null && itemParent.hasFocus());
}
public void addSpinnerListener(SpinnerListener spinnerListener) {
spinnerListeners.add(spinnerListener);
}
public boolean removeSpinnerListener(SpinnerListener spinnerListener) {
return spinnerListeners.remove(spinnerListener);
}
// Listener that listens for 'expand' and 'collapse' events.
private static class OnFocusChangeListenerWrapper implements View.OnFocusChangeListener {
private final Collection<SpinnerListener> spinnerListeners;
private final View.OnFocusChangeListener originalFocusListener;
private OnFocusChangeListenerWrapper(View.OnFocusChangeListener originalFocusListener, Collection<SpinnerListener> spinnerListeners) {
this.spinnerListeners = spinnerListeners;
this.originalFocusListener = originalFocusListener;
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (originalFocusListener != null) {
originalFocusListener.onFocusChange(view, hasFocus); // Preserve the pre-existing focus listener (if any).
}
callSpinnerListeners(hasFocus);
}
private void callSpinnerListeners(boolean hasFocus) {
for (SpinnerListener spinnerListener : spinnerListeners) {
if (spinnerListener != null) {
callSpinnerListener(hasFocus, spinnerListener);
}
}
}
private void callSpinnerListener(boolean hasFocus, SpinnerListener spinnerListener) {
if (hasFocus) {
spinnerListener.onSpinnerExpanded();
}
else {
spinnerListener.onSpinnerCollapsed();
}
}
}
}
Then when I use a spinner in my activity or fragment all I had to do was to set the spinner adapter to the above custom adapter:
private ListeningArrayAdapter<CharSequence> adapter;
private Spinner buildSpinner() {
final CharSequence[] items = {"One", "Two", "Three"};
final Spinner spinner = (Spinner)getActivity().getLayoutInflater().inflate(R.layout.item_spinner, null);
adapter = new ListeningArrayAdapter<CharSequence>(getActivity(), R.layout.item_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter.addSpinnerListener(new TestSpinnerListener(getActivity())); // Add your own spinner listener implementation here.
spinner.setAdapter(adapter);
return spinner;
}
I know that this is a bit of a hack and a a bit brittle but it worked for me. It would be much better if the Spinner class had all this functionality build in and allowed you to set an expand-collapse listener. For the time being I will have to do with this hack.
来源:https://stackoverflow.com/questions/18447063/spinner-get-state-or-get-notified-when-opens