如何在操作栏中像search框一样构buildGmail?

我正在使用ActionBarcompat内的SearchView小部件来search时过滤列表。

当用户开始input文本时,主布局中的ListView将使用适配器进行更新以过滤结果。 我这样做是通过实现一个OnQueryTextListener并筛选每个关键笔划的结果。

相反,我想创build一个带有自动build议列表的search框,并且不会更改底层视图

在这里输入图像说明

我已经通过使用SearchView组件的这个教程 ,但它需要一个可search的活动。 我希望下拉菜单能够在MainActivity中获得ListView(就像在Gmail应用程序中一样),而不是一个专门的Activity。
另外,像在教程中一样执行它似乎是一个矫枉过正(我只是一个下拉列表)

Solutions Collecting From Web of "如何在操作栏中像search框一样构buildGmail?"

如果你只是想要一个组件来完成问题中所描述的内容,我build议这个库 。 您也可以实现out-of-the-bx 可search的界面 ,但请注意,它具有UI限制:

要实现与Gmail应用类似的界面,您必须了解以下几点:

  • 内容提供者;
  • 在SQLite中保留数据
  • Listview或RecyclerView及其适配器;
  • 在活动之间传递数据

最终结果应该如下所示:

最后结果

有很多(很多)方法可以得到相同的结果(或更好的),我会描述一个可能的方法。

第01部分:布局

我决定在一个新的Activity中pipe理整个界面,因为我创build了三个XML布局:

  • custom_searchable.xml :在一个RelativeLayout中汇编所有的UI元素,作为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 :保存用户input查询的search栏。 它也将包含麦克风,擦除和返回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 :保存要显示在结果列表中的UI元素以响应用户查询而显示;

     <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"/> 

第02部分:实现SearchActivity

这个想法是,当用户键入searchbutton(你可以放在任何你想要的地方),这个SearchActivity将被调用。 它有一些主要的责任:

  • 绑定到custom_searchable_header_layout.xml中的UI元素:通过这样做,可以:

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

     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 } } 
  • 添加监听器的返回button(这将轮到刚才调用finish()并返回到调用者的活动):

     this.dismissDialog.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { finish(); } 
  • 调用谷歌语音到文本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); } } }); } 

内容提供商

在构buildsearch界面时,开发人员有两个select:

  1. 向用户build议最近的查询:这意味着每次用户进行search时,types化的查询将被保存在数据库中,以供将来检索作为未来search的build议;
  2. 向用户build议自定义选项:开发者将尝试通过处理已经input的字母来预测用户想要的内容;

在这两种情况下,答案都应该作为一个Cursor对象来传递,这个Cursor对象的内容在结果列表中显示为itens。 整个过程可以使用Content Provider API来实现。 有关如何使用内容提供商的详细信息可以在此链接中find 。

在开发人员想要实现1中描述的行为的情况下,使用SearchRecentSuggestionsProvider类的策略是有用的。 有关如何做到这一点的详细信息可以在这个链接 。

实现search界面

该接口应提供以下行为:

  • 当用户input一个字母时,对检索到的内容提供者类的查询方法的调用应该返回一个已填充的游标,并在列表中显示build议 – 您应该不冻结UI线程,所以我build议执行此操作在AsyncTask中search:

      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(); } } 
  • 在你的AsyncTask的onPostExecute()方法中,你应该检索一个列表(应该来自doInBackground()方法),该列表包含要显示在ResultList中的结果(你可以将它映射到一个POJO类,并将它传递给你的自定义适配器,或者你可以使用CursorAdapter来完成这个任务):

     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; 
  • 确定用户何时从软键盘触摸searchbutton。 当他这样做时,向可search的活动(负责处理search结果的活动)发送一个意图,并将该查询添加为意图中的额外信息

     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; } } } 
  • 识别用户何时点击显示的build议中的一个,并发送和意图包含项目信息(这个意图应该不同于前面的步骤)

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

所描述的步骤应该足够实现一个接口youeld。 所有的代码示例都是从这里取得的 。 我已经做了这个库,它几乎完成了上面所描述的。 它还没有很好的testing,一些UIconfiguration可能还没有。

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

我已经build立了一个小教程来做到这一点

http://drzon.net/how-to-create-a-clearable-autocomplete-dropdown-with-autocompletetextview/

概观

build议我必须用AutoCompleteTextViewreplaceSearchView

首先,创build一个适配器。 在我的情况下,它是一个JSONObject ArrayAdapter。 我想在下拉列表中显示的数据是场地名称和场地地址。 请注意,适配器必须是Filtarable并重写getFilter()

 // adapter for the search dropdown auto suggest ArrayAdapter<JSONObject> searchAdapter = new ArrayAdapter<JSONObject>(this, android.R.id.text1) { private Filter filter; public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.getLayoutInflater().inflate(R.layout.search_item, parent, false); } TextView venueName = (TextView) convertView.findViewById(R.id.search_item_venue_name); TextView venueAddress = (TextView) convertView.findViewById(R.id.search_item_venue_address); final JSONObject venue = this.getItem(position); convertView.setTag(venue); try { CharSequence name = highlightText(venue.getString("name")); CharSequence address = highlightText(venue.getString("address")); venueName.setText(name); venueAddress.setText(address); } catch (JSONException e) { Log.i(Consts.TAG, e.getMessage()); } return convertView; } @Override public Filter getFilter() { if (filter == null) { filter = new VenueFilter(); } return filter; } }; 

这是自定义的VenueFilter

 private class VenueFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { List<JSONObject> list = new ArrayList<JSONObject>(venues); FilterResults result = new FilterResults(); String substr = constraint.toString().toLowerCase(); if (substr == null || substr.length() == 0) { result.values = list; result.count = list.size(); } else { final ArrayList<JSONObject> retList = new ArrayList<JSONObject>(); for (JSONObject venue : list) { try { if (venue.getString("name").toLowerCase().contains(constraint) || venue.getString("address").toLowerCase().contains(constraint) || { retList.add(venue); } } catch (JSONException e) { Log.i(Consts.TAG, e.getMessage()); } } result.values = retList; result.count = retList.size(); } return result; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { searchAdapter.clear(); if (results.count > 0) { for (JSONObject o : (ArrayList<JSONObject>) results.values) { searchAdapter.add(o); } } } } 

现在设置search框( actionbar_search.xml )的布局:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="match_parent" android:layout_gravity="fill_horizontal" android:focusable="true" > <AutoCompleteTextView android:id="@+id/search_box" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:dropDownVerticalOffset="5dp" android:dropDownWidth="wrap_content" android:inputType="textAutoComplete|textAutoCorrect" android:popupBackground="@color/white" android:textColor="#FFFFFF" > </AutoCompleteTextView> </RelativeLayout> 

和个人下拉项目(场地名称和场地地址)的布局。 这看起来不好,你必须定制它:

 <?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:textAlignment="gravity" > <TextView android:id="@+id/search_item_venue_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/cyan" android:layout_gravity="right" /> <TextView android:id="@+id/search_item_venue_address" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toStartOf="@+id/search_item_venue_name" android:gravity="right" android:textColor="@color/white" /> </RelativeLayout> 

接下来我们要把它放在操作栏中

 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_USE_LOGO | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP); LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflater.inflate(R.layout.actionbar_search, null); AutoCompleteTextView textView = (AutoCompleteTextView) v.findViewById(R.id.search_box); textView.setAdapter(searchAdapter); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // do something when the user clicks } }); actionBar.setCustomView(v); } 

就是这样,我还有一些东西需要弄清楚:

  • 这会在操作栏中进行“总是在那里”search,我希望它像SearchView小部件一样 – 放大镜玻璃,当您点击它时打开一个search框(并且有一个Xbutton可以将其解除并返回到正常)
  • 还没有想出如何自定义下拉框,例如,Gmail的似乎有影子,我的只是平坦的,改变线分隔符的颜色等…

总的来说,这节省了创build可search活动的所有开销。 如果您知道如何自定义,请添加

如果你想实现只有下拉效果去AutoCompleteTextView

你可以在这里find一个很好的教程

那么如果你想实现精确的devise,你必须实现ActionBar ,如果你想实现更低的版本,你需要实现ActionBarCombat而不是ActionBar

您应该使用ListPopupWindow并将其锚定到search视图窗口小部件。

我已经成功地使用了Michaels回复( https://stackoverflow.com/a/18894726/2408033 ),但是我不喜欢它是如何使用手动的,将视图膨胀并将其添加到操作栏,切换其状态等。

我修改它以使用ActionBar ActionView,而不是手动添加视图到操作栏/工具栏。

我发现这样做的效果好很多,因为我不需要像添加链接中toggleSearch方法中的示例那样pipe理打开/closures状态和隐藏视图。 它也适用于后退button。

在我的menu.xml中

  <item android:id="@+id/global_search" android:icon="@android:drawable/ic_menu_search" android:title="Search" app:actionLayout="@layout/actionbar_search" app:showAsAction="ifRoom|collapseActionView" /> 

在我的onCreateOptionsMenu

  View actionView = menu.findItem(R.id.global_search).getActionView(); searchTextView = (ClearableAutoCompleteTextView) actionView.findViewById(R.id.search_box); searchTextView.setAdapter(searchAdapter); 

你可以在我的项目中find完整的实现版本。 请注意有两个search视图,因为我正在使用实际的SearchView来过滤listView。

https://github.com/babramovitch/ShambaTimes/blob/master/app/src/main/java/com/shambatimes/schedule/MainActivity.java

请参考这个例子,它实现了你所要求的: http : //wptrafficanalyzer.in/blog/android-searchview-widget-with-actionbarcompat-library/

你需要使用collapseActionView属性。

collapseActionView属性允许您的SearchView展开占据整个操作栏,并在不使用时折回到正常的操作栏项目中。

例如:

 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/search" android:title="@string/search_title" android:icon="@drawable/ic_search" android:showAsAction="collapseActionView|ifRoom" android:actionViewClass="android.widget.SearchView" /> </menu>