Spinner: get state or get notified when opens

放肆的年华 提交于 2019-12-16 22:45:34

问题


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:

  1. If it was closed, and now it calls "getDropDownView" in the adapter, it can be assumed that it got opened.

  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!