Недавние предложения запросов во фрагменте

Я реализовал SearchView в Fragment, как предложил @David в этой теме. Кто-нибудь знает, как добавить Recent Query Suggestions в SearchView во фрагменте?


person hornet2319    schedule 06.10.2015    source источник
comment
что вы подразумеваете под последними предложениями по запросу?   -  person pskink    schedule 06.10.2015
comment
@pskink Я имею в виду поисковые предложения, основанные на последних поисковых запросах.   -  person hornet2319    schedule 06.10.2015
comment
где вы хотите, чтобы ваши предложения были размещены? в выпадающем списке?   -  person pskink    schedule 06.10.2015
comment
@pskink да, как в Play Market.   -  person hornet2319    schedule 06.10.2015


Ответы (2)


Из документации по Android.

ШАГ 1. Создайте поставщика контента

Создайте класс MySuggestionProvider, мой находится в searchviewpager/utils/MySuggestionProvider

package com.soon.karat.searchviewpager.utils;

import android.content.SearchRecentSuggestionsProvider;

public class MySuggestionProvider extends SearchRecentSuggestionsProvider {
    // AUTHORITY is a unique name, but it is recommended to use the name of the 
    // package followed by the name of the class.
    public final static String AUTHORITY = "com.soon.karat.searchviewpager.utils.MySuggestionProvider";

    // Uncomment line below, if you want to provide two lines in each suggestion:
    // public final static int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;
    public final static int MODE = DATABASE_MODE_QUERIES;

    public MySuggestionProvider() {
        setupSuggestions(AUTHORITY, MODE);
    }
}

ШАГ 2. Добавьте провайдера в манифест

Добавьте следующую строку кода в свой манифест на уровне приложения.

<application...>
        <provider
            android:name=".utils.MySuggestionProvider"
            android:authorities="com.soon.karat.searchviewpager.utils.MySuggestionProvider" />

android:name: это путь, по которому находится ваш класс SuggestionProvider. Мой находится в searchviewpager/utils/MySuggestionProvider --> поэтому имя будет .utils.MySuggestionProvider

android:authorities: это та же самая строка AUTHORITY, которую вы указали в SuggestionProvider. Она должна иметь то же имя.

ШАГ 3. Добавьте поиск в файл searchable.xml

Добавьте две последние строки (searchSuggestAuthority и searchSuggestSelection) в файл searchable.xml.

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_for_anything"
    android:searchSuggestAuthority="com.soon.karat.searchviewpager.utils.MySuggestionProvider"
    android:searchSuggestSelection=" ?"/>

android:searchSuggestAuthority: это та же строка AUTHORITY, которую вы указали в своем классе MySuggestionProvider.

android:searchSuggestSelection: это просто пробел и маркер вопроса.

ШАГ 4. Сохраните запросы

Всякий раз, когда пользователь отправляет запрос, вы должны сохранить его в своем SuggestionProvider, скажем, вы сохраните его внутри onQueryTextSubmit.

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            Log.i(TAG, "onQueryTextSubmit: Query was submitted");

            // -------------------------------------------------------
            //           Lines responsible to save the query 
            // -------------------------------------------------------
            SearchRecentSuggestions suggestions = new SearchRecentSuggestions(MainActivity.this,
                    MySuggestionProvider.AUTHORITY,
                    MySuggestionProvider.MODE);
            suggestions.saveRecentQuery(query, null);
            // -------------------------------------------------------

            displaySearchResults(query);

            // The listener can override the standard behavior by returning true to indicate that it has
            // handled the submit request. Otherwise return false to let the SearchView handle the
            // submission by launching any associated intent.
            return true;
        }

ДОПОЛНИТЕЛЬНО

Скрыть клавиатуру и закрыть SearchView

Возможно, вы хотите скрыть клавиатуру и закрыть окно поиска, когда пользователь завершит отправку запроса в ваше приложение. Для этого вы используете следующие строки кода.

private void dismissKeyBoardAndSearchView() {
    Helpers.hideKeyBoard(this);
    // You should check if MenuItem is not null, otherwise the app would crash when rotating the screen 
    if (searchMenuItem != null) {
        searchMenuItem.collapseActionView();
    }
}

searchMenuItem — это MenuItem, и это то же самое, что вы использовали при создании SearchView в onCreateOptionsMenu, вы просто объявляете его глобально для доступа в этом другом методе или анализируете его в методе:

@Override
public boolean onCreateOptionsMenu(Menu menu) {

    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);

    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    // -----------------------------------------------
    //     This is the MenuItem you will collapse
    // -----------------------------------------------
    searchMenuItem = menu.findItem(R.id.menu_search);
    // -----------------------------------------------
    SearchView searchView = (SearchView) searchMenuItem.getActionView();
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setQueryRefinementEnabled(true);

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {

Скрытие клавиатуры внутри класса Helpers

public class Helpers {

public static void hideKeyBoard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    View view = activity.getCurrentFocus();
    if (view == null) {
        view = new View(activity);
    }
    assert inputMethodManager != null;
    inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}

}

Обрабатывать, когда пользователь нажимает на предложение

Вы можете заметить, что когда пользователь нажимает на предложение, ничего не происходит, потому что onQueryTextSubmit не вызывается. Android просто перезапускает действие и отправляет ему запрос. Затем для обработки кликов в предложении необходимо добавить следующий код:

Предположим, что действие, которое выполняет поиск, совпадает с действием, которое получает поиск:

ШАГ 1. Установите для параметра launchMode значение singleTop

Задайте для запуска действия значение singleTop в манифесте, чтобы система не создавала действие несколько раз, когда пользователь выполняет несколько поисков.

<application...>
    <provider
        android:name=".utils.MySuggestionProvider"
        android:authorities="com.soon.karat.searchviewpager.utils.MySuggestionProvider" />
    <activity android:name=".SearchableActivity"
        android:launchMode="singleTop">
        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>
        <meta-data
            android:name="android.app.searchable"
            android:resource="@xml/searchable" />
    </activity>

STEP 2 - Handle the intent in onCreate and in onNewIntent

Вы должны обрабатывать цель поиска как в onCreate, так и в onNewItent. onNewIntent необходим, потому что вы устанавливаете свою активность в singleTop, когда приложение получает поиск, вместо того, чтобы воссоздавать активность и вызывать onCreate, оно просто вызывает onNewIntent.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    handleIntent(getIntent());
}


@Override
protected void onNewIntent(Intent intent) {
    setIntent(intent);
    handleIntent(intent);
}

Настройка макета подсказки для недавнего запроса

Возможно, вам не нравится, как отображается ваш недавний запрос, тогда вы можете изменить его макет простым способом.

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>

    <!-- Add this line -->
    <item name="searchViewStyle">@style/MySearchViewStyle</item>
</style>

<style name="MySearchViewStyle" parent="Widget.AppCompat.SearchView" >
    <item name="suggestionRowLayout">@layout/my_search_dropdown_item_icons</item>
</style>

my_search_drodown_item_icons: это макет, который вы создаете и настраиваете.

ВНИМАНИЕ: вы должны использовать те же идентификаторы, которые были у Android для предыдущего макета, чтобы он работал. Затем перейдите в этот файл @layout/abc_search_dropdown_item_icons_2line и скопируйте те же идентификаторы.

Если вам сложно найти этот файл abc, вы можете заменить @layout/my_search_dropdown_item_icons на --> @layout/abc_search_dropdown_item_icons_2line --> затем поместите курсор на «abc_search...» и нажмите Ctrl+B. Вы будете перенаправлены в этот файл, где вы можете взять те же идентификаторы.

person Soon Santos    schedule 04.07.2018
comment
Как это связано с фрагментом, он все еще использует действие для обработки последних предложений? - person duskandawn; 12.07.2020

Итак, я нашел это решение с помощью этого конкретного репозитория на github, спасибо ему и спасибо.

https://github.com/nex3z/android-examples/tree/master/CustomSearchSuggestionItem

Вот пример реализации того, как вы можете интегрировать представление поиска во фрагменте с недавним запросом предложения в Kotlin -

  1. Создать адаптер для отображения списка последних

    SearchAdapter.java

     public class SearchAdapter extends CursorAdapter {
    
     private static final String LOG_TAG = 
     SearchAdapter.class.getSimpleName();
    
     public SearchAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
      }
    
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
     View view = LayoutInflater.from(context).inflate(
               R.layout.search_item, parent, false);
    
     ViewHolder viewHolder = new ViewHolder(view);
     view.setTag(viewHolder);
    
     return view;
      }
    
     @Override
     public void bindView(View view, Context context, Cursor cursor) {
       ViewHolder viewHolder = (ViewHolder) view.getTag();
       viewHolder.mTitle.setText(
       cursor.getString(cursor.
           getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)));
         }
    
      public String getSuggestionText(int position) {
        if (position >= 0 && position < getCursor().getCount()) {
        Cursor cursor = getCursor();
        cursor.moveToPosition(position);
        return cursor.getString(cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
        }
        return null;
          }
    
        public static class ViewHolder {
         public TextView mTitle;
    
            public ViewHolder(View view) { 
            // view of your custom layout
            mTitle = (TextView) view.findViewById(R.id.text);
              }
           }
         }
    

    search_item.xml (внутри папки макета)

      <TextView xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tool="http://schemas.android.com/tools"
      android:id="@+id/text"
      android:layout_width="wrap_content"
      android:layout_height="50dp"
      android:fontFamily="@font/poppins_bold"
      android:textColor="@color/colorPrimary"
      android:gravity="start|center"
      android:paddingStart="30dp"
      android:paddingEnd="30dp"
      tool:text="hello" />
    

    menu.xml (поместите его в файл menu.xml)

    <item
    android:id="@+id/action_search"
    android:icon="@android:drawable/ic_menu_search"
    android:title="@string/search"
    app:actionViewClass="androidx.appcompat.widget.SearchView"
    app:showAsAction="always" />
    

    SuggestionProvider.kt

     class SuggestionProvider : SearchRecentSuggestionsProvider() {
       init {
        setupSuggestions(AUTHORITY, MODE)
       }
    
       companion object {
        // Set As Path To This File
        const val AUTHORITY = "com.your_package_name.SuggestionProvider"
        const val MODE: Int = DATABASE_MODE_QUERIES
       }
     }
    

    AndroidManifest.xml (убедитесь, что в поле android:authrotities указан тот же путь, что и в поле AUTHORITY FIELD SuggestionProvider.kt)

      <application>
       .....
        <provider
        android:name=".utils.SuggestionProvider"
        android:authorities="com.you_package_name.SuggestionProvider" />
    
       .....
       </application>
    

    YourFragment.kt (поместите этот код в уже созданный фрагмент, где вы хотите, чтобы панель поиска отображалась на панели действий)

    private var mSuggestionAdapter: SearchAdapter? = null
    private lateinit var searchView: SearchView
    private var queryTextListener: SearchView.OnQueryTextListener? = null
    
    
    
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        R.id.action_search ->
            return false
    }
    searchView.setOnQueryTextListener(queryTextListener)
    return super.onOptionsItemSelected(item)
    }
    
    
    
    
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    inflater.inflate(R.menu.menu, menu)
    val searchItem = menu.findItem(R.id.action_search)
    val searchManager =
        activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
    
    mSuggestionAdapter = SearchAdapter(activity, null, 0)
    
    if (searchItem != null) {
        searchView = searchItem.actionView as SearchView
    }
    if (searchView != null) {
        searchView.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
        searchView.suggestionsAdapter = mSuggestionAdapter;
    
        queryTextListener = object : SearchView.OnQueryTextListener {
            override fun onQueryTextChange(newText: String): Boolean {
                // Update Cursor With Each Query Text Change
                val cursor = getRecentSuggestions(newText)
                if (cursor != null) {
                    mSuggestionAdapter?.swapCursor(cursor)
                }
                return false
            }
    
            override fun onQueryTextSubmit(query: String): Boolean {
    
                // Save Submitted Query To Adapter
                val suggestions = SearchRecentSuggestions(
                    activity,
                    SuggestionProvider.AUTHORITY, SuggestionProvider.MODE
                )
                suggestions.saveRecentQuery(query, null)
    
                // Do Your Search Stuff Here With Query
                return true
            }
        }
    
        searchView.setOnSuggestionListener(object : SearchView.OnSuggestionListener {
            override fun onSuggestionSelect(position: Int): Boolean {
                return false
            }
    
            override fun onSuggestionClick(position: Int): Boolean {
            // On Clicking Suggestion Load It To Submit Query Listener    
    
          searchView.setQuery(mSuggestionAdapter?.getSuggestionText(position), true)
                return true
            }
        })
    
    
        searchView.setOnQueryTextListener(queryTextListener)
    
         }
         super.onCreateOptionsMenu(menu, inflater)
        }
    
    
        // Function To Retrieve Suggestion From Content Resolver
        fun getRecentSuggestions(query: String): Cursor? {
         val uriBuilder = Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(SuggestionProvider.AUTHORITY)
    
        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY)
    
         val selection = " ?"
         val selArgs = arrayOf(query)
    
         val uri = uriBuilder.build()
        return activity?.contentResolver?.query(uri, null, selection, selArgs, null)
         }
    

Вот и все, у вас есть панель поиска в вашем фрагменте, результат запроса которого вы можете контролировать, как вы обрабатываете поиск и последнее предложение.

person iamsr    schedule 06.12.2019
comment
Спасибо за это решение! Глобально это работает нормально, но иногда я получаю эту ошибку без каких-либо подробностей и следов: java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteQuery: SELECT 0 AS suggest_format, 'android.resource://system/17301578' AS suggest_icon_1, display1 AS suggest_text_1, query AS suggest_intent_query, _id FROM suggestions WHERE display1 LIKE ? ORDER BY date DESC У вас есть идеи, как это исправить? - person Thomas Pires; 14.01.2021
comment
Это сработало, спасибо - person Syed Umair; 10.02.2021