検索バー

検索バーを使用して検索機能を実装します。検索バーは永続的な検索フィールドで、ユーザーがキーワードやフレーズを入力してアプリ内の関連する結果を表示できます。検索がアプリの主な機能である場合に推奨されます。

2 つの検索バーが表示されます。左側のフォームにはテキスト フィールドのみがあります。右側の検索バーには、テキスト フィールドとその下に検索候補が表示されています。
図 1.基本的な検索バー(1)と候補が表示される検索バー(2)。

API サーフェス

SearchBar コンポーザブルを使用して検索バーを実装します。このコンポーザブルの主なパラメータは次のとおりです。

  • inputField: 検索バーの入力フィールドを定義します。通常は SearchBarDefaults.InputField を使用します。これにより、次の項目をカスタマイズできます。
    • query: 入力フィールドに表示されるクエリテキスト。
    • onQueryChange: クエリ文字列の変更を処理するラムダ。
  • expanded: 検索バーが展開されて候補やフィルタされた結果が表示されるかどうかを示すブール値。
  • onExpandedChange: プルダウンの展開状態の変更を処理するラムダ。

  • content: この検索バーのコンテンツ。検索結果を inputField の下に表示します。

次のスニペットは、候補が表示される SearchBar の基本的な実装を示しています。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleSearchBar(
    textFieldState: TextFieldState,
    onSearch: (String) -> Unit,
    searchResults: List<String>,
    modifier: Modifier = Modifier
) {
    // Controls expansion state of the search bar
    var expanded by rememberSaveable { mutableStateOf(false) }

    Box(
        modifier
            .fillMaxSize()
            .semantics { isTraversalGroup = true }
    ) {
        SearchBar(
            modifier = Modifier
                .align(Alignment.TopCenter)
                .semantics { traversalIndex = 0f },
            inputField = {
                SearchBarDefaults.InputField(
                    query = textFieldState.text.toString(),
                    onQueryChange = { textFieldState.edit { replace(0, length, it) } },
                    onSearch = {
                        onSearch(textFieldState.text.toString())
                        expanded = false
                    },
                    expanded = expanded,
                    onExpandedChange = { expanded = it },
                    placeholder = { Text("Search") }
                )
            },
            expanded = expanded,
            onExpandedChange = { expanded = it },
        ) {
            // Display search results in a scrollable column
            Column(Modifier.verticalScroll(rememberScrollState())) {
                searchResults.forEach { result ->
                    ListItem(
                        headlineContent = { Text(result) },
                        modifier = Modifier
                            .clickable {
                                textFieldState.edit { replace(0, length, result) }
                                expanded = false
                            }
                            .fillMaxWidth()
                    )
                }
            }
        }
    }
}

コードに関する主なポイント

  • rememberSaveable を使用すると、検索バーが展開されているか折りたたまれているかが、構成の変更後も保持されます。構成の変更中にアクティビティが破棄される前に、保存された値がホスト アクティビティの savedInstanceState バンドルに書き込まれます。
  • semantics 修飾子は、TalkBack のトラバーサル順序を制御します。
    • isTraversalGroupBox に設定され、すべての子コンポーザブルをグループ化します。
    • traversalIndex は、TalkBack が各グループ ピアからユーザー補助情報を読み取る順序を指定するように設定されます。TalkBack は、1 などの正の値を持つピア よりも先に、-1 などの負の値を持つピアのユーザー補助情報 を読み取ります。値は浮動小数点数であるため、各ピアの -1.01.0 の間の値を設定することで、多くのピアのカスタム順序を指定できます。
  • SearchBar には、ユーザー入力用の inputField と、検索候補を表示する Column が含まれています。
    • SearchBarDefaults.InputField は入力フィールドを作成し、ユーザー クエリの変更を処理します。
    • onQueryChange はテキスト入力を処理し、入力フィールドのテキストが変更されるたびに状態を更新します。
    • expanded 状態は、候補リストの表示 / 非表示を制御します。
  • searchResults.forEach { result -> … }searchResults リストを反復処理し、結果ごとに ListItem を作成します。
    • ListItem がクリックされると、textFieldState が更新され、検索バーが折りたたまれ、選択した検索結果が textField に入力されます。

結果

検索バーが表示され、バーの中に「a」という文字が入力されています。検索バーの下に 6 つの検索候補を含むリストが表示されている。
図 2.候補が表示される検索バー。

フィルタされたリストが表示される検索バー

この例では、ユーザーの検索クエリに基づいてリストをフィルタする SearchBar を示します。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomizableSearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    onSearch: (String) -> Unit,
    searchResults: List<String>,
    onResultClick: (String) -> Unit,
    modifier: Modifier = Modifier,
    // Customization options
    placeholder: @Composable () -> Unit = { Text("Search") },
    leadingIcon: @Composable (() -> Unit)? = { Icon(Icons.Default.Search, contentDescription = "Search") },
    trailingIcon: @Composable (() -> Unit)? = null,
    supportingContent: (@Composable (String) -> Unit)? = null,
    leadingContent: (@Composable () -> Unit)? = null,
) {
    // Track expanded state of search bar
    var expanded by rememberSaveable { mutableStateOf(false) }

    Box(
        modifier
            .fillMaxSize()
            .semantics { isTraversalGroup = true }
    ) {
        SearchBar(
            modifier = Modifier
                .align(Alignment.TopCenter)
                .semantics { traversalIndex = 0f },
            inputField = {
                // Customizable input field implementation
                SearchBarDefaults.InputField(
                    query = query,
                    onQueryChange = onQueryChange,
                    onSearch = {
                        onSearch(query)
                        expanded = false
                    },
                    expanded = expanded,
                    onExpandedChange = { expanded = it },
                    placeholder = placeholder,
                    leadingIcon = leadingIcon,
                    trailingIcon = trailingIcon
                )
            },
            expanded = expanded,
            onExpandedChange = { expanded = it },
        ) {
            // Show search results in a lazy column for better performance
            LazyColumn {
                items(count = searchResults.size) { index ->
                    val resultText = searchResults[index]
                    ListItem(
                        headlineContent = { Text(resultText) },
                        supportingContent = supportingContent?.let { { it(resultText) } },
                        leadingContent = leadingContent,
                        colors = ListItemDefaults.colors(containerColor = Color.Transparent),
                        modifier = Modifier
                            .clickable {
                                onResultClick(resultText)
                                expanded = false
                            }
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp, vertical = 4.dp)
                    )
                }
            }
        }
    }
}

コードに関する主なポイント

  • ユーザーが検索バーにテキストを入力または削除するたびに、onQueryChange ラムダ関数が呼び出されます。
  • SearchBarDefaults.InputField には、入力フィールドの先頭に検索アイコンを追加する leadingIcon と、入力フィールドの末尾に [その他のオプション] アイコンを追加する trailingIcon が含まれています。ここで、並べ替えとフィルタのオプションをユーザーに提供できます。
  • onSearch = { … } は、検索が送信されると onSearch ラムダを呼び出し、検索バーを折りたたみます 。
  • LazyColumn は、検索結果が多数になる可能性がある場合に効率的に処理します。searchResults リストを反復処理し、各結果を ListItem として表示します。
  • ListItem コンポーザブルには、アイテムのテキスト、追加情報を表示するテキスト、アイテムの leadingContent として星アイコンが表示されます。この例では、アイテムをお気に入りに追加するオプションが表示されます。
  • フィルタリング ロジックについては、CustomizableSearchBarExample を GitHub の 完全な ソースコードでご覧ください

結果

検索バーが表示され、その中に「テキスト検索」というヒントの単語が表示されています。検索バーの下に検索候補の一覧が表示され、各候補の横にスターアイコンが表示されています。
図 3.関連性の高い候補が表示される検索バー。

参考情報