58

是否有可能知道 aSpinner是开放的还是封闭的?如果 Spinner 有某种 onOpenListener 会更好。

我试过使用这样的 OnItemSelectedListener:

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

    });

我可以知道如果选择了某些东西(在 executeSomething() 中),窗口将关闭。但是如果我在对话框之外单击,我不会收到通知,这也会关闭微调器

4

8 回答 8

109

监视这些事件的另一个选项是扩展Spinner类并使用其方法之一(performClick()这将触发其对话框/弹出窗口),然后监视持有此自定义的窗口的焦点Spinner。这应该为您提供所有可能的完成可能性(对于对话框或下拉模式)所需的关闭事件。

自定义Spinner类:

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

}
于 2013-09-05T12:30:17.117 回答
40

基于@Luksprog 精彩的解决方案,我只想添加一个小改动,如果有人在片段中使用 CustomSpinner,这将非常有帮助。我们不使用该Activity.onWindowFocusChanged函数,而是重写该View.onWindowFocusChanged函数。因此整个 CustomSpinner 类变成

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();
        }
    }
}
于 2014-12-29T16:39:31.177 回答
12

嗨朋友们,我从过去两天开始在这个问题上苦苦挣扎,最后我得到了以下解决方案,完成了我的工作。我试过了,效果很好。谢谢

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

            });
于 2015-07-20T09:51:53.863 回答
3

在花了一天时间查看所有解决方案之后,这是我检测 Spinner 的打开和关闭以及如何在微调器外部聚焦如何关闭微调器的简单解决方案。

第 1 步:在 Fragment 或 Activity中将 addOnWindowFocusChangeListener添加到 Spinner。

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val spinner = spinner_view

    val arrayAdapter = ArrayAdapter<RestoreManager.ConnectionType>(context!!, R.layout.layout_backup_spinner)
    arrayAdapter.setDropDownViewResource(R.layout.spinner_item)

    spinner?.let {
        val spinnerAdapter = SpinnerAdapter(activity!!)
        it.adapter = spinnerAdapter 
        it.setSelection(0)

        it.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View?, position: Int, id: Long) {}

            override fun onNothingSelected(parentView: AdapterView<*>) {}
        }

        it.viewTreeObserver?.addOnWindowFocusChangeListener { hasFocus -> //This updates the arrow icon up/down depending on Spinner opening/closing
            spinnerAdapter .spinnerOpen = hasFocus
            spinnerAdapter .notifyDataSetChanged()
        }
    }
}

每次微调器打开或关闭时都会调用addOnWindowFocusChangeListener 。当微调器打开并且用户在微调器外部点击以关闭微调器时,它也会触发。在此方法中,您可以更新 SpinnerAdapter 的 UI。

对于我的用例,我想在微调器打开和关闭时上下显示箭头图标。spinnerAdapter.spinnerOpen所以我在我的微调适配器中设置了标志 。

第 2 步:在您的SpinnerAdapter override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {}中,每次微调器打开或关闭时都会被调用。这是 SpinnerAdapter 中的代码:

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    val spinView = if (convertView == null) {
        val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        inflater.inflate(R.layout.layout_backup_spinner, null)
    } else {
        convertView
    }

    var arrowIcon = spinView.findViewById<ImageView>(R.id.arrow_icon)
    if (spinnerOpen) arrowIcon.setImageResource(R.drawable.arrow_down)
    else arrowIcon.setImageResource(R.drawable.arrow_up)

    return spinView
}
于 2020-08-31T08:32:06.667 回答
2

没有内置函数,但使用OnTouchListenerand很容易做到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();
}

然后分配适当的侦听器:

    OnOpenListener onOpenListener = new OnOpenListener(mySpinner) {

        @Override
        public void onOpen() {
            // spinner was opened
        }

        @Override
        public void onClose() {
            // spinner was closed
        }
    };
于 2013-09-02T08:16:45.523 回答
1

我认为找到它何时打开和关闭的最佳方法是这样:

  1. 如果它已关闭,现在它在适配器中调用“getDropDownView”,则可以假定它已打开。

  2. 如果调用“onItemSelected”或“onNothingSelected”,现在它已关闭。


编辑:这是一个示例代码

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;
        }
    }
}
于 2015-10-27T08:02:22.960 回答
0

我找不到使用微调器获得这种行为的方法,所以唯一对我有用的是使用微调器(自定义)适配器:

public interface SpinnerListener {

    void onSpinnerExpanded();   

    void onSpinnerCollapsed();
}

然后可以编写一个自定义适配器,它只抓取“旋转器展开”视图并向其添加一个侦听器以侦听“展开”和“折叠”事件。我使用的自定义适配器是:

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

然后,当我在我的活动或片段中使用微调器时,我所要做的就是将微调器适配器设置为上述自定义适配器:

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

我知道这有点小技巧,有点脆弱,但它对我有用。如果 Spinner 类内置了所有这些功能并允许您设置展开-折叠侦听器,那就更好了。暂时我将不得不处理这个黑客。

于 2014-08-05T11:31:42.037 回答
0

您需要使用反射并访问私有字段“mPopup”,然后设置方法setOnDismissListener(),无论用户单击空白区域还是选择新项目,都会在弹出窗口关闭时触发。您可以在此处了解有关其工作原理的更多信息:https ://stackoverflow.com/a/69156679/3753104

这是自定义 Spinner 的完整源代码

open class CustomSpinner: androidx.appcompat.widget.AppCompatSpinner {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    lateinit var listPopupWindow: ListPopupWindow
    lateinit var onPopUpClosedListener: (dropDownMenu: DropDownMenu) -> Unit
    lateinit var onPopUpOpenedListener: (dropDownMenu: DropDownMenu) -> Unit

    init {

        try {

            // get the listPopupWindow
            val listPopupWindowField = androidx.appcompat.widget.AppCompatSpinner::class.java.getDeclaredField("mPopup")
            listPopupWindowField.isAccessible = true
            listPopupWindow = listPopupWindowField.get(this) as ListPopupWindow
            listPopupWindow.isModal = false

        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun performClick(): Boolean {
        val returnValue = super.performClick()

        // indicate that the pop-up was opened
        if (::onPopUpOpenedListener.isInitialized) {
            onPopUpOpenedListener.invoke(this)
        }

        try {

            // get the popupWindow
            val popupWindowField = ListPopupWindow::class.java.getDeclaredField("mPopup")
            popupWindowField.isAccessible = true
            val popupWindow = popupWindowField.get(listPopupWindow) as PopupWindow

            // get the original onDismissListener
            val onDismissListenerField = PopupWindow::class.java.getDeclaredField("mOnDismissListener")
            onDismissListenerField.isAccessible = true
            val onDismissListener = onDismissListenerField.get(popupWindow) as PopupWindow.OnDismissListener

            // now override the original OnDismissListener
            listPopupWindow.setOnDismissListener {

                // indicate that the pop-up was closed
                if (::onPopUpClosedListener.isInitialized) {
                    onPopUpClosedListener.invoke(this)
                }

                // now we need to call the original listener that will remove the global OnLayoutListener
                onDismissListener.onDismiss()
            }

        } catch (e: Exception) {
            e.printStackTrace()
        }

        return returnValue
    }
}

然后只需将侦听器附加到您的自定义微调器

val customSpinner = findViewById<CustomSpinner>(R.id.mySpinner)
customSpinner.onPopUpClosedListener = { spinner: CustomSpinner ->
    // called when the pop-up is closed
}

customSpinner.onPopUpOpenedListener = { spinner: CustomSpinner ->
    // called when the pop-up is opened      
}
于 2021-09-13T02:58:06.657 回答