如何在操作栏中构建类似 Gmail 的搜索框? [英] How to build Gmail like search box in the action bar?

查看:13
本文介绍了如何在操作栏中构建类似 Gmail 的搜索框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前在 ActionBarcompat 中使用 SearchView 小部件在搜索时过滤列表.

当用户开始输入文本时,主布局中的 ListView 会使用 Adapter 更新以过滤结果.我通过实现一个 OnQueryTextListener 并过滤每个击键的结果来做到这一点.

相反,我想创建一个 Gmail 之类的搜索框,并生成自动建议列表并且不更改基础视图

我已经阅读了使用 的:将所有 UI 元素组装在一个相对布局中,作为 SearchActivity 的内容;

<android.support.v7.widget.RecyclerViewandroid:id="@+id/cs_result_list"android:layout_width="match_parent"android:layout_height="wrap_content"机器人:stackFromBottom =真"android:transcriptMode="正常"/>

  • custom_searchable_header_layout.xml:保存用户将在其中键入查询的搜索栏.它还将包含麦克风、擦除和返回 btn;

    <编辑文本android:id="@+id/custombar_text"android:layout_width="match_parent"android:layout_height="match_parent"android:hint="搜索..."android:textColor="@color/textPrimaryColor"机器人:单线=真"android:imeOptions="actionSearch"android:background="#00000000"><requestFocus/></EditText></android.support.design.widget.TextInputLayout><相对布局android:id="@+id/custombar_mic_wrapper"android:layout_width="55dp"android:layout_height="fill_parent"android:layout_alignParentRight="true"机器人:重力=center_vertical"android:background="@drawable/left_oval_ripple"机器人:焦点=真"android:clickable="true" ><图像视图android:id="@+id/custombar_mic"android:layout_width="wrap_content"android:layout_height="wrap_content"机器人:layout_centerVertical =真"机器人:layout_centerHorizo​​ntal="true"机器人:背景=#00000000"android:src="@drawable/mic_icon"/></RelativeLayout>

  • custom_searchable_row_details.xml:保存要在结果列表中显示的UI元素,以响应用户查询;

    <线性布局android:id="@+id/rd_wrapper"机器人:方向=垂直"android:layout_width="match_parent"android:layout_height="match_parent"机器人:重力=center_vertical"android:layout_toRightOf="@+id/rd_left_icon"android:layout_marginLeft="20dp"android:layout_marginRight="50dp"><文本视图android:id="@+id/rd_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/textPrimaryColor"机器人:文本=标题"android:textSize="16dp"android:textStyle="粗体"android:maxLines="1"/><文本视图android:id="@+id/rd_sub_header_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/textPrimaryColor"android:text="子标题"android:textSize="14dp"android:maxLines="1"/></LinearLayout><图像视图android:id="@+id/rd_right_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="3dp"android:layout_alignParentRight="true"机器人:layout_centerVertical =真"android:src="@drawable/arrow_left_up_icon"/>

  • 第 2 部分:实现 SearchActivity

    这个想法是,当用户输入搜索按钮(你可以把它放在任何你想要的地方)时,这个 SearchActivity 将被调用.它有一些主要职责:

    • 绑定到 custom_searchable_header_layout.xml:通过这样做,可以:

    • 为 EditText 提供侦听器(用户将在其中键入其查询):

      TextView.OnEditorActionListener searchListener = new TextView.OnEditorActionListener() {public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent event) {//做处理}}searchInput.setOnEditorActionListener(searchListener);searchInput.addTextChangedListener(new TextWatcher() {public void onTextChanged(final CharSequence s, int start, int before, int count) {//做处理}}

    • 为返回按钮添加侦听器(轮到它只会调用完成()并返回到调用者活动):

      this.dismissDialog.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {结束();}

    • 调用谷歌语音转文本 API 的意图:

       private voidimplementVoiceInputListener(){this.voiceInput.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {如果(micIcon.isSelected()){searchInput.setText("");查询 = "";micIcon.setSelected(Boolean.FALSE);micIcon.setImageResource(R.drawable.mic_icon);} 别的 {意图意图 = 新意图(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);intent.putExtra(RecognizerIntent.EXTRA_PROMPT,现在说");SearchActivity.this.startActivityForResult(intent, VOICE_RECOGNITION_CODE);}}});}

    内容提供者

    在构建搜索界面时,开发人员通常有两个选择:

    1. 向用户建议最近的查询:这意味着每次用户进行搜索时,输入的查询将保存在数据库中,以便稍后检索作为未来搜索的建议;
    2. 向用户建议自定义选项:开发人员将尝试通过处理已经输入的字母来预测用户想要什么;

    在这两种情况下,答案都应作为 Cursor 对象返回,其内容将在结果列表中显示为 itens.这整个过程可以使用 Content Provider API 来实现.可以在此链接中获取有关如何使用内容提供程序的详细信息.

    在开发人员想要实现 1. 中描述的行为的情况下,使用扩展 SearchRecentSuggestionsProvider 类的策略会很有用.可以在此链接中找到有关如何执行此操作的详细信息.>

    实现搜索界面

    此接口应提供以下行为:

    • 当用户键入一个字母时,对检索到的内容提供者类的查询方法的调用应该返回一个填充的光标,其中包含要显示在列表中的建议 - 您应该注意不要冻结 UI 线程,因此我建议在 AsyncTask 中执行此搜索:

       public void onTextChanged(final CharSequence s, int start, int before, int count) {if (!"".equals(searchInput.getText().toString())) {查询 = searchInput.getText().toString();setClearTextIcon();如果(isRecentSuggestionsProvider){//Provider 是 SearchRecentSuggestionsProvider 的后代mapResultsFromRecentProviderToList();//在该方法中执行查询} 别的 {//Provider是自定义的,应该遵守合同mapResultsFromCustomProviderToList();//在该方法中执行查询}} 别的 {设置麦克风图标();}}

    • 在 AsyncTask 的 onPostExecute() 方法中,您应该检索一个列表(应该来自 doInBackground() 方法),其中包含要在 ResultList 中显示的结果(您可以将其映射到 POJO 类中)并将其传递给您的自定义适配器,或者您可以使用最适合此任务的 CursorAdapter):

      protected void onPostExecute(List resultList) {SearchAdapter 适配器 = new SearchAdapter(resultList);searchResultList.setAdapter(适配器);}受保护的列表 doInBackground(Void[] params) {光标结果 = 结果 = queryCustomSuggestionProvider();列表<结果项>resultList = new ArrayList<>();整数 headerIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);整数 subHeaderIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);整数 leftIconIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);整数 rightIconIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);而 (results.moveToNext()) {String header = results.getString(headerIdx);字符串 subHeader = (subHeaderIdx == -1) ?null : results.getString(subHeaderIdx);整数 leftIcon = (leftIconIdx == -1) ?0 : results.getInt(leftIconIdx);整数 rightIcon = (rightIconIdx == -1) ?0 : results.getInt(rightIconIdx);ResultItem aux = new ResultItem(header, subHeader, leftIcon, rightIcon);resultList.add(aux);}结果.close();返回结果列表;

    • 识别用户何时从软键盘触摸搜索按钮.当他这样做时,向可搜索活动(负责处理搜索结果的活动)发送一个意图,并将查询作为额外信息添加到意图中

      protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);开关(请求代码){案例语音_识别_代码:{if (resultCode == RESULT_OK && null != data) {ArrayListtext = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);searchInput.setText(text.get(0));}休息;}}}

    • 识别用户何时点击显示的建议之一并发送包含项目信息的意图(此意图应与上一步的意图不同)

      private void sendSuggestionIntent(ResultItem item) {尝试 {Intent sendIntent = new Intent(this, Class.forName(searchableActivity));sendIntent.setAction(Intent.ACTION_VIEW);sendIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);捆绑 b = 新捆绑();b.putParcelable(CustomSearchableConstants.CLICKED_RESULT_ITEM, item);sendIntent.putExtras(b);开始活动(发送意图);结束();} catch (ClassNotFoundException e) {e.printStackTrace();}}

    描述的步骤应该足以实现您自己的接口.所有代码示例均取自此处.我制作了这个库,它几乎完成了上面描述的内容.它尚未经过充分测试,某些 UI 配置可能尚未可用.

    我希望这个答案可以帮助有需要的人.

    I am currently using SearchView widget inside ActionBarcompat to filter a list while searching.

    When the user starts entering text the ListView in the main layout updates with an Adapter to filter the results. I do this by implementing a OnQueryTextListener and filter the results on each key stroke.

    Instead, I want to create a Gmail like search box with auto suggest list generated and no changes to the underlying view

    I have went through this tutorial that uses the SearchView component but it requires a searchable activity. I want the drop-down to be over the MainActivity where I have the ListView (like in the Gmail app) and not a dedicated Activity.
    Besides, implementing it the same way as in the tutorial seems like an overkill for what I want (just a dropdown)

    解决方案

    If you just want a component that does what is discribed in the question, I suggest this library. You can also implement the out-of-the-bx searchable interface, however, be aware that it does have UI limitations:

    To implement an interface similar to Gmail App, you will have to understand conceps of:

    • Content Providers;
    • Persisting data in SQLite
    • Listview or RecyclerView and its adapters;
    • Passing data between activities;

    Final result should look something like:

    There are many (many) ways to get to the same result (or better), I'll discribed one possible way.

    Part 01: Layout

    I decided to manage the entire interface in a new Activity, for that I've created three XML layouts:

    • custom_searchable.xml: assemblys all UI elements in one RelativeLayout that will serve as content for the SearchActivity;

      <include
          android:id="@+id/cs_header"
          layout="@layout/custom_searchable_header_layout" />
      
      <android.support.v7.widget.RecyclerView
          android:id="@+id/cs_result_list"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:stackFromBottom="true"
          android:transcriptMode="normal"/>
      

    • custom_searchable_header_layout.xml: holds the search bar where the user will type his query. It will also contain the mic, erase and return btn;

      <RelativeLayout
          android:id="@+id/custombar_return_wrapper"
          android:layout_width="55dp"
          android:layout_height="fill_parent"
          android:gravity="center_vertical"
          android:background="@drawable/right_oval_ripple"
          android:focusable="true"
          android:clickable="true" >
      
          <ImageView
              android:id="@+id/custombar_return"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_centerHorizontal="true"
              android:background="#00000000"
              android:src="@drawable/arrow_left_icon"/>
      </RelativeLayout>
      
      <android.support.design.widget.TextInputLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_toRightOf="@+id/custombar_return_wrapper"
          android:layout_marginRight="60dp"
          android:layout_marginLeft="10dp"
          android:layout_marginTop="10dp"
          android:layout_marginBottom="10dp">
      
          <EditText
              android:id="@+id/custombar_text"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:hint="Search..."
              android:textColor="@color/textPrimaryColor"
              android:singleLine="true"
              android:imeOptions="actionSearch"
              android:background="#00000000">
              <requestFocus/>
          </EditText>
      
      </android.support.design.widget.TextInputLayout>
      
      <RelativeLayout
          android:id="@+id/custombar_mic_wrapper"
          android:layout_width="55dp"
          android:layout_height="fill_parent"
          android:layout_alignParentRight="true"
          android:gravity="center_vertical"
          android:background="@drawable/left_oval_ripple"
          android:focusable="true"
          android:clickable="true" >
      
          <ImageView
              android:id="@+id/custombar_mic"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_centerHorizontal="true"
              android:background="#00000000"
              android:src="@drawable/mic_icon"/>
      </RelativeLayout>
      

    • custom_searchable_row_details.xml: holds the UI elements to be displayed in the result list to be displayed in response to the user query;

      <ImageView
          android:id="@+id/rd_left_icon"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginTop="3dp"
          android:layout_centerVertical="true"
          android:layout_marginLeft="5dp"
          android:src="@drawable/clock_icon" />
      
      <LinearLayout
          android:id="@+id/rd_wrapper"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:gravity="center_vertical"
          android:layout_toRightOf="@+id/rd_left_icon"
          android:layout_marginLeft="20dp"
          android:layout_marginRight="50dp">
      
          <TextView
              android:id="@+id/rd_header_text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:textColor="@color/textPrimaryColor"
              android:text="Header"
              android:textSize="16dp"
              android:textStyle="bold"
              android:maxLines="1"/>
      
          <TextView
              android:id="@+id/rd_sub_header_text"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:textColor="@color/textPrimaryColor"
              android:text="Sub Header"
              android:textSize="14dp"
              android:maxLines="1" />
      </LinearLayout>
      
      <ImageView
          android:id="@+id/rd_right_icon"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginTop="3dp"
          android:layout_alignParentRight="true"
          android:layout_centerVertical="true"
          android:src="@drawable/arrow_left_up_icon"/>
      

    Part 02: Implementing the SearchActivity

    The idea is that, when the user types the search button (which you can place any where you want), this SearchActivity will be called. It has some main responsabilities:

    • Bind to the UI elements in the custom_searchable_header_layout.xml: by doing that, it is possible:

    • to provide listeners for the EditText (where the user will type his query):

      TextView.OnEditorActionListener searchListener = new TextView.OnEditorActionListener() {
      public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent event) {
          // do processing
         }
      }
      
      searchInput.setOnEditorActionListener(searchListener);
      
      searchInput.addTextChangedListener(new TextWatcher() {        
      public void onTextChanged(final CharSequence s, int start, int before, int count) {
           // Do processing
         }
      }
      

    • add listener for the return button (which by its turn will just call finish() and return to the caller activity):

      this.dismissDialog.setOnClickListener(new View.OnClickListener() {
          public void onClick(View v) {
          finish();
      }    
      

    • calls the intent for google speech-to-text API:

          private void implementVoiceInputListener () {
              this.voiceInput.setOnClickListener(new View.OnClickListener() {
      
                  public void onClick(View v) {
                      if (micIcon.isSelected()) {
                          searchInput.setText("");
                          query = "";
                          micIcon.setSelected(Boolean.FALSE);
                          micIcon.setImageResource(R.drawable.mic_icon);
                      } else {
                          Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
      
                          intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                          intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak now");
      
                          SearchActivity.this.startActivityForResult(intent, VOICE_RECOGNITION_CODE);
                      }
                  }
              });
          }
      

    Content Provider

    When building the a search interface the developer has tipically two options:

    1. Suggest recent queries to the user: this implies that everytime the user makes a search, the typed query will be persisted in a database to be retrieved latter on as a suggestion for future searches;
    2. Suggest custom options to the user: the developer will try to predict what the user wants by processing the already typed letters;

    In both cases the answers shall be delivered back as a Cursor object that will have its content displayed as itens in the result list. This whole process can be implement using the Content Provider API. Details about how to use Content Providers can be reached in this link.

    In the case where the developer wants to implement the behavior described in 1., it can be usefull to use the strategy of exteding the SearchRecentSuggestionsProvider class. Details about how to do it can be reached in this link.

    Implementing the search interface

    This interface shall provide the following behavior:

    • When the user types a letter a call to the query method of the retrieved content provider class should return a filled cursor with the suggestion to be displayed in the list - you should take to not freeze the UI thread, so it I recommend to perform this search in an AsyncTask:

          public void onTextChanged(final CharSequence s, int start, int before, int count) {
              if (!"".equals(searchInput.getText().toString())) {
                  query = searchInput.getText().toString();
      
                  setClearTextIcon();
      
                  if (isRecentSuggestionsProvider) {
                      // Provider is descendant of SearchRecentSuggestionsProvider
                      mapResultsFromRecentProviderToList(); // query is performed in this method
                  } else {
                      // Provider is custom and shall follow the contract
                      mapResultsFromCustomProviderToList(); // query is performed in this method
                  }
              } else {
                  setMicIcon();
              }
          }
      

    • Inside the onPostExecute() method of your AsyncTask, you should retrieve a list (that should come from the doInBackground() method) containing the results to be displayed in the ResultList (you can map it in a POJO class and pass it to your custom adapter or you can use a CursorAdapter which would be the best practive for this task):

      protected void onPostExecute(List resultList) {
           SearchAdapter adapter = new SearchAdapter(resultList);
           searchResultList.setAdapter(adapter);
      }
      
      protected List doInBackground(Void[] params) {
          Cursor results = results = queryCustomSuggestionProvider();
          List<ResultItem> resultList = new ArrayList<>();
      
          Integer headerIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
          Integer subHeaderIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
          Integer leftIconIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
          Integer rightIconIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
      
          while (results.moveToNext()) {
              String header = results.getString(headerIdx);
              String subHeader = (subHeaderIdx == -1) ? null : results.getString(subHeaderIdx);
              Integer leftIcon = (leftIconIdx == -1) ? 0 : results.getInt(leftIconIdx);
              Integer rightIcon = (rightIconIdx == -1) ? 0 : results.getInt(rightIconIdx);
      
              ResultItem aux = new ResultItem(header, subHeader, leftIcon, rightIcon);
              resultList.add(aux);
          }
      
          results.close();
          return resultList;
      

    • Identify when the user touches the search button from the soft keyboard. When he does that, send an intent to the searchable activity (the one responsible for handling the search result) and add the query as extra information in the intent

      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
          super.onActivityResult(requestCode, resultCode, data);
          switch (requestCode) {
              case VOICE_RECOGNITION_CODE: {
                  if (resultCode == RESULT_OK && null != data) {
                      ArrayList<String> text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                      searchInput.setText(text.get(0));
                  }
                  break;
              }
          }
      }
      

    • Identify when the user clicks in one of the displayed suggestions and send and intent containing the item information (this intent should be different from the one of the previous step)

      private void sendSuggestionIntent(ResultItem item) {
          try {
              Intent sendIntent = new Intent(this, Class.forName(searchableActivity));
              sendIntent.setAction(Intent.ACTION_VIEW);
              sendIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
      
              Bundle b = new Bundle();
              b.putParcelable(CustomSearchableConstants.CLICKED_RESULT_ITEM, item);
      
              sendIntent.putExtras(b);
              startActivity(sendIntent);
              finish();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }
      }
      

    The discribed steps should be enough for implementing an interface yourseld. All code examples were taken from here. I've made this library that does pretty much of what is described above. It is not well tested yet and some of the UI configuration might not be available yet.

    I hope this answer might help someone in need.

    这篇关于如何在操作栏中构建类似 Gmail 的搜索框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆