Как создать Gmail как окно поиска в панели действий?

В настоящее время я использую виджет SearchView внутри ActionBarcompat для фильтрации списка во время поиска.

Когда пользователь начинает вводить текст, ListView в основном макете обновляется адаптером для фильтрации результатов. Я делаю это, реализуя OnQueryTextListener и OnQueryTextListener результаты на каждом OnQueryTextListener ключа.

Вместо этого я хочу создать поле поиска в формате Gmail с открытым списком автопредложений и без изменений в базовом представлении

Введите описание изображения здесь

Я просмотрел этот учебник, в котором используется компонент SearchView но для него требуется операция с возможностью поиска . Я хочу, чтобы выпадающее меню было поверх MainActivity, где у меня есть ListView (например, в приложении Gmail), а не посвященный Activity.
Кроме того, реализация его так же, как в учебнике, кажется излишним для того, что я хочу (просто выпадающее меню)

Если вам просто нужен компонент, который делает то, что описано в вопросе, я предлагаю эту библиотеку . Вы также можете реализовать интерфейс для поиска вне поля, но помните, что он имеет ограничения пользовательского интерфейса:

Чтобы реализовать интерфейс, похожий на приложение Gmail, вам нужно будет понять:

  • Поставщики контента;
  • Сохранение данных в SQLite
  • Listview или RecyclerView и его адаптеры;
  • Передача данных между действиями;

Конечный результат должен выглядеть примерно так:

конечный результат

Есть много (многих) способов добраться до одного и того же результата (или лучше), я расскажу один из возможных способов.

Часть 01: Макет

Я решил управлять всем интерфейсом в новом Activity, потому что я создал три XML-макета:

  • Custom_searchable.xml : собирает все элементы пользовательского интерфейса в одном RelativeLayout, который будет служить в качестве контента для 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 : содержит панель поиска, в которой пользователь вводит свой запрос. Он также будет содержать микрофон, стирание и возврат 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 : содержит элементы пользовательского интерфейса, которые будут отображаться в списке результатов, который будет отображаться в ответ на запрос пользователя;

     <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

Идея заключается в том, что, когда пользователь вводит кнопку поиска (которую вы можете разместить там, где хотите), будет вызываться эта функция SearchActivity . Он имеет некоторые основные обязанности:

  • Привяжите элементы пользовательского интерфейса в custom_searchable_header_layout.xml : сделав это, можно:

  • Для предоставления слушателям 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 } } 
  • Добавьте слушателя для кнопки возврата (которая по своему ходу будет просто называть финиш () и вернуться к активности вызывающего абонента):

     this.dismissDialog.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { finish(); } 
  • Вызывает намерение API-интерфейса google go-to-text:

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

Поставщик услуг

При построении интерфейса поиска разработчик имеет типично два варианта:

  1. Предложите последние запросы пользователю: это означает, что каждый раз, когда пользователь выполняет поиск, типизированный запрос сохраняется в базе данных, которая будет возвращена последней в качестве предложения для будущих поисков;
  2. Предложите пользовательские параметры пользователю: разработчик попытается предсказать, чего хочет пользователь, обработав уже набранные буквы;

В обоих случаях ответы должны быть возвращены как объект Cursor, который будет отображаться в списке результатов в виде списка. Весь этот процесс может быть реализован с использованием API-интерфейса Content Provider. Подробную информацию о том, как использовать Content Providers, можно найти по этой ссылке .

В случае, когда разработчик хочет реализовать поведение, описанное в 1., может быть полезно использовать стратегию выкидывания класса SearchRecentSuggestionsProvider. Подробности о том, как это сделать, можно найти по этой ссылке .

Реализация интерфейса поиска

Этот интерфейс должен обеспечивать следующее поведение:

  • Когда пользователь вводит письмо, вызов метода запроса из класса поставщика контента должен возвращать заполненный курсор с предложением, которое должно отображаться в списке, – вы должны предпринять, чтобы не заморозить поток пользовательского интерфейса, поэтому я рекомендую выполнить это Поиск в 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(); } } 
  • Внутри метода onPostExecute () вашего AsyncTask вы должны получить список (который должен исходить из метода 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; 
  • Определите, когда пользователь коснется кнопки поиска с мягкой клавиатуры. Когда он это сделает, отправьте намерение на операцию поиска (отвечающую за обработку результата поиска) и добавьте запрос в качестве дополнительной информации в намерении

     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; } } } 
  • Определите, когда пользователь нажимает на одно из отображаемых предложений и отправляет и намерение, содержащее информацию об элементе (это намерение должно отличаться от предыдущего)

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

Описанных шагов должно быть достаточно для реализации интерфейса yourseld. Все примеры кода были взяты здесь . Я создал эту библиотеку, которая делает почти то, что описано выше. Он еще не тестирован, и некоторые из настроек пользовательского интерфейса могут быть недоступны.

Надеюсь, этот ответ может помочь кому-то в этом нуждаться.

Я создал небольшой учебник, чтобы сделать это

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

обзор

Мне пришлось заменить SearchView на AutoCompleteTextView как было предложено.

Сначала создайте адаптер. В моем случае это был 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); } } } } 

Теперь настройте макет для окна поиска ( 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); } 

Вот и все, у меня все еще есть кое-что, чтобы понять:

  • Это ставит «всегда там» поиск в панели действий, я хочу, чтобы он был похож на виджет SearchView – стекло лупы, которое открывается до окна поиска, когда вы нажимаете на него (и имеет небольшую кнопку X , чтобы отклонить его и вернуться К нормальному)
  • Не выяснили, как настроить выпадающий ящик, например, у Gmail, похоже, есть тень, моя просто плоская, меняет цвет разделителей строк и т. Д. …

В целом это экономит все накладные расходы на создание поиска . Добавьте, если вы знаете, как его настроить и т. Д.

Если вы хотите реализовать только эффект падения, перейдите в AutoCompleteTextView

И вы можете найти хороший учебник здесь

Затем, если вы хотите реализовать точный дизайн, вам нужно реализовать ActionBar, и если вы хотите реализовать более низкую версию, вам нужно реализовать ActionBarCombat вместо ActionBar

Вы должны использовать ListPopupWindow и привязать его к виджету вида поиска.

Я успешно использовал ответ Майклса ( https://stackoverflow.com/a/18894726/2408033 ), но мне не нравилось, как это было вручную, раздувая представления и добавляя их в панель действий, переключая свое состояние и т. Д.

Я изменил его, чтобы использовать ActionBar ActionView вместо добавления вида вручную на панель действий / панель инструментов.

Я считаю, что это работает намного лучше, так как мне не нужно управлять состоянием open / close и скрывать представления, как это было в его примере в методе toggleSearch в добавленной ссылке. Он также отлично работает с кнопкой «Назад».

В моем меню.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); 

В моем проекте вы можете найти полностью работоспособную версию реализации. Обратите внимание, что есть два вида поиска, поскольку я использовал фактический SearchView для фильтрации спискаView.

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>