当下拉菜单关闭时捕获事件

18
我想捕捉当下拉菜单关闭时触发的事件。我们可以在onItemSelected()中捕捉到用户点击任何项目时的事件,但我想即使用户触摸下拉区域外部或返回按钮也要捕捉到这些事件。在这两种情况下,当我观察日志时,它会显示“尝试完成输入事件,但输入事件接收器已被处理”
我观察了源代码,这是在InputEventReceiver.javafinishInputEvent(InputEvent event, boolean handled)方法中打印的。但它是一个final方法,因此没有覆盖它的意义。请问有人能建议一种处理在这些情况下关闭下拉菜单的方法吗?

你是否使用过setOnItemSelectedListener()进行检查? - rekire
@rekire,是的,它正在覆盖onNothingSelected()函数。 - TheLittleNaruto
@rekire 是的,但是当它被解除时,控制权不会传递到onNothingSelected()。 - Kanth
@Appu 面临同样的问题。你找到任何解决方案了吗? - Syed Muhammad Umair
@SyedMuhammadUmair 我完全忘记了这个问题,所以没有回答我的问题。我现在已经自我回答了,但如果您严格要使用Spinner,则这将对您毫无用处。我使用了Popup菜单。 - Kanth
5个回答

1

我使用了弹出菜单而不是Spinner。因为据我所知,Spinner无法捕获dismiss事件,但是通过将onDismissListener()设置为弹出菜单,我成功地做到了。

popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {  
             public boolean onMenuItemClick(MenuItem item) {  
              Toast.makeText(MyActivity.this,"Clicked on: " + item.getTitle(),Toast.LENGTH_LONG).show();  
              return true;  
             }  
            });  
popup.setOnDismissListener (new PopupMenu.OnDismissListener(){

public void onDismiss()
{
   //catch dismiss event here.
}
});

谢谢,但那需要最低API 14,而我需要支持API 11。 - Syed Muhammad Umair
我最后使用了ListPopupWindow(在API级别11中添加) - Syed Muhammad Umair

0

我曾经遇到过同样的问题,我想要检测弹出窗口何时关闭,无论是点击外部还是选择元素。我不知道为什么Google不想添加简单的监听器,让我们可以用来检测这样关键的事情。现在已经是2021年了,仍然没有好的方法来检测它,真的吗,Google?

显然,解决方案是使用反射并获取对私有变量的访问权限。正如@Kanth建议的那样,我们需要访问OnDismissListener()。但他的答案有点过时,特别是如果你打算使用AppCompatSpinner

进一步检查后,我们可以看到AppCompatSpinner包含了私有对象“mPopup”,它是从接口SpinnerPopup类型中获得的。

 private SpinnerPopup mPopup;

这个接口随后被类DropdownPopup使用,并实现了它的方法,我们需要更仔细地查看实现的方法show()。如果我们往下滑动,就可以看到它设置了OnDismissListener()。因此,该侦听器随后用于使用方法removeGlobalOnLayoutListener()移除全局布局侦听器。因此,我们不能直接更改setOnDismissListener,因为之前添加的全局布局侦听器需要被移除。

enter image description here

现在我们需要找到侦听器存储的确切位置,然后获取该值并保留它。然后设置新的OnDismissListener,在其中可以检测弹出窗口的关闭。最后非常重要的是调用原始的OnDismissListener,以便它可以删除全局布局侦听器。因此,调用方法 setOnDismissListener() 进入ListPopupWindow类,从其 'mPopup' 对象调用相同的方法。

enter image description here

最后,我们来到了结束方法和存储监听器引用对象的类。该对象称为mOnDismissListener,当我们使用方法setOnDismissListener()设置新的监听器时,我们需要保持对它的引用。

enter image description here

因此,我们需要覆盖Spinner类,并以某种方式覆盖OnDismissListener,为此我们需要深入3个父类。

CustomSpinner     
├── AppCompatSpinner    (mPopup)    
│   ├── ListPopupWindow (mPopup)     
|   |   ├── PopupWindow (mOnDismissListener) finally!!!     
|   |   |    
|────────      
  1. 我们创建了一个自定义类CustomSpinner,该类实现了AppCompatSpinner类
  2. 我们需要访问AppCompatSpinner中的私有对象mPopup
  3. 然后我们需要从ListPopupWindow类中获取私有对象mPopup
  4. 接下来,我们需要从PopupWindow类中获取私有对象mOnDismissListener

现在,我们需要找到一个方法,在DropdownPopup类中的show()方法之后调用,该类声明在AppCompatSpinner类中。但是,在原始OnDismissListener被调用之前,应该触发此方法。这个特殊的方法是performClick(),当用户点击下拉框时,将调用此方法,然后触发show()方法,然后附加原始的OnDismissListener。

因此,以下是我们需要在performClick()方法中执行的步骤:

  1. 存储对原始OnDismissListener的引用
  2. 设置新的OnDismissListener,当弹出窗口关闭时调用它
  3. 调用自定义监听器onPopUpClosedListener,我们可以使用它
  4. 最后最重要的是使用我们之前存储的引用调用原始OnDismissListener以删除全局布局监听器

所以这就是我们自定义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

    init {

        try {

            // get private property and make it accessible
            val listPopupWindowField = androidx.appcompat.widget.AppCompatSpinner::class.java.getDeclaredField("mPopup")
            listPopupWindowField.isAccessible = true

            // get the list popup window
            listPopupWindow = listPopupWindowField.get(this) as ListPopupWindow
            listPopupWindow.isModal = false

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

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

        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 {

                // here we detect when the drop down is dismissed and call the listener
                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
    }

}

然后我们可以简单地使用onPopUpClosedListener监听器,并检测弹出窗口何时关闭。

val customSpinner: CustomSpinner = findViewById(R.id.mySpinner)
customSpinner.onPopUpClosedListener = {
     
    // here we detect when the pop-up from our custom spinner is closed
}

0
还可以寻找类似于onDetachFromWindow的其他事件吗?下拉菜单没有我们经常使用的任何常规生命周期事件--拥有一个onStoponDestroy将是很好的。当然,您需要扩展下拉菜单类并创建接口来定义自己的监听器:
public class ChattySpinner extends Spinner {
    private ChattySpinnerListener chattyListener;

    public ChattySpinner(Context context) {
        super(context);
    }

    public ChattySpinner(Context context, int mode) {
        super(context, mode);
    }

    public ChattySpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ChattySpinner(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ChattySpinner(Context context, AttributeSet attrs, int defStyle, int mode) {
        super(context, attrs, defStyle, mode);
    }

    public void setChattyListener(ChattySpinnerListener listener) {
        this.chattyListener = listener;
    }

    @Override
    protected void onDetachedFromWindow() {
        if(chattyListener != null) {
            chattyListener.onDetach();
        }

        super.onDetachedFromWindow();
    }

    public interface ChattySpinnerListener {
        public void onDetach();
    }
}

在您的布局XML中,您要确保指定此控件而不是普通的Spinner,并在代码中使用实现您希望在Spinner分离时执行的任何操作的侦听器。由您来确定客户端如何跟踪是否已选择某些内容,也许通过在您提供选择侦听器的onItemSelected方法中设置变量。

0
正确答案:
final ArrayAdapter adapterPlayerSelect = new ArrayAdapter(getActivity(), R.layout.item_simple_list, playModules);
        adapterPlayerSelect.setDropDownViewResource(R.layout.item_simple_list_bg);
        spPlayerSelect.setAdapter(adapterPlayerSelect);
        spPlayerSelect.setOnTouchListener((v, event) -> {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                final ArrayAdapter adapterPlayerPopup = new ArrayAdapter(getActivity(), R.layout.item_simple_list_bg, playModules);
                final ListView lvMenu = new ListView(getActivity());
                lvMenu.setAdapter(adapterPlayerPopup);
                final androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(getActivity(), R.style.Theme_AppCompat_Dialog);
                builder.setView(lvMenu);
                final androidx.appcompat.app.AlertDialog dialog = builder.create();
                lvMenu.setOnItemClickListener((parent, view, position, id) -> {
                    spPlayerSelect.setSelection(position);
                    dialog.dismiss();
                });
                dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        //Action on dismiss
                    }
                });
                dialog.show();
                return true;
            }
            return false;
        });

-1
如果你真的不需要使用Spinner,可以尝试使用以下代码。
在对话框内部使用ListView。你可以监听对话框的取消/关闭事件(同一件事)。这个方法适用于API 11。
final Dialog dialog = new Dialog(context);
            dialog.setContentView(R.layout.custom_list_popup);
            //dialog.setCancelable(false);
            dialog.setTitle("Title");

            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    //
                    //Do your onCancel things here
                    //
                }
            });


            final ListView listView = (ListView) dialog.findViewById(R.id.lv_sales_tax);
            listView.setAdapter(adapter);

            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                    //
                    //Do your stuff here
                    //

                    dialog.dismiss();
                }
            });

            dialogButton.setVisibility(View.GONE);
            dialog.show();

        }
    });

custom_list_popup.xml的内容

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="20dp"
android:orientation="vertical">

<ListView
    android:id="@+id/lv_sales_tax"
    android:divider="@drawable/list_divider"
    android:dividerHeight="20dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接