Search code examples
androidmaterial-uiandroid-jetpack-composeandroid-jetpackmaterial-components-android

Jetpack Compose TopAppBar with search bar embedded


I'm trying to implement something that I feel should be easy but I'm having a hard time.

I have a screen with a scaffold, topbar, and lazycolumn as content:

App using scaffold with M3 TopAppBar and LazyColumn as content

I'm using a M3 TopAppBar with a enterAlwaysScrollBehavior so it disappears/appears when the content is scrolled.

What I want to do is implement the search button. How can I make that once the search icon is tapped, the "Cards" title turns into a textfield to input a search query?

I've done this easily using a Row and Crossfade instead of TopAppBar, but then I lose the scrollBehavior feature.

Any ideas?

Thanks, and happy new year!


Solution

  • Did the following:

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    internal fun SearchAppBar() {
        val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
    
        Scaffold(
            modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
            topBar = {
                var isSearch by remember { mutableStateOf(false) }
                var value by remember { mutableStateOf("") }
    
                Crossfade(
                    modifier = Modifier.animateContentSize(),
                    targetState = isSearch,
                    label = "Search"
                ) { target ->
                    if (!target) {
                        TopAppBar(
                            title = { Text("Cards") },
                            actions = { IconButton(Icons.Filled.Search) { isSearch = !isSearch } },
                            scrollBehavior = scrollBehavior,
                        )
                    } else {
                        TextField(
                            modifier = Modifier
                                .fillMaxWidth()
                                .windowInsetsPadding(TopAppBarDefaults.windowInsets)
                                .layout { measurable, constraints ->
                                    val placeable = measurable.measure(constraints)
                                    val height = placeable.height * (1 - scrollBehavior.state.collapsedFraction)
                                    layout(placeable.width, height.roundToInt()) {
                                        placeable.place(0, 0)
                                    }
                                },
                            value = value,
                            placeholder = { Text("Enter card name") },
                            onValueChange = { value = it },
                            leadingIcon = {
                                IconButton(Icons.AutoMirrored.Filled.ArrowBack) {
                                    isSearch = !isSearch
                                }
                            },
                            trailingIcon = if (value.isNotBlank()) {
                                { IconButton(Icons.Filled.Close) { value = "" } }
                             } else {
                                null
                            }
                        )
                    }
                }
            },
        ) { paddingValues ->
            LazyColumn(
                Modifier
                    .fillMaxSize()
                    .padding(paddingValues)
            ) {
                items(200) { index ->
                    Text(modifier = Modifier.fillMaxWidth(), text = "Title $index")
                }
            }
        }
    }
    
    @Composable
    fun IconButton(imageVector: ImageVector, onClick: () -> Unit) {
        IconButton(onClick = onClick) {
            Icon(
                imageVector = imageVector,
                contentDescription = null
            )
        }
    }