搜索栏

使用搜索栏实现搜索功能。搜索栏是一种永久性搜索字段,可让用户输入关键字或字词,以便在应用中显示相关结果。如果搜索是应用的主要功能,则建议使用搜索栏。

系统会显示两个搜索栏。左侧的布局仅包含一个文本字段。
  左侧的搜索栏包含一个文本字段,下方显示搜索建议。
图 1. 基本搜索栏 (1) 和带有建议的搜索栏 (2)。

API Surface

使用 SearchBar 可组合项实现搜索栏。此可组合项的关键参数包括:

  • inputField:定义搜索栏的输入字段。它通常使用 SearchBarDefaults.InputField,可自定义以下内容:
    • query:要显示在输入字段中的查询文本。
    • onQueryChange:用于处理查询字符串更改的 Lambda。
  • expanded:一个布尔值,指示搜索栏是否已展开即可显示建议或过滤后的结果。
  • onExpandedChange:用于处理下拉菜单展开状态变化的 Lambda。

  • 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 可确保搜索栏是展开还是收起的状态在配置更改后保持不变。它会在配置更改期间销毁 activity 之前,将记住的值写入托管 activity 的 savedInstanceState bundle。
  • semantics 修饰符用于控制 TalkBack 遍历顺序。
    • Box 设置 isTraversalGroup 以对其所有子可组合项进行分组。
    • traversalIndex 用于指定 TalkBack 从每个群组对等方读取无障碍信息的顺序。TalkBack 会先读取值为负(例如 -1)的对等项的无障碍信息,然后再读取值为正(例如 1)的对等项的无障碍信息。由于该值是浮点值,因此您可以通过为每个对等点设置介于 -1.01.0 之间的值,指定多个对等点的自定义顺序。
  • SearchBar 包含用于用户输入的 inputField,以及用于显示搜索建议的 Column
    • SearchBarDefaults.InputField 会创建输入字段并处理用户查询的更改。
    • 每当输入字段中的文本发生变化时,onQueryChange 都会处理文本输入并更新状态。
    • The expanded 状态用于控制建议列表的可见性。
  • searchResults.forEach { result -> … } 会遍历 searchResults 列表,并为每个结果创建一个 ListItem
    • 点击 ListItem 后,系统会更新 textFieldState、收起搜索栏,并使用所选搜索结果填充 textField

结果

显示一个搜索栏,其中输入了字母“a”。搜索栏下方会显示一个包含六条搜索建议的列表。
图 2. 显示建议的搜索栏。

带有过滤列表的搜索栏

以下示例展示了一个 SearchBar,用于根据用户的搜索查询过滤列表:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomizableSearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    onSearch: (String) -> Unit,
    searchResults: List<String>,
    onResultClick: (String) -> Unit,
    // 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,
    modifier: Modifier = Modifier
) {
    // 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 lambda 函数。
  • SearchBarDefaults.InputField 包含一个 leadingIcon,用于向输入字段的开头添加搜索图标,以及一个 trailingIcon,用于向输入字段的末尾添加“更多选项”图标。在这里,您可以向用户提供排序和过滤选项。
  • onSearch = { … } 会在提交搜索时调用 onSearch lambda 并收起搜索栏。
  • LazyColumn 可高效处理可能数量众多的搜索结果。它会遍历 searchResults 列表,并将每个结果显示为 ListItem
  • 每个 ListItem 可组合项都会显示商品文本、显示其他信息的文本,以及作为商品 leadingContent 的星形图标。在此示例中,系统会显示用于将内容收藏为“收藏夹”的选项。
  • 如需了解过滤逻辑,请参阅 GitHub 上的完整源代码中的 CustomizableSearchBarExample

结果

系统会显示一个搜索栏,其中包含“搜索文字”的提示字词。搜索栏下方会显示搜索建议列表,每个建议旁边都有一个星形图标。
图 3. 显示相关建议的搜索栏。

其他资源