You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose的TopAppBar中实现带菜单图标自定义搜索栏

需求:实现TopAppBar内带菜单图标的自定义搜索栏

我已经实现了一款独立的搜索栏,现在想要在TopAppBar中实现另一款带菜单图标的嵌入式自定义搜索栏(样式为TopAppBar内包含搜索输入框、左侧返回按钮、右侧菜单与清除按钮),不需要搜索建议,但自己无法完成,以下是现有代码,请求帮助:


现有代码

@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 14:57:01