可滑动隐藏的 TopAppBar —— Compose

效果:

动画.gif
在原生中非常简单,直接使用 CoordinatorLayout + AppBarLayout 在设置 behavior 就行。

不过在 Compose 中却没有直接实现,这里只能自己实现了。

ScrollableTabRow 和 HorizontalPager

首先先把 Tab 和 Pager 的 可组合函数 封装好:

@OptIn(ExperimentalPagerApi::class)
@Composable
fun HomeTab(
    modifier: Modifier = Modifier,
    pagerState: PagerState,
    count: Int,
    label: @Composable (Int, Boolean)->Unit,
){
    ScrollableTabRow(
        edgePadding = 18.dp,
        backgroundColor = MaterialTheme.colors.primary,
        divider = {},
        modifier = modifier,
        indicator = {
            TabRowDefaults.Indicator(
                color = MaterialTheme.colors.secondary,
                modifier = Modifier.pagerTabIndicatorOffset(pagerState, it))

        },
        selectedTabIndex = pagerState.currentPage) {
        repeat(count){
            label(it, pagerState.currentPage == it)
        }
    }
}


@OptIn(ExperimentalPagerApi::class)
@Composable
fun HomePagerLayout(
    modifier: Modifier = Modifier,
    pagerState: PagerState,
    count: Int,
    pager: @Composable PagerScope.(Int) -> Unit
){
    HorizontalPager(
        count = count,
        state = pagerState,
        modifier = modifier,
        content = pager
    )
}

BehaviorToolbarScaffold

然后是我封装好的 Scaffold

/**
 * Created by HeYanLe on 2022/2/4 19:38.
 * https://github.com/heyanLE
 */

/**
 * 支持滑动隐藏 Toolbar 的 Scaffold
 * @param toolbarSize Toolbar 高度
 * @param toolbar  Toolbar 可组合函数
 * @param content 主体,需要联动滑动的需要指定 NestedScrollConnection
 */
@Composable
fun BehaviorToolbarScaffold (
    toolbarSize: Dp = 56.dp,
    toolbar: @Composable ()->Unit,
    content: @Composable (NestedScrollConnection)-> Unit
){
    // TopAppbar 的 offset
    val offset = remember {
        mutableStateOf(0f)
    }
    val nestedScrollConn = object: NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            // 滑动计算,消费让 TopAppbar 滑动的对应 Offset,同时让 TopAppbar 滑动
            var off = (offset.value) + available.y
            var con = available.y
            if(off >= 0){
                off = 0f
                con = 0-(offset.value)
            }
            if(off <= -(toolbarSize.toPx())){
                off = -(toolbarSize.toPx())
                con = -(toolbarSize.toPx()) - (offset.value)
            }
            offset.value = off
            return Offset(0f, con)
        }
    }
    // 为了避免遮挡,这里需要分两层
    Box() {
        // 主体层使用一个 Spacer 占位
        Column {
            Spacer(modifier = Modifier.height(((toolbarSize.toPx()) - (-offset.value)).toDp()))
            content(nestedScrollConn)
        }
        // TopAppbar 层随着 offset 移动
        Box(modifier = Modifier
            .fillMaxWidth()
            .height(toolbarSize)
            .graphicsLayer { translationY = (offset.value) }){
            toolbar()
        }
    }
}

组合

然后就是把之前的组合一起:

需要注意的是 需要在 需要联动滑动的 可组合函数的 Modifier 中将 NestedScrollConnection 传入

@OptIn(ExperimentalPagerApi::class)
@Composable
fun TestPreview(){
    val pagerState = rememberPagerState()
    val scope = rememberCoroutineScope()

    Column() {
        BehaviorToolbarScaffold(
            toolbar = {
                TopAppBar(
                    elevation = 0.dp,
                    backgroundColor = MaterialTheme.colors.primary,
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(56.dp)

                    ,
                    title = {
                        Text(text = stringResource(id = R.string.app_name))
                    }
                )
            }
        ){ conn ->
            HomeTab(
                modifier = Modifier.height(56.dp),
                pagerState = pagerState, count = 2) { i,b ->
                Tab(
                    unselectedContentColor = MaterialTheme.colors.onPrimary,
                    selectedContentColor = MaterialTheme.colors.secondary,
                    modifier = Modifier.fillMaxHeight(),
                    selected = b,
                    onClick = {
                        scope.launch {
                            pagerState.animateScrollToPage(i)
                        }
                    }) {
                    Text(text = "test")
                }

            }

            HomePagerLayout(
                modifier = Modifier
                    .fillMaxSize(),
                pagerState = pagerState,
                count = 2) {
                Box(modifier = Modifier
                    .fillMaxSize()
                    .background(MaterialTheme.colors.background)){
                    when(it){
                        0 -> TestPage(conn)
                        1 -> TestPage(conn)
                    }
                }

            }
        }
    }
}

LazyColmn

然后是页面:

@Composable
fun TestPage(
    nestedScrollConnection: NestedScrollConnection
){
    LazyColumn(
        modifier = Modifier
            .fillMaxWidth()
        	// 需要将 nestedScrollConnection 传入要联动滑动的 可组合函数的 Modifier 中
            .nestedScroll(nestedScrollConnection)
    ){
        items(50){
            Text(text = it.toString())
        }
    }
}