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_ACTION
SearchManager.SUGGEST_COLUMN_INTENT_DATA
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
SearchManager.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
を用いる。ユーザのプライバシーのため、アプリケーションでは必ず提供しておくべきである。