搜索栏

使用搜索栏实现搜索功能。搜索栏是一个持久的搜索字段,允许用户输入关键字或短语,以在应用内显示相关结果,当搜索是应用的主要焦点时,建议使用搜索栏。

Two search bars are shown. The one on the left only has a text field.
  The search bar on the left has a text field and a search suggestion beneath it.
图 1. 基本搜索栏 (1) 和带建议的搜索栏 (2)。

API 外观

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

结果

A search bar is shown with the letter 'a' typed inside of the bar. A list containing six search suggestions is displayed below the search bar.
图 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

结果

A search bar containing the words hinted text search inside is shown. Below the search bar, a list of search suggestions is displayed, with a star icon beside each suggestion.
图 3. 显示相关建议的搜索栏。

其他资源