AppBarに検索ボックスを設置する方法を、ViewとComposeそれぞれで説明する。

View(SearchView)

メニューリソースに SearchView を追加し、onCreateOptionsMenu() で設定する。

メニューリソースを作成する

res/menu/menu_main.xmlSearchView を追加する。 app:showAsAction="ifRoom|collapseActionView" を指定すると、AppBarにスペースがある場合はアイコンとして表示し、タップで展開するビヘイビアになる。

<?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/action_search"
        android:icon="@drawable/ic_search"
        android:title="検索"
        app:actionViewClass="androidx.appcompat.widget.SearchView"
        app:showAsAction="ifRoom|collapseActionView" />

</menu>

onCreateOptionsMenuで設定する

onCreateOptionsMenu() でメニューをinflateし、SearchView.OnQueryTextListener を設定する。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(findViewById(R.id.toolbar))
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)

        val searchItem = menu.findItem(R.id.action_search)
        val searchView = searchItem.actionView as SearchView

        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String?): Boolean {
                query?.let { search(it) }
                searchView.clearFocus()
                return true
            }

            override fun onQueryTextChange(newText: String?): Boolean {
                newText?.let { filterList(it) }
                return true
            }
        })

        return true
    }

    private fun search(query: String) {
        // 検索処理
    }

    private fun filterList(query: String) {
        // リアルタイムフィルタリング処理
    }
}

onQueryTextSubmit() は検索ボタンまたはキーボードのEnterキー押下時に呼ばれる。onQueryTextChange() はテキスト変更のたびに呼ばれるため、インクリメンタルサーチに使用する。

展開状態を初期表示にする

アプリ起動時から検索ボックスを展開した状態にするには、expandActionView() を呼ぶ。

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    menuInflater.inflate(R.menu.menu_main, menu)

    val searchItem = menu.findItem(R.id.action_search)
    searchItem.expandActionView()

    // ...
    return true
}

ヒントテキストを設定する

searchView.queryHint = "キーワードを入力"

Compose(SearchBar)

Material3の SearchBar コンポーネントを使う。SearchBar は全画面表示の検索UIで、DockedSearchBar はコンテンツ内へ埋め込む形式のコンポーネントである。

依存関係を追加する

dependencies {
    implementation("androidx.compose.material3:material3:1.3.1")
}

SearchBarを使う

@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }
    val results = remember(query) { filterItems(query) }

    SearchBar(
        inputField = {
            SearchBarDefaults.InputField(
                query = query,
                onQueryChange = { query = it },
                onSearch = { active = false },
                expanded = active,
                onExpandedChange = { active = it },
                placeholder = { Text("キーワードを入力") },
                leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
                trailingIcon = {
                    if (query.isNotEmpty()) {
                        IconButton(onClick = { query = "" }) {
                            Icon(Icons.Default.Close, contentDescription = "クリア")
                        }
                    }
                }
            )
        },
        expanded = active,
        onExpandedChange = { active = it },
    ) {
        // 検索結果をサジェストとして表示
        results.forEach { item ->
            ListItem(
                headlineContent = { Text(item) },
                modifier = Modifier.clickable {
                    query = item
                    active = false
                }
            )
        }
    }
}

private fun filterItems(query: String): List<String> {
    val items = listOf("Android", "Kotlin", "Jetpack Compose", "Material3")
    return if (query.isEmpty()) emptyList()
    else items.filter { it.contains(query, ignoreCase = true) }
}

DockedSearchBarを使う

DockedSearchBar はAppBar直下など、コンテンツの一部として検索ボックスを配置する場合に適している。展開してもオーバーレイ表示になり、画面全体を占有しない。

@Composable
fun DockedSearchScreen() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }
    val results = remember(query) { filterItems(query) }

    Column {
        DockedSearchBar(
            inputField = {
                SearchBarDefaults.InputField(
                    query = query,
                    onQueryChange = { query = it },
                    onSearch = { active = false },
                    expanded = active,
                    onExpandedChange = { active = it },
                    placeholder = { Text("キーワードを入力") },
                    leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
                )
            },
            expanded = active,
            onExpandedChange = { active = it },
        ) {
            results.forEach { item ->
                ListItem(
                    headlineContent = { Text(item) },
                    modifier = Modifier.clickable {
                        query = item
                        active = false
                    }
                )
            }
        }

        // メインコンテンツ
    }
}

参考