Search
Overview
Search Dialog
Android は、システム共通の検索ダイアログを持っている。AndroidManifest.xml 内で、このダイアログを利用するアクティビティを指定する。
<activity android:name=".SearchableActivity" >
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<intent-filter>にIntent.ACTION_SEARCHを加える。meta-data[@android:name="android.app.searchable"]に、検索ダイアログの設定を記述したXMLリソースを指定する。
SearchableInfo
検索ダイアログの設定は XML リソースから行なう。
res/xml/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/searchable_hint">
</searchable>
android:labelのみが必須の属性になる。Global Search で表示されるアプリケーション名を指定する。android:hintに検索クエリ未入力時のヒント文字列を指定する。- これらの二つの属性値は
@stringリソースからでないと認識されないバグがある。リテラル文字列で指定しても有効にならない。
この設定ファイルを元に SearchableInfo オブジェクトが生成される。アプリケーションからは SearchManager から取得できる。
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
SearchableInfo は XML リソースからの生成のみに限られ、ランタイム時に動的に生成することはできない。
onSearchRequested
ボタンやメニューのフックとして、Activity#onSearchRequested を呼ぶ事で検索ダイアログが表示される。検索操作により、Intent.ACTION_SEARCH が、アクティビティに送信される。
検索毎に、新たなアクティビティが生成され Back Stack に積まれて行くので、起動中のアクティビティに送信するには android:launchMode="singleTop" を指定する。ただし、Back Stack から検索履歴を辿ることはできなくなる。
<activity android:name=".SearchableActivity"
android:launchMode="singleTop">
検索クエリ文字列は、キー名 SearchManager.QUERY の Extra から取得する。
@Override
public void onCreate(Bundle savedInstanceState) {
...
handleIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String q = intent.getStringExtra(SearchManager.QUERY);
...
}
}
android:launchMode="single" の場合、アクティビティは生成済みのため Activity#onNewIntent で Intent を取得する。
startSearch
検索クエリ以外の追加情報を渡したい時は、Activity#onSearchRequested をオーバーライドする。Activity#startSearch を介して、Bundle で追加情報を登録する。
@Override
public boolean onSearchRequested() {
Bundle appData = new Bundle();
appData.putBoolean(SearchableActivity.SEARCH_OPTION_FOO, true);
startSearch(null, false, appData, false);
return true;
}
Intent#getBundleExtra より、キー名 SearchManager.APP_DATA で取得する。
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
boolean foo = appData.getBoolean(SearchableActivity.SEARCH_OPTION_FOO);
...
}
android.app.default_searchable
検索ダイアログのみ利用し、Intent.ACTION_SEARCH を受け取る検索処理は、他のアクティビティに受け渡すこともできる。meta-data[@android:name="android.app.default_searchable"] に、受け渡し先のアクティビティを指定すればよい。
<activity android:name=".MainActivity">
<meta-data android:name="android.app.default_searchable"
android:value=".SearchableActivity" />
</activity>
SearchView Widget
Android 3.0 (API11) 以上であれば、Widget の android.widget.SearchView を使うことができる。
Action Bar 内で利用する場合は、android:actionVewClass で、クラス名を指定する。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_search"
...
android:actionViewClass="android.widget.SearchView" />
</menu>
SearchView に対し、SearchableInfo をセットするだけでよい。Activity#onSearchRequested は検索ダイアログを起動するハンドラなので、 Widget 利用時に呼ぶ必要はない。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconified(false);
return super.onCreateOptionsMenu(menu);
}
appcompat-v7 では、android.support.v7.widget.SearchView を指定する。
<menu ...
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_search"
...
app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>
SearchView は、android.support.v4.view.MenuItemCompat を使って取得する。
// Use the static method MenuItemCompat#getActionView, instead
SearchView searchView = (SearchView) MenuItemCompat.getActionView(
menu.findItem(R.id.action_search));
Query Suggestions
Content Provider により、検索クエリの候補表示を行なうことができる。
searchable[@android:searchSuggestAuthority] に Content Provider の Authority を指定する。
<searchable ...
android:searchSuggestAuthority="net.example.android.search.SuggestionsProvider" />
検索クエリの入力毎に ContentProvider#query が呼ばれるので、適宜、候補文字列への Cursor を返せばよい。URI の最後尾パスに、検索クエリがURLエンコードされて渡される。
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// content://net.example.android.search.SuggestionProvider/(encodedQueryString)?...
Log.i(TAG, uri.toString());
String q = uri.getLastPathSegment();
...
}
searchable[@android:searchSuggestSelection] に、ContentProvider#query の第2引数 selection に渡す文字列(WHERE 節等)を指定できる。この場合、検索クエリは、第3引数の selectionArgs に渡される。selection 文字列をどう使うかは、ContentProvider#query メソッドの実装次第なので、単に selectionArgs から検索クエリを受け取るために、ダミーの android:searchSuggestSelection を指定する方法もある。
<searchable ...
android:searchSuggestAuthority="net.example.android.search.SuggestionsProvider"
android:searchSuggestSelection=" ? " />
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
if (1 != selectionArgs.length)
throw new IllegalArgumentException("The length of selectionArgs must be 1.");
String q = selectionArgs[0];
...
}
Cursor には、主に以下のカラムが必要になる。
BasicColumns._ID: 候補一覧の Adapter から選択するために必要SearchManager.SUGGEST_COLUMN_TEXT_1: 候補文字列SearchManager.SUGGEST_COLUMN_TEXT_2: 候補説明文SearchManager.SUGGEST_COLUMN_QUERY:Intent.ACTION_SEARCHの場合、検索クエリとしてSerchManager.QUERYに渡す文字列
候補選択時に Intent.ACTION_SEARCH ではなく、任意の Intent を発行することもできる。
- http://developer.android.com/guide/topics/search/adding-custom-suggestions.html#IntentForSuggestions
Cursor の各レコードに SearchManager.SUGGEST_COLUMN_INTENT_* のカラム値を含めればよい。
SearchManager.SUGGEST_COLUMN_INTENT_ACTIONSearchManager.SUGGEST_COLUMN_INTENT_DATASearchManager.SUGGEST_COLUMN_INTENT_DATA_IDSearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA
全ての候補に共通な値は、SearchableInfo に含めることもできる。
<!--
searchSuggestIntentAction: SUGGEST_COLUMN_INTENT_ACTION
searchSuggestIntentData : SUGGEST_COLUMN_INTENT_DATA
-->
<searchable ...
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSuggestIntentData="content://net.example.android.data" />
SearchRecentSuggestionsProvider
検索クエリの履歴を候補にするなら、SearchRecentSuggestionsProvider を継承した Content Provider を使えば良い。
import android.content.SearchRecentSuggestionsProvider;
public class SearchHistoryProvider extends SearchRecentSuggestionsProvider {
public final static String AUTHORITY = "net.example.android.search.SearchHistoryProvider";
public final static int MODE = DATABASE_MODE_QUERIES;
public SearchHistoryProvider() {
setupSuggestions(AUTHORITY, MODE);
}
}
SearchRecentSuggestionsProvider#query の実装では、検索クエリを selectionArgs から取得するため、android:searchSuggestSelection には、ダミー文字列 ? を指定する。
<searchable ...
android:searchSuggestAuthority="net.example.android.search.SearchHistoryProvider"
android:searchSuggestSelection=" ? " />
履歴に追加する検索クエリは SearchRecentSuggestions#saveRecentQuery を介して登録する。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE);
suggestions.saveRecentQuery(query, null);
}
}
履歴の削除には SearchRecentSuggestions#clearHistory を用いる。ユーザのプライバシーのため、アプリケーションでは必ず提供しておくべきである。