【问题标题】:Catching an event when spinner drop down is dismissed关闭微调器下拉菜单时捕获事件
【发布时间】:2013-11-07 10:02:48
【问题描述】:

我想在微调器下拉菜单被解除时捕捉一个事件。当用户单击 onItemSelected() 中的任何项目时,我们可以捕获它。但是即使用户在下拉区域或后退按钮之外触摸,我也想抓住它,因为它们也会使其消失。在这两个原因中,我观察日志时,它说“尝试完成一个输入事件,但输入事件接收器已经被释放”

我观察到source code,这是从 finishInputEvent(InputEvent event, boolean handler) 方法中的 InputEventReceiver.java 打印出来的。但这是一种最终方法,因此没有必要覆盖它。有人可以建议在这些情况下关闭下拉菜单时的处理方式吗?

【问题讨论】:

  • 你用setOnItemSelectedListener()检查了吗?
  • @rekire 是的,它覆盖了 onNothingSelectd() 函数
  • @rekire 是的,但是当它被解除时,控件不会到达 onNothingSelected()。
  • @Appu 面临同样的问题。你找到解决办法了吗?
  • @SyedMuhammadUmair 我完全忘记了这个问题,所以没有回答我的问题。我现在已经自我回答了,但是如果您严格地想使用 Spinner,这对您来说毫无用处。我用过弹出菜单。

标签: android drop-down-menu android-spinner


【解决方案1】:

我使用了Popup Menu 而不是 Spinner。因为据我所知,微调器无法捕获解除事件,但是使用弹出菜单我通过将onDismissListerner() 设置为弹出菜单来做到这一点

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。
  • 我最终使用了 ListPopupWindow(在 API 级别 11 中添加)
【解决方案2】:

寻找像onDetachFromWindow 这样的另一个事件怎么样?微调器没有我们经常使用的任何常规生命周期事件——如果有onStoponDestroy 可以使用会很好。当然,你必须扩展 spinner 类并创建一个接口来定义你自己的监听器:

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 中,您要确保指定此控件而不是您的普通微调器,并在您的代码中设置侦听器,并在微调器分离时执行您想要执行的任何操作。您可以在客户端确定如何跟踪是否已选择某些内容,可能通过在您提供选择侦听器的onItemSelected 方法中设置一个变量。

【讨论】:

    【解决方案3】:

    我遇到了同样的问题,我想检测弹出窗口何时关闭,无论是在外部单击还是选择了元素。我不知道为什么谷歌不想添加简单的侦听器来检测如此重要的事情。都2021年了,还没有什么好的检测方法,真的是谷歌???

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

    进一步检查,我们可以看到 AppCompatSpinner 包含私有对象“mPopup”,它来自接口 SpinnerPopup 类型。

     private SpinnerPopup mPopup;
    

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

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

    最后我们到了 end 方法和存储监听器引用对象的类。该对象被称为 mOnDismissListener,当我们使用 setOnDismissListener()

    所以我们需要重写微调器类并以某种方式重写 OnDismissListener,为此我们需要将 3 个父类放入兔子洞。

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

    现在我们需要从在 AppCompatSpinner 类中声明的 DropdownPopup 类中找到一个在方法 show() 之后调用的方法。但是这个方法应该在调用原来的 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
    }
    

    【讨论】:

      【解决方案4】:

      如果您真的不需要使用微调器,请尝试使用此代码。
      Dialog内的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>
      

      【讨论】:

        猜你喜欢
        • 2017-02-07
        • 1970-01-01
        • 2019-01-20
        • 1970-01-01
        • 1970-01-01
        • 2018-06-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多