如何在Jetpack Compose的TopAppBar中实现带菜单图标自定义搜索栏
需求:实现TopAppBar内带菜单图标的自定义搜索栏
我已经实现了一款独立的搜索栏,现在想要在TopAppBar中实现另一款带菜单图标的嵌入式自定义搜索栏(样式为TopAppBar内包含搜索输入框、左侧返回按钮、右侧菜单与清除按钮),不需要搜索建议,但自己无法完成,以下是现有代码,请求帮助:
现有代码
MenuSearchBar.kt
@Composable fun CustomSearchView( modifier: Modifier = Modifier, text: String, onTextChange: (String) -> Unit, onCloseClick: () -> Unit, onSearchClick: (String) -> Unit ) { Box( modifier = modifier .padding(20.dp) .clip(CircleShape) .background(Color(0XFF101921)) ) { TextField(value = text, onValueChange = onTextChange, colors = TextFieldDefaults.colors( //containerColor = Color.Transparent, cursorColor = MaterialTheme.colorScheme.onSurface, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent, ), placeholder = { Text("Search") }, singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), keyboardActions = KeyboardActions(onSearch = { onSearchClick(text) }), leadingIcon = { Icon( Icons.Filled.Search, contentDescription = stringResource(id = R.string.content_description_search) ) }, trailingIcon = { if (text.isNotEmpty()) { Icon( modifier = Modifier.clickable { if (text.isNotEmpty()) { onTextChange("") } else { onCloseClick() } }, imageVector = Icons.Filled.Close, contentDescription = stringResource(id = R.string.content_description_close) ) } } ) } }
HomeScreenView.kt
var mShowSearch by remember { mutableStateOf(false) } var mTextSearch by remember { mutableStateOf(mHomeViewModel.searchQuery.value) } if (mShowSearch) { MenuSearchBar(text = mTextSearch, onTextChange = { mTextSearch = it }) { mShowSearch = false } } @Composable fun TopAppBarWithNavigationBar() { ModalNavigationDrawer( drawerContent = { ModalDrawerSheet { NavShape(mCredit, mDebit) Spacer(Modifier.height(12.dp)) mNavItemsList.forEachIndexed { index, item -> //Spacer(Modifier.height(6.dp)) NavigationDrawerItem( label = { Text(text = item.label) }, selected = index == mSelectedItems, onClick = { //navController.navigate(item.route) mSelectedItems = index mCoroutineScope.launch { mDrawerState.close() } Toast.makeText(mContext, item.label, Toast.LENGTH_SHORT).show() }, icon = { Icon( imageVector = if (index == mSelectedItems) { item.selectedIcon } else item.unselectedIcon, contentDescription = item.contentDescription ) }, badge = { item.badgeCount?.let { Text(text = item.badgeCount.toString()) } }, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) ) } } }, drawerState = mDrawerState ){ Scaffold(modifier = Modifier.fillMaxSize(), topBar = { TopAppBar(title = { Text( text = stringResource(R.string.app_name), maxLines = 1, overflow = TextOverflow.Ellipsis ) }, navigationIcon = { IconButton(onClick = { mCoroutineScope.launch { mDrawerState.open() } }) { Icon( imageVector = Icons.Filled.Menu, contentDescription = stringResource(id = R.string.content_description_menu) ) } }, actions = { IconButton(onClick = { mShowSearch = true }) { Icon( imageVector = Icons.Filled.Search, contentDescription = stringResource(id = R.string.content_description_search) ) } val mAngle: Float by animateFloatAsState( targetValue = if (mShowDropdownMenu) 180f else 0f, label = stringResource(id = R.string.label_animate_sort_icon) ) IconButton(onClick = { mShowDropdownMenu = !mShowDropdownMenu }) { Icon( modifier = Modifier.rotate(mAngle), imageVector = Icons.Filled.ArrowDropDown, contentDescription = stringResource(id = R.string.content_description_sort) ) } DropdownMenu(expanded = mShowDropdownMenu, onDismissRequest = { mShowDropdownMenu = false }) { DropdownMenuItem(text = { Text(text = stringResource(R.string.label_sort_by_name)) }, onClick = { mHomeViewModel.updateSortOrder(SortOrder.BY_NAME) mShowDropdownMenu = false }, leadingIcon = { Icon( imageVector = Icons.Filled.SortByAlpha, contentDescription = stringResource(id = R.string.content_description_sort_by_name) ) }) DropdownMenuItem(text = { Text(text = stringResource(R.string.label_sort_by_date_created)) }, onClick = { mHomeViewModel.updateSortOrder(SortOrder.BY_DATE) mShowDropdownMenu = false }, leadingIcon = { Icon( imageVector = Icons.Filled.ChangeCircle, contentDescription = stringResource(id = R.string.content_description_sort_by_date) ) }) } }, scrollBehavior = mScrollBehavior ) }) }
修改方案
核心思路是通过状态切换TopAppBar的显示内容:点击搜索图标后,将原标题、菜单按钮替换为搜索栏,同时保留菜单图标和返回/清除按钮。
1. 调整状态变量
在HomeScreenView.kt中替换原有搜索状态变量,新增控制TopAppBar内搜索栏显示的状态:
var mShowSearchInTopBar by remember { mutableStateOf(false) } var mTextSearch by remember { mutableStateOf(mHomeViewModel.searchQuery.value) }
2. 修改TopAppBar内容逻辑
修改TopAppBar的title、navigationIcon和actions区域,根据状态切换显示内容:
TopAppBar( title = { if (mShowSearchInTopBar) { // 嵌入TopAppBar的搜索栏 CustomSearchView( modifier = Modifier.fillMaxWidth(), text = mTextSearch, onTextChange = { mTextSearch = it }, onCloseClick = { mShowSearchInTopBar = false mTextSearch = "" }, onSearchClick = { query -> mHomeViewModel.updateSearchQuery(query) mShowSearchInTopBar = false } ) } else { Text( text = stringResource(R.string.app_name), maxLines = 1, overflow = TextOverflow.Ellipsis ) } }, navigationIcon = { if (mShowSearchInTopBar) { // 搜索状态下显示返回按钮 IconButton(onClick = { mShowSearchInTopBar = false mTextSearch = "" }) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = stringResource(id = R.string.content_description_back) ) } } else { // 正常状态下显示菜单按钮 IconButton(onClick = { mCoroutineScope.launch { mDrawerState.open() } }) { Icon( imageVector = Icons.Filled.Menu, contentDescription = stringResource(id = R.string.content_description_menu) ) } } }, actions = { if (!mShowSearchInTopBar) { // 非搜索状态下显示搜索图标和排序按钮 IconButton(onClick = { mShowSearchInTopBar = true }) { Icon( imageVector = Icons.Filled.Search, contentDescription = stringResource(id = R.string.content_description_search) ) } val mAngle: Float by animateFloatAsState( targetValue = if (mShowDropdownMenu) 180f else 0f, label = stringResource(id = R.string.label_animate_sort_icon) ) IconButton(onClick = { mShowDropdownMenu = !mShowDropdownMenu }) { Icon( modifier = Modifier.rotate(mAngle), imageVector = Icons.Filled.ArrowDropDown, contentDescription = stringResource(id = R.string.content_description_sort) ) } DropdownMenu(expanded = mShowDropdownMenu, onDismissRequest = { mShowDropdownMenu = false }) { DropdownMenuItem(text = { Text(text = stringResource(R.string.label_sort_by_name)) }, onClick = { mHomeViewModel.updateSortOrder(SortOrder.BY_NAME) mShowDropdownMenu = false }, leadingIcon = { Icon( imageVector = Icons.Filled.SortByAlpha, contentDescription = stringResource(id = R.string.content_description_sort_by_name) ) }) DropdownMenuItem(text = { Text(text = stringResource(R.string.label_sort_by_date_created)) }, onClick = { mHomeViewModel.updateSortOrder(SortOrder.BY_DATE) mShowDropdownMenu = false }, leadingIcon = { Icon( imageVector = Icons.Filled.ChangeCircle, contentDescription = stringResource(id = R.string.content_description_sort_by_date) ) }) } } }, scrollBehavior = mScrollBehavior )
3. 适配CustomSearchView到TopAppBar
修改MenuSearchBar.kt中的搜索栏,调整样式并添加菜单图标:
@Composable fun CustomSearchView( modifier: Modifier = Modifier, text: String, onTextChange: (String) -> Unit, onCloseClick: () -> Unit, onSearchClick: (String) -> Unit, drawerState: DrawerState, coroutineScope: CoroutineScope ) { Box( modifier = modifier .clip(CircleShape) .background(Color(0XFF101921)) .padding(horizontal = 8.dp) ) { TextField(value = text, onValueChange = onTextChange, colors = TextFieldDefaults.colors( containerColor = Color.Transparent, cursorColor = MaterialTheme.colorScheme.onSurface, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent, ), placeholder = { Text("Search") }, singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), keyboardActions = KeyboardActions(onSearch = { onSearchClick(text) }), leadingIcon = { Icon( Icons.Filled.Search, contentDescription = stringResource(id = R.string.content_description_search), modifier = Modifier.size(20.dp) ) }, trailingIcon = { Row { // 添加菜单图标按钮 IconButton(onClick = { coroutineScope.launch { drawerState.open() } }) { Icon( imageVector = Icons.Filled.Menu, contentDescription = stringResource(id = R.string.content_description_menu), modifier = Modifier.size(20.dp) ) } if (text.isNotEmpty()) { IconButton(onClick = { if (text.isNotEmpty()) { onTextChange("") } else { onCloseClick() } }) { Icon( imageVector = Icons.Filled.Close, contentDescription = stringResource(id = R.string.content_description_close), modifier = Modifier.size(20.dp) ) } } } } ) } }
注意:调用CustomSearchView时需要传入drawerState和coroutineScope参数。
4. 移除旧搜索栏逻辑
删除HomeScreenView.kt中原有的独立搜索栏显示代码:
// 移除以下代码块 if (mShowSearch) { MenuSearchBar(text = mTextSearch, onTextChange = { mTextSearch = it }) { mShowSearch = false } }
内容的提问来源于stack exchange,提问作者Sajjad Hussain Bhutta
相关产品推荐
相关产品推荐

