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

Jetpack Compose如何实现支持手势控制的Prominent Top App Bar

Jetpack Compose 可折叠顶部应用栏手势交互实现问题

我希望使用Jetpack Compose创建类似下方截图的顶部应用栏,并为其添加对应手势交互:
效果示例截图
我已经参考Android官方嵌套滚动相关文档实现了可折叠顶部栏,但无法通过手势控制其展开、折叠以及同步修改布局,以下是我尝试编写的可折叠工具栏代码:

val toolbarHeight = 48.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeight.roundToPx().toFloat() }
// our offset to collapse toolbar
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
// now, let's create connection to the nested scroll system and listen to the scroll
// happening inside child LazyColumn
val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            // try to consume before LazyColumn to collapse toolbar if needed, hence pre-scroll
            val delta = available.y
            val newOffset = toolbarOffsetHeightPx.value + delta
            toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
            // here's the catch: let's pretend we consumed 0 in any case, since we want
            // LazyColumn to scroll anyway for good UX
            // We're basically watching scroll without taking it
            return Offset.Zero
        }
    }
}

我想要实现标准的顶部栏手势滑动折叠、展开效果,目前缺少完整的手势控制逻辑实现,恳请大家提供相关参考资料或解决方案。


解决方案

问题原因

你当前代码的核心问题是onPreScroll方法始终返回Offset.Zero,意味着你没有消费任何滚动事件,嵌套滚动系统不会将顶部栏的偏移变更和列表滚动做联动;另外你缺少了松手后的吸顶/吸底惯性逻辑,也没有将偏移量应用到顶部栏的布局属性上。

完整实现步骤

1. 修正嵌套滚动连接逻辑

你需要根据滚动方向合理消费滚动偏移,同时添加松手后的自动折叠/展开逻辑:

val toolbarHeight = 48.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeight.roundToPx().toFloat() }
val toolbarOffsetHeightPx = remember { mutableFloatStateOf(0f) }
// 定义阈值,滚动超过一半高度松手就自动切换折叠/展开状态
val collapseThreshold = toolbarHeightPx * 0.5f

val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            val delta = available.y
            return when {
                // 向上滚动、顶部栏未完全折叠:消费滚动偏移折叠顶部栏
                delta < 0 && toolbarOffsetHeightPx.value > -toolbarHeightPx -> {
                    val newOffset = (toolbarOffsetHeightPx.value + delta).coerceIn(-toolbarHeightPx, 0f)
                    val consumed = newOffset - toolbarOffsetHeightPx.value
                    toolbarOffsetHeightPx.value = newOffset
                    Offset(0f, consumed)
                }
                // 向下滚动、列表已到顶部:消费滚动偏移展开顶部栏
                delta > 0 && toolbarOffsetHeightPx.value < 0f -> {
                    val newOffset = (toolbarOffsetHeightPx.value + delta).coerceIn(-toolbarHeightPx, 0f)
                    val consumed = newOffset - toolbarOffsetHeightPx.value
                    toolbarOffsetHeightPx.value = newOffset
                    Offset(0f, consumed)
                }
                else -> Offset.Zero
            }
        }

        // 处理松手后的惯性滚动
        override suspend fun onPreFling(available: Velocity, source: NestedScrollSource): Velocity {
            val currentOffset = toolbarOffsetHeightPx.value
            return when {
                currentOffset < -collapseThreshold && available.y < 0 -> {
                    animateToOffset(-toolbarHeightPx)
                    Velocity(0f, available.y)
                }
                currentOffset > -collapseThreshold && available.y > 0 -> {
                    animateToOffset(0f)
                    Velocity(0f, available.y)
                }
                else -> Velocity.Zero
            }
        }

        private suspend fun animateToOffset(target: Float) {
            toolbarOffsetHeightPx.animateTo(
                targetValue = target,
                animationSpec = tween(durationMillis = 200)
            )
        }
    }
}

2. 将偏移应用到实际布局

你需要给顶部栏添加偏移修饰符,同时给列表添加顶部padding避免内容被遮挡:

Box(
    Modifier
        .fillMaxSize()
        .nestedScroll(nestedScrollConnection)
) {
    // 列表内容
    LazyColumn(
        contentPadding = PaddingValues(top = toolbarHeight),
        modifier = Modifier.fillMaxSize()
    ) {
        // 此处添加你的列表项逻辑
    }
    // 顶部应用栏
    TopAppBar(
        title = { Text("示例标题") },
        modifier = Modifier
            .height(toolbarHeight)
            .offset { IntOffset(0, toolbarOffsetHeightPx.value.roundToInt()) }
    )
}

优化建议

如果需要实现Material Design风格的折叠工具栏,可以直接使用Material3库中提供的TopAppBarScrollBehavior相关API,无需手动处理嵌套滚动逻辑;你也可以根据顶部栏的折叠进度添加标题缩放、背景透明度渐变等自定义动画效果。


内容的提问来源于stack exchange,提问作者prat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 01:57:01