【问题标题】:Android SearchView.OnQueryTextListener OnQueryTextSubmit not fired on empty query stringAndroid SearchView.OnQueryTextListener OnQueryTextSubmit 未在空查询字符串上触发
【发布时间】:2012-11-27 01:56:21
【问题描述】:

我使用的是 Android 4.1.2。我在ActionBar 上有一个SearchView 小部件。来自 android 开发者网站的关于 SearchView.OnQueryTextListener 的文档指出,onQueryTextSubmit 在用户提交查询时被触发/调用。这可能是由于键盘上的按键或按下提交按钮造成的。”

如果搜索查询为空,则不会发生这种情况。我需要这个来触发一个空查询以清除 ListView 的搜索过滤器。这是一个错误还是我做错了什么?

【问题讨论】:

    标签: android searchview


    【解决方案1】:

    这不是错误,source code 故意检查 null 和空值:

    private void onSubmitQuery() {
        CharSequence query = mQueryTextView.getText();
        if (query != null && TextUtils.getTrimmedLength(query) > 0) {
    

    但是,当用户清除搜索 EditText 时,您应该能够使用 OnQueryTextChange 回调清除 ListView 的可过滤项。

    【讨论】:

    • 感谢您提供信息和对源代码的研究。我相信即使查询文本为空,也应该触发 OnQueryTextSubmit 以允许我们清除搜索过滤器并重新显示整个未过滤的列表。即使我通过 OnQueryTextChange 事件跟踪查询文本,当搜索文本变为空时也会出现问题。没有事件被触发。我必须在操作栏上放另一个菜单项才能重新显示整个未过滤的列表。
    • 我建议当 OnQueryTextChange 通知您搜索 EditText 为空时您自己手动清除过滤器。我同意尝试以任何方式使用空字符串调用onSubmitQuery() 是行不通的。
    • 在某些情况下,您不想立即进行过滤(在文本更改时),因为建议列表已经这样做了。因此,您仅在搜索点击时过滤真实列表。然后用户必须有可能清除此搜索。所以是的,我也认为这个检查是一个错误。
    • 推理:“它在源代码中,所以它不是错误”是完全有缺陷的。
    【解决方案2】:

    我有一个更简单的解决方法:使用 onQueryTextChange,但仅在它为空时才呈现。

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String query) {
                        renderList(true);
                        return false;
                }
    
                @Override
                public boolean onQueryTextChange(String newText) {
                        if (searchView.getQuery().length() == 0) {
                                renderList(true);
                        }
                        return false;
                }
        });
    

    【讨论】:

    • 为什么不用newText 而不是searchView.getQuery()
    【解决方案3】:

    我遇到了同样的问题,最终得到了以下解决方案:自定义SearchView + OnQueryTextListener.onQueryTextChange

    自定义搜索视图:

    public class MySearchView extends SearchView {
    
    private boolean expanded;
    
    public MySearchView(Context context) {
        super(context);
    }
    
    @Override
    public void onActionViewExpanded() {
        super.onActionViewExpanded();
        expanded = true;
    }
    
    @Override
    public void onActionViewCollapsed() {
        super.onActionViewCollapsed();
        expanded = false;
    }
    
    public boolean isExpanded() {
        return expanded;
    }
    }
    

    创建动作和设置回调:

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        searchAction = menu.add(0, SEARCH_ACTION_ID, 0 , getString(R.string.action_search));
        searchAction.setShowAsAction(SHOW_AS_ACTION_ALWAYS | SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);       
        searchView = new MySearchView(getSherlockActivity());
        searchView.setOnQueryTextListener(searchQueryListener);
        searchView.setIconifiedByDefault(true);
        searchAction.setActionView(searchView);
    }
    

    最后一位听众:

    private OnQueryTextListener searchQueryListener = new OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            search(query);
            return true;
        }
    
        @Override
        public boolean onQueryTextChange(String newText) {
            if (searchView.isExpanded() && TextUtils.isEmpty(newText)) {
                search("");
            }
    
            return true;
        }
    
        public void search(String query) {
            // reset loader, swap cursor, etc.
        }
    
    };
    

    在 ABS 4.3 上测试。

    【讨论】:

    • 看起来您实际上并不需要自定义 SearchView(使用 isExpanded())。 isIconified() 会做同样的事情,尽管你只是取反。如果 isIconified() 为 true 并且 newText 为空,那么用户只是打开了 SearchView,他们没有明确清除搜索视图中的文本。至少,这似乎适用于 ABS 4.x
    【解决方案4】:

    我对 SearchView 有同样的问题。我的解决方案包括样式化 SearchView,如指南 http://novoda.com/blog/styling-the-actionbar-searchview/ 所示,并为作为 SearchView 一部分的 EditText(How do I trigger an action when the user has hit enter?) 设置 OnEditorActionListener:

    final SearchView searchView = (SearchView)findViewById(R.id.search);
    int searchPlateId = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null);
    EditText searchPlate = (EditText) searchView.findViewById(searchPlateId);
    searchPlate.setOnEditorActionListener(new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                //Do something
            }
            return false;
    
        }});
    

    【讨论】:

    • 如果你使用v7 searchView,那么searchPlace id就是R.id.search_src_text,所以EditText searchPlate = (EditText) searchView.findViewById(R.id.search_src_text)
    【解决方案5】:

    这是一个旧线程,但我找到了另一个解决方案(Kotlin 代码,但也适用于 Java):

    override fun onQueryTextChange(newText: String?): Boolean {
                if(newText == null || newText.isEmpty())
                    searchView.setQuery("\u00A0", false)
                return true
            }
    

    \u00A0 是一个看起来像空格的字符,但在代码方面它不是。换句话说,SearchView 不是空的。空字符串或" " 将不起作用,因为基本 SearchView 使用修剪,而且用户可能希望搜索以空格结尾或开头的文本。但据我所知,用户无法输入\u00A0 字符。

    那么您需要做的就是在您的自定义 SearchView 中将 getQuery 覆盖为 return super.getQuery.replace("\u00A0", "")

    【讨论】:

    • 不错的解决方法!我通过使用零长度空格 (\u200B) 而不是不间断空格 (\u00A0) 对其进行了一些修改,以便在插入字符后搜索视图看起来是空的。
    【解决方案6】:

    这是一个非常肮脏的 hack,但是它可以按预期正常工作,即使在 SearchView 中没有输入(或输入然后编辑)文本时也可以启用提交操作。

    它反射性地获得对内部 TextView 编辑器操作侦听器的访问,将其包装在自定义侦听器中,在该侦听器中将调用委托给处理程序,最后将其设置为内部 TextView 的操作侦听器。

        Class klass = searchView.getClass();
        try {
            Field currentListenerField = klass.getDeclaredField("mOnEditorActionListener");
            currentListenerField.setAccessible(true);
            TextView.OnEditorActionListener previousListener = (TextView.OnEditorActionListener) currentListenerField.get(searchView);
            TextView.OnEditorActionListener newListener = new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    if (v.getText().length() == 0)
                        handleQuery("");
                    return previousListener.onEditorAction(v, actionId, event);
                }
            };
            Field innerTextViewField = klass.getDeclaredField("mSearchSrcTextView");
            innerTextViewField.setAccessible(true);
            SearchView.SearchAutoComplete innerTextView = (SearchView.SearchAutoComplete) innerTextViewField.get(searchView);
            innerTextView.setOnEditorActionListener(newListener);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    

    【讨论】:

    • 这个解决方案对我有用,但你能解释一下命名“mOnEditorActionListener”和“mSearchSrcTextView”的意义吗?我可以命名这些我想要的任何东西吗?另外:我在同一页面上有第二个 SearchView 需要类似的功能。当我将侦听器添加到我的第二个 SearchView 时,是否需要以不同的方式命名它们?
    • 为什么这个解决方案需要 try/catch,在什么情况下会发生,为什么我们会抛出异常——它不会简单地崩溃吗?
    • 看看这个链接。本质上,我们正在修改在 Android SDK 库类中定义的字段(我们无法通过普通 API 访问它)。所以这个 sn-p 将适用于任何 searchView 变量或其他名称,但更改 "mOnEditorActionListener" 会破坏它。 cs.android.com/android/platform/superproject/+/…
    • 只有在较新的 SDK 版本中对 SearchView 类进行了重大重新设计(例如,删除了该字段)时,try/catch 才会失败。因此,您将得到运行时错误,而不是编译错误。这就是为什么我提到这是一个肮脏的黑客:)
    【解决方案7】:

    正如其他人所提到的,这种行为是故意的。我放弃了OnQueryChangeListener 的解决方案,并决定通过在 SearchView 的EditText 上实现OnEditorActionListener 来解决这个问题,您可以使用R.id.search_src_text。只要你把SearchView 的setImeOptions 改成EditorInfo.IME_ACTION_SEARCH 你就可以处理键盘搜索按钮的点击。详情请见this SO answer

    【讨论】:

      【解决方案8】:

      我遇到了同样的问题,因为不支持空查询,所以我必须下载并使用ActionBarSherlock,然后修改 onSubmitQuery() 方法。

      这就是我的 onSubmitQuery() 现在的样子

      private void onSubmitQuery() {
           CharSequence query = mQueryTextView.getText();
           if (query == null) {query = "";}
           if (mOnQueryChangeListener == null
                   || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
               if (mSearchable != null) {
                   launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
                   setImeVisibility(false);
               }
               dismissSuggestions();
           }
       }
      

      希望这会有所帮助。

      【讨论】:

      • 这行得通...修改库不好,但还没有找到其他解决方案。
      【解决方案9】:

      我可以通过以这种方式实现 setOnQueryTextListener 来做到这一点:

      SearchView searchView = (SearchView) menu.findItem(R.id.searchProductView).getActionView();
          searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
              @Override
              public boolean onQueryTextSubmit(String query) {
                  return false;
              }
      
              @Override
              public boolean onQueryTextChange(String newText) {
                  if (TextUtils.isEmpty(newText)) {
                      loadLocalData(newText);
                  }
                  return true;
              }
          });
      

      我的 loadLocalData 方法在哪里

      //query my DB
      products = ProductDAO.findByIdList(idList);
      LinearLayoutManager layoutManager = new LinearLayoutManager(this);
      listView.setLayoutManager(layoutManager);
      listView.setAdapter(<YOURADAPTER>)
      

      即使您使用“X”按钮清除文本,此解决方案也会清除您的查询

      【讨论】:

        【解决方案10】:

        这是一个用 Kotlin 编写的完整示例:

        import android.content.Context
        import android.text.TextUtils
        import android.util.AttributeSet
        import android.view.inputmethod.EditorInfo
        import android.widget.TextView
        import androidx.appcompat.widget.SearchView
        
        /**
         * Custom [SearchView] which can listen to empty submit events
         *
         * If the internal implementation would change, this will not crash at runtime. It will simply
         * ignore the empty submit listener and will continue to work as this is the base [SearchView]
         */
        class EmptySubmitSearchView @JvmOverloads constructor(
            context: Context,
            attrs: AttributeSet? = null,
            defStyleAttr: Int = 0
        ) : SearchView(context, attrs, defStyleAttr) {
        
            private lateinit var searchAutoComplete: SearchAutoComplete
        
            private lateinit var internalEditorListener: TextView.OnEditorActionListener
        
            private var emptySubmitListener: OnEmptySubmitListener? = null
        
            init {
                try {
                    val defaultListenerField = SearchView::class.java
                        .getDeclaredField("mOnEditorActionListener")
                    defaultListenerField.isAccessible = true
                    internalEditorListener = defaultListenerField.get(this) as TextView.OnEditorActionListener
        
                    val searchAutoCompleteField = SearchView::class.java
                        .getDeclaredField("mSearchSrcTextView")
                    searchAutoCompleteField.isAccessible = true
                    searchAutoComplete = searchAutoCompleteField.get(this) as SearchAutoComplete
        
                    searchAutoComplete.setOnEditorActionListener { v, actionId, event ->
                        if (actionId == EditorInfo.IME_ACTION_DONE && TextUtils.isEmpty(searchAutoComplete.text)) {
                            emptySubmitListener?.onEmptyTextSubmit()
                        } else {
                            internalEditorListener.onEditorAction(v, actionId, event)
                        }
                        true
                    }
                } catch (ignored: Exception) {
                    //Some internal field has changed. Check there is a field called
                    //mSearchSrcTextView and mOnEditorActionListener and if not update them accordingly
                }
            }
        
            /**
             * Set custom empty listener or null to remove it
             */
            fun setOnEmptySubmitListener(listener: OnEmptySubmitListener?) {
                emptySubmitListener = listener
            }
        
            interface OnEmptySubmitListener {
                fun onEmptyTextSubmit()
            }
        }
        

        您可以使用setOnEmptySubmitListener 设置自定义侦听器,这将使您能够区分事件。

        您可以自定义要响应的 actionId,在这种情况下,我的 XML 中有 EmptySubmitSearchView 和 android:imeOptions="actionDone|flagNoExtractUi"

        【讨论】:

          【解决方案11】:

          您无法覆盖此行为。 一种解决方法是在用户退出搜索视图时清除过滤器。

          您可以为此使用OnCloseListener。 但是,这也可能导致问题,具体取决于您开发的最低 API 级别。

          【讨论】:

            【解决方案12】:

            另一种选择是通过 TextWatcher 手动触发 onQueryTextChange 方法:

            public class MyActivity extends Activity implements SearchView.OnQueryTextListener, TextWatcher {
            
                // Basic onCreate(Bundle) here
            
                @Override
                public boolean onCreateOptionsMenu (final Menu menu) {
                    getMenuInflater().inflate(R.menu.myMenu, menu);
            
                    final SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
                    final SearchManager manager = (SearchManager) getSystemService(SEARCH_SERVICE);
                    searchView.setSearchableInfo(manager.getSearchableInfo(getComponentName()));
                    searchView.setOnQueryTextListener(this);
            
                    final int resource_edit_text = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null);
                    ((EditText) searchView.findViewById(resource_edit_text)).addTextChangedListener(this);
            
                    return super.onCreateOptionsMenu(menu);
                }
            
                // Implementation of TextWatcher, used to have a proper onQueryTextChange (it doesn't update when the last character is removed)
                @Override
                public void beforeTextChanged (final CharSequence charSequence, final int start, final int count, final int after) {}
            
                @Override
                public void onTextChanged (final CharSequence charSequence, final int start, final int before, final int after) {}
            
                @Override
                public void afterTextChanged (final Editable editable) {
                    if (editable.length() == 0)
                        onQueryTextChange(editable.toString());
                }
            
                // Implementation of OnQueryTextChangeListener
            
                @Override
                public boolean onQueryTextChange (final String query) {
                    // do stuff
                    return false;
                }
            
                @Override
                public boolean onQueryTextSubmit (final String query) {
                    // do stuff
                    return false;
                }
            }
            

            这一切都与这里放弃的所有默认 xml 文件有关,这不是这个答案的重点。这是我选择使用的解决方法,请随意使用其他人

            【讨论】:

            • TextWatcher.afterTextChanged()回调中,if-condition不应该是editable.length()==0。应该是editable.length()&gt;0。顺便说一句,感谢您的解决方案,它帮助我在 Api-8 上工作。
            • 如果ActionBarActivity使用support-v7,添加另一行查找if (edit_text== null)edit_text= (EditText)searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text);
            【解决方案13】:

            在我的例子中,我只是想让用户清除他的查询,因为它在 API 搜索中用作关键字,所以最简单的想法是:

                searchView.setOnSearchClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        userQuery = "";
                    }
                });
            

            其中 userQuery 是我用于搜索等的变量。

            希望它可以帮助某人:)

            【讨论】:

              【解决方案14】:

              这是 samvel1024 将其转换为 Kotlin 的答案。 您可能需要将 IME_ACTION_GO 更改为您用于键盘“开始”按钮的任何内容。只需在页面初始化期间运行此方法即可。

              /**
               * Business: User should be allowed to perform an empty search.
               * SearchView does not cause OnQueryTextChange to fire when the box is empty.
               * To create a listener method for this scenario, we use reflection to gain access to the SearchView's inner TextView.
               */
              private fun enableEmptySearch(searchView: SearchView) {
                  try {
                      val searchViewClass = searchView::class.java
                      val currentListenerField: Field = searchViewClass.getDeclaredField("mOnEditorActionListener")
                      currentListenerField.isAccessible = true
                      val previousListener: TextView.OnEditorActionListener = currentListenerField.get(searchView) as TextView.OnEditorActionListener
                      val newListener: TextView.OnEditorActionListener = TextView.OnEditorActionListener { v, actionId, event ->
                          if (actionId == EditorInfo.IME_ACTION_GO && v.text.isEmpty()) {
                              querySubmit()
                          }
                          previousListener.onEditorAction(v, actionId, event)
                      }
                      val innerTextViewField: Field = searchViewClass.getDeclaredField("mSearchSrcTextView")
                      innerTextViewField.isAccessible = true
                      (innerTextViewField.get(searchView) as TextView).setOnEditorActionListener(newListener) // Allow user to tap the "GO" button when the SearchView is empty.
                  } catch (ignored: Exception) {
                      // If a major change occurs to android SDK, internal fields mSearchSrcTextView or mOnEditorActionListener might not exist.
                      // This exception will prevent this additional feature from functioning, but not crash the app or cause any other issues.
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2010-12-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多