androidandroid-jetpack-composeappbar

Jetpack compose AppBar not collapsing searchview below


I have a requirement to collapse both TopAppbar and Search view and the tab layout should be sticky there. I tried different ways but Searchview is not collapsing, it is pinned below MediumTopApp bar.

[enter image description here](https://i.sstatic.net/rUq5nDLk.png)

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun MyCollapsingLayout() {
    // Define state for tabs
    val tabs = listOf("Tab 1", "Tab 2", "Tab 3")
    var selectedTabIndex by remember { mutableIntStateOf(0) }
    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

    // LazyColumn state to detect scrolling
    val listState = rememberLazyListState()

    Scaffold(
        topBar = {
            // Wrapping both the MediumTopAppBar and the SearchBar in a single Column
            Column(
                modifier = Modifier
                    .nestedScroll(scrollBehavior.nestedScrollConnection) // Attach scroll behavior
                    .fillMaxWidth()
                    .zIndex(1f)
            ) {
                // MediumTopAppBar with collapsing behavior
                MediumTopAppBar(
                    title = { Text("Medium AppBar") },
                    scrollBehavior = scrollBehavior
                )

                // Search bar is part of the collapsible area
                SearchBar()
            }
        },
        content = { padding ->
            LazyColumn(
                state = listState, // Attach LazyColumn to listen for scrolling
                modifier = Modifier
                    .fillMaxSize()
                    .padding(padding)
            ) {
                // Sticky header for TabRow, it will stay at the top when scrolled
                stickyHeader {
                    TabRow(
                        selectedTabIndex = selectedTabIndex,
                        modifier = Modifier
                            .fillMaxWidth()
                            .zIndex(1f) // Ensures the tab stays above scrolling content
                    ) {
                        tabs.forEachIndexed { index, tab ->
                            Tab(
                                selected = selectedTabIndex == index,
                                onClick = { selectedTabIndex = index },
                                text = { Text(tab) }
                            )
                        }
                    }
                }

                // The rest of the scrollable content
                items(50) { index ->
                    ListItem(text = "Item $index")
                }
            }
        }
    )
}

@Composable
fun SearchBar() {
    // Search Bar composable that will scroll away with the MediumTopAppBar
    TextField(
        value = "",
        onValueChange = {},
        placeholder = { Text("Search...") },
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
    )
}

@Composable
fun ListItem(text: String) {
    Text(
        text = text,
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    )
}

I have the requirement to collapse both TopAppBar and SearchView

Now TopAppBr Collapsed but searchview pinned there


Solution

  • The scrollBehaviour exposes a state variable scrollBehavior.state.collapsedFraction. It denotes to what percentage the TopAppBar is collapsed. You can try to use it to animate the height of the TextField.

    First, allow passing in a Modifier into the SearchBar Composable:

    @Composable
    fun SearchBar(modifier: Modifier) {
        TextField(
            value = "",
            onValueChange = {},
            placeholder = { Text("Search...") },
            modifier = modifier  // apply the passed in Modifier here
                .fillMaxWidth()
                .padding(8.dp)
        )
    }
    

    Then, call it as follows:

    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            Column(
                modifier = Modifier.fillMaxWidth()
            ) {
                MediumTopAppBar(
                    title = { Text("Medium AppBar") },
                    scrollBehavior = scrollBehavior
                )
                SearchBar(
                    modifier = Modifier
                        .height((TextFieldDefaults.MinHeight + 16.dp) * (1 - scrollBehavior.state.collapsedFraction))
                        .clipToBounds()
                        .alpha(1 - scrollBehavior.state.collapsedFraction)
                )
            }
        },
        content = { padding ->
            //...
        }
    )
    

    Also note that I needed to apply the nestedScroll Modifier on the Scaffold to get it to work.

    Output:

    Screen Recording

    Note:

    The height from the TextField is coming from 56.dp height plus 8.dp padding at the top and bottom according to the Material Design Guidelines.