Search code examples
androidandroid-studiokotlinviewandroid-jetpack-compose

Android Compose - How to make TabBar collapse along with TopAppBar


I want to have an appbar in the top of my screen which have two lines:

  1. The top line will contain screen name and navigation icons (TopAppBar)
  2. The second line will contain tabs to navigate to other parts of the application (TabBar)

I wish to implement the above in a scaffold. Following is the code I have written so far.

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun NestedScrollTest() {

    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            TopAppBar(
                title = { Text(text = "Scroll Behavior Test") },
                navigationIcon = {
                    IconButton(onClick = { /*TODO*/ }) {
                        Icon(imageVector = Icons.Default.Menu, contentDescription = "")
                    }
                },
                scrollBehavior = scrollBehavior
            )
        }
    ) {
        MyTabRowNew()
        LazyColumn(
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = it.calculateTopPadding() + 56.dp)) {
            items((1..50).toList()) { item ->
                Text(modifier = Modifier.padding(8.dp), text = "Item $item")
            }
        }
    }
}

@Composable
fun MyTabRowNew(){
    var state by remember { mutableStateOf(0) }
    val titles = listOf("Tab 1", "Tab 2", "Tab 3 with lots of text")
    Column(
        modifier = Modifier.padding(top = 64.dp)
    ) {
        TabRow(selectedTabIndex = state) {
            titles.forEachIndexed { index, title ->
                Tab(
                    selected = state == index,
                    onClick = { state = index },
                    text = { Text(text = title, maxLines = 2, overflow = TextOverflow.Ellipsis) }
                )
            }
        }
    
    }
}

With this code, the TopAppBar collapses, but the TabBar does not and so looks odd How can I collapse TabBar too along with TopAppBar, when I scroll in LazyColumn.

Thanks


Solution

  • You can actually acheive this with a combination of LazyColumnState and AnimateVisibility. first you need to add this Composable function in your project somewhere:

    @Composable
        fun LazyListState.isScrollingUp(): Boolean {
            var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) }
            var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
            return remember(this) {
                derivedStateOf {
                    if (previousIndex != firstVisibleItemIndex) {
                        previousIndex > firstVisibleItemIndex
                    } else {
                        previousScrollOffset >= firstVisibleItemScrollOffset
                    }.also {
                        previousIndex = firstVisibleItemIndex
                        previousScrollOffset = firstVisibleItemScrollOffset
                    }
                }
            }.value
        }
    

    what this does it simply detects if the LazyColumn is being scrolled up . now that you have this its simple you just need to detect if the LazyColumn is being Scrolled up if True show the TopBars if not hide them:

    val lazyColumnState = rememberLazyListState()
                        Scaffold(modifier = Modifier.fillMaxSize(),
                            topBar = {
                                AnimatedVisibility(visible = lazyColumnState.isScrollingUp()) {
                                    TopAppBar(
                                        title = { Text(text = "Scroll Behavior Test") },
                                        navigationIcon = {
                                            IconButton(onClick = { /*TODO*/ }) {
                                                Icon(
                                                    imageVector = Icons.Default.Menu,
                                                    contentDescription = ""
                                                )
                                            }
                                        }
                                    )
                                }
                            }
                        ) { paddingValues ->
                            //second top bar
                            Column(modifier = Modifier
                                .fillMaxSize()
                                .padding(paddingValues)) {
    
    
                                AnimatedVisibility(visible = lazyColumnState.isScrollingUp()) {
                                    Row(
                                        modifier = Modifier
                                            .fillMaxWidth()
                                            .height(40.dp)
                                    ) {
                                        Button(onClick = { /*TODO*/ }, colors = ButtonDefaults.buttonColors(containerColor = Color.Red)) {
                                            Text(text = "5")
                                        }
                                    }
                                }
    
                                LazyColumn(
                                    modifier = Modifier
                                        .fillMaxSize() ,
                                    state = lazyColumnState
                                ) {
                                    items(50) {
                                        Text(
                                            text = it.toString(), modifier = Modifier
                                                .fillMaxWidth()
                                                .height(30.dp)
                                        )
                                    }
                                }
                            }
                        }