Android アプリケーションに 検索機能を組み込む

デバイスの検索ボタンを押したら、検索条件を入力するテキストボックスが出てきて、アプリケーションのデータを検索できるような機能を自作アプリケーションに追加してみる。

http://developer.android.com/guide/topics/search/search-dialog.html

android_search01

2つの方法

実現するには、検索ダイアログ(search dialog) と 検索ウィジェット(search widget) の2つ方法があるようだ。

検索ダイアログは、スクリーンの最前面に表示されるダイアログ。検索ウィジェットは、UIの任意の場所における部品(SearchView) のインスタンスで、EditText と同じような見た目らしい。しかしながら、検索ウィジェットは、Android3.0(APIレベル11)以降の機能らしい。Android3.0 の実機は持っていないし、現状開発対象としていないので、検索ダイアログを採用する。

おおまかな流れ

大きな流れとしては、以下で検索機能を実現可能。検索サジェストや、ボイスサーチの設定もできるようだが、今回はまず、基本的な検索のみを実現したいと思う。

  1. 検索設定XMLを作成 (/res/xml/searchable.xml)
  2. 検索可能なアクティビティの作成
  3. 検索条件の受け取りと結果の表示

 

検索設定XMLの作成

/res/xml/searchable.xml として、以下の様な内容の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_hint" >
</searchable>

設定内容

リファレンス

要素 内容
android:label 必須。アプリケーション名とすべき
android:hint 検索ボックスにヒントを表示。必須ではないが、表示した方がよい

 

その他設定可能な内容

検索可能なアクティビティの作成

AndroidManifext.xml の編集

検索が実行されると、Android システムは、検索クエリを Intent につめて ACTION_SEARCH アクションで送信するしてくるので、作成したアクティビティの設定を AndroidManifest.xml にて対応するように記述する必要がある。

info.typea.eitangoroid.WordSearchableActivity は、今回作成する、検索可能なアクティビティ

 <application 
    :
     <meta-data android:name="android.app.default_searchable"
                 android:value="info.typea.eitangoroid.WordSearchableActivity"/>
     <activity android:name="info.typea.eitangoroid.WordSearchableActivity">
         <intent-filter>
             <action android:name="android.intent.action.SEARCH" />
         </intent-filter>   
         <meta-data android:name="android.app.searchable"
                android:resource="@xml/searchable"/>     
     </activity>
    :
  • アクティビティが ACTION_SEARCH インテントを 受け取るように、<intent-filter>に宣言
  • 検索設定を <meta-data> に記述
  • 検索を実行可能なアクティビティを宣言するには、<meta-data> をアクティビティの <activity> 要素に追加する。(ただし、applicatiopn 直下に記述すると、アプリケーションのすべてのアクティビティから利用可能になる)

 

検索条件の受け取りと結果の表示

検索可能なアクティビティで、以下のことを行う。

  1. 検索クエリの受け取り
  2. 検索
  3. 結果の表示

通常、検索結果は ListView に表示すると良い。また、ListActivity は ListView を含み、いくつかの便利メソッドを提供する。

検索クエリの受け取り

以下の様に、Intent から、検索文字列を取得できるので、あとはそれを利用して検索を実行し、結果を表示してあげればよい。

 @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.search_result);

    Intent intent = getIntent();
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
        String query = intent.getStringExtra(SearchManager.QUERY);
            :
    }

 

ListActivity を利用して、検索可能なアクティビティを作成する

ここまでで、基本的にはOKだが、ListActivity を利用して結果を出すところまでメモしておく。

/**
 * アプリケーション固有のデータ(WordBook.Word) を検索し、結果表示を行うアクティビティ
 */
public class WordSearchableActivity extends ListActivity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.search_result);

        Intent intent = getIntent();
        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
            String query = intent.getStringExtra(SearchManager.QUERY);
            
            // 検索の実行(SQLiteからデータを取得する)
            WordBookDao dao = new WordBookDao(this);
            List<WordBook.Word> words = dao.selectWordsByQueryForKeyword(query);
            
            WordAdapter adapter = new WordAdapter(this, R.layout.search_result, R.layout.search_result_row, words);
            setListAdapter(adapter);
        }
    }

    /**
     * アプリケーション固有のデータ(WordBook.Word) をListViewに表示するためのアダプタ
     */
    public static class WordAdapter extends ArrayAdapter<WordBook.Word> {
        private List<WordBook.Word> items;
        
        public WordAdapter(Context context, int resource,
                int textViewResourceId, List<Word> items) {
            
            super(context, resource, textViewResourceId, items);
            this.items = items;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            if (view == null) {
                LayoutInflater lif = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = lif.inflate(R.layout.search_result_row, null);
            }
            
            TextView txtKeyword = (TextView) view.findViewById(R.id.txt_search_keyword);
            TextView txtContent = (TextView) view.findViewById(R.id.txt_search_content);
            
            WordBook.Word word = items.get(position);
            txtKeyword.setText(word.getKeyword());
            txtContent.setText(word.getContent());
           return view;
        }
    }
}

ListActivity 用のレイアウト

/layout/search_result.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/search_layout"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <ListView android:id="@+id/android:list" 
              android:layout_height="wrap_content" 
              android:layout_width="fill_parent" android:scrollingCache="false" android:dividerHeight="1px" android:layout_gravity="top|left" android:layout_weight="90">
              
    </ListView>
    <TextView android:id="@+id/android:empty" 
              android:layout_width="wrap_content" 
              android:layout_height="wrap_content"
              android:textSize="20sp" android:text="@string/msg_no_search_result">
    </TextView>
</LinearLayout>

ListActivity 行用のレイアウト

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:orientation="vertical">

    <TextView 
        android:id="@+id/txt_search_keyword"
        android:text="" 
        android:layout_height="wrap_content" 
        android:layout_width="wrap_content" 
        android:layout_gravity="top|left" 
        android:textSize="20sp" 
        android:paddingLeft="6sp"/>
    <TextView 
        android:id="@+id/txt_search_content" 
        android:text=""
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_gravity="bottom|left" 
        android:paddingRight="10sp" 
        android:textSize="14sp"/>
</LinearLayout>    

注意点など

onSearchRequested()

onSearchRequested() を呼び出すことで、ハードウェアの検索ボタンからではなく、UIから、検索ダイアログを呼び出すことができる。(検索可能なアクティビティのメソッドを呼び出すのではなく、検索ダイアログを呼び出したい

アクティビティのonSearchRequestede を呼び出す)

ハードウェアの検索ボタンが存在することを仮定せずに、UIとしてボタンを提供すべき。

ライフサイクル

検索ダイアログは、単にダイアログなので、アクティビティのライフサイクルメソッド(onPause()など)は呼び出さない。

通知

検索ダイアログがアクティブになった時に処理を行い対場合、onSearchRequested() をオーバーライドする。

検索ダイアログが閉じられた場合、setOnDismissListener()setOnCancelListener() を利用して、通知を得るようにできる

 

実行結果

検索ボタンで、画面上部にアプリアイコン付きで検索条件を入力するダイアログがあらわれて・・・検索を実行すると、結果がリスト表示された!

android_search02 android_search03

 

めでたしめでたし。また一歩野望に近づいた。