前人未踏の領域へ Androidアプリ開発編

Androidアプリ開発に関する調査メモ置き場。古い記事にはアプリ以外も含まれます。

SearchViewのカスタマイズ

課題

ActionBarで検索窓を設置したい。ただしデフォルトとはデザインが異なるため一部カスタマイズが必要である

対応

Googleからは検索用のWidgetとしてSearchViewが提供されている。
こちらをベースに必要な変更を行う

表示する

とりあえず表示できるようにする。app:actionViewClassを使ってSearchViewを明示するのと、Proguardを使っている場合には
難読化されないように設定が必要なので注意。

search_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_search"
        android:icon="@drawable/ic_search"
        android:title="@string/label_search_title"
        app:actionViewClass="android.support.v7.widget.SearchView"/>
</menu>

SearchFragment

override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
    inflater?.inflate(R.menu.search_menu, menu)
}

proguard-rules.pro

## SearchView
-keep class android.support.v7.widget.SearchView { *; }
常に開いた状態にする

なお、menu.xmlに色々書いても反映されなかったのでここからはコードで編集する。
findItemの引数はファイル名(ここではsearch_menu.xml)ではなくSearchViewのID(android:id="@+id/menu_search")の方なので注意。

val actionView = menu?.findItem(R.id.menu_search)?.actionView
        actionView?.let {
            it as SearchView
            setupSearchView(it)
        }

デフォルトオープンにはsetIconifiedByDefaultにfalseをセットすればよい。

searchView.setIconifiedByDefault(false)
入力イベントの処理

setOnQueryTextListenerを使う

searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextChange(newText: String?): Boolean {
               //TODO テキストが変更された
            }

            override fun onQueryTextSubmit(query: String?): Boolean {
                //TODO  検索ボタンが押下された
            }
        })
アイコンの差し替え
val mCollapsedIcon = view.findViewById<ImageView>(android.support.v7.appcompat.R.id.search_mag_icon)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    mCollapsedIcon.setImageDrawable(resources.getDrawable(R.drawable.ic_search, null))
}else {
    mCollapsedIcon.setImageDrawable(resources.getDrawable(R.drawable.ic_search))
}

非表示にしたい場合はnullをセットする

余白を削る

左側にマージンがあって気になる場合、search_edit_frameとsearch_mag_iconの余白を削る。それでも16dpほどどこかにあるが現時点では消し方不明。

        //setup searchEditFrame
        searchEditFrame = view.findViewById(android.support.v7.appcompat.R.id.search_edit_frame)
        val frameLp = searchEditFrame?.layoutParams as LinearLayout.LayoutParams?
        frameLp?.let {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                frameLp.marginStart = 0
                frameLp.leftMargin = 0
            } else {
                frameLp.leftMargin = 0
            }
        }

        //setup collapsedIcon
        collapsedIcon = view.findViewById(android.support.v7.appcompat.R.id.search_mag_icon)
        val iconLp = collapsedIcon?.layoutParams as LinearLayout.LayoutParams?
        iconLp?.let {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                iconLp.marginStart = 0
                iconLp.leftMargin = 0
            } else {
                iconLp.leftMargin = 0
            }
        }

トラブルシューティング

setOnCloseListenerが呼ばれない

IconifiedByDefaultがfalseになっているとSearchViewはクローズされないのでsetOnCloseListenerは発生しない(SearchView.onCloseClickedを参照)。
setOnQueryTextListenerで代用