Ответ 1
Если вам просто нужен компонент, который делает то, что описано в вопросе, я предлагаю эту библиотеку. Вы также можете реализовать интерфейс , но помните, что он имеет ограничения пользовательского интерфейса:
Чтобы реализовать интерфейс, похожий на приложение 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-речи:
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); } } }); }
Поставщик контента
При создании интерфейса поиска разработчик имеет типично два варианта:
- Предложить последние запросы пользователю: это означает, что каждый раз, когда пользователь выполняет поиск, типизированный запрос сохраняется в базе данных, которая будет извлекаться последним в качестве предложения для будущих поисков;
- Предложить пользовательские параметры пользователю: разработчик попытается предсказать, чего хочет пользователь, обработав уже набранные буквы;
В обоих случаях ответы должны быть возвращены как объект 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. Все примеры кода были взяты из здесь. Я создал эту библиотеку, которая делает почти то, что описано выше. Он еще не тестирован, и некоторые из настроек пользовательского интерфейса могут быть недоступны.
Я надеюсь, что этот ответ может помочь кому-то в этом нуждаться.