这一节主要了解一下Compose中的SearchBar,在Jetpack Compose中,SearchBar是Material 3组件库提供的一种搜索组件,它结合了文本输入框和下拉结果列表的功能,提供了良好的搜索体验,简单总结如下:
API
query:当前搜索查询文本
onQueryChange:查询文本变化时的回调
onSearch:用户提交搜索时的回调
active:搜索栏是否处于活动状态(展开状态)
onActiveChange:搜索栏活动状态变化时的回调
placeholder:搜索框为空时显示的占位文本
leadingIcon:搜索框前面显示的图标
trailingIcon:搜索框后面显示的图标
shape:搜索框的形状配置
colors:搜索框的颜色配置
content:搜索栏处于活动状态时显示的内容
场景:
1 内容检索,在列表、网格或复杂内容中快速查找特定项
2 动态过滤,实时根据输入内容过滤列表数据
3 搜索历史与建议,显示用户历史搜索记录或智能推荐关键词。
栗子:
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
// 搜索结果数据类
data class SearchResult(
val id: String,
val title: String,
val subtitle: String,
val imageResId: Int = android.R.drawable.ic_menu_gallery
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TestSearchBarExample() {
val scope = rememberCoroutineScope()
var query by remember { mutableStateOf("") }
var active by remember { mutableStateOf(false) }
var isLoading by remember { mutableStateOf(false) }
val allResults = remember {
mutableListOf().apply {
for (i in 1..50) {
add(SearchResult(
id = "result_$i",
title = "搜索结果 $i",
subtitle = "这是关于搜索结果 $i 的详细描述"
))
}
}
}
var filteredResults by remember { mutableStateOf(emptyList()) }
val recentSearches = remember {
mutableStateListOf("Android开发", "Jetpack Compose", "Kotlin语言")
}
LaunchedEffect(query) {
if (query.length >= 2) {
isLoading = true
delay(500)
filteredResults = allResults.filter {
it.title.contains(query, ignoreCase = true) ||
it.subtitle.contains(query, ignoreCase = true)
}
isLoading = false
} else if (query.isEmpty()) {
filteredResults = emptyList()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("搜索示例") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
)
}
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
) {
SearchBar(
query = query,
onQueryChange = { query = it },
onSearch = {
active = false
if (query.isNotEmpty() && !recentSearches.contains(query)) {
recentSearches.add(0, query)
if (recentSearches.size > 5) {
recentSearches.removeAt(recentSearches.size - 1)
}
}
},
active = active,
onActiveChange = { active = it },
placeholder = { Text("搜索...") },
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "搜索"
)
},
trailingIcon = {
if (query.isNotEmpty()) {
IconButton(onClick = { query = "" }) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "清除"
)
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
if (query.isEmpty()) {
if (recentSearches.isNotEmpty()) {
Text(
text = "最近搜索",
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(16.dp)
)
LazyColumn {
items(recentSearches) { search ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable {
query = search
active = false
}
.padding(16.dp)
) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "历史记录",
modifier = Modifier.padding(end = 16.dp)
)
Text(search)
}
}
}
}
} else if (isLoading) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
} else if (filteredResults.isEmpty()) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "无结果",
tint = Color.Gray,
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Text("没有找到与 \"$query\" 相关的结果")
}
}
} else {
LazyColumn {
items(filteredResults) { result ->
SearchResultItem(
result = result,
onClick = {
active = false
}
)
}
}
}
}
if (!active) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "搜索",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(96.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "开始搜索",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "在上方搜索栏输入关键词查找内容",
color = Color.Gray
)
}
}
}
}
}
}
@Composable
fun SearchResultItem(result: SearchResult, onClick: () -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(16.dp)
) {
Icon(
painter = painterResource(id = result.imageResId),
contentDescription = result.title,
modifier = Modifier
.size(40.dp)
.padding(end = 16.dp)
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = result.title,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = result.subtitle,
fontSize = 12.sp,
color = Color.Gray
)
}
}
}
注意:
1 状态管理与交互处理,SearchBar的query和active状态需要分别管理
2 搜索防抖处理,避免用户每输入一个字符就触发搜索,可以使用LaunchedEffect和delay实现防抖
3 结果列表性能优化,使用LazyColumn而非Column展示大量结果,避免性能问题