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

Jetpack Compose中如何获取Composable的窗口内坐标(排除状态栏高度)以实现正确的滚动透明度动画

Jetpack Compose中如何获取Composable的窗口内坐标(排除状态栏高度)以实现正确的滚动透明度动画

我完全懂你的困扰——硬编码状态栏高度来修正坐标太像“临时凑数”,而positionInWindow()和positionInRoot()返回的坐标又带着状态栏高度,导致文字滑到TopAppBar下面才开始变透明,完全不是你想要的“顶部刚碰到TopAppBar就启动动画”的效果。下面给你两个官方推荐的干净解决方案,不用手动算状态栏高度:

方案一:利用LocalWindowInsets获取状态栏内边距(官方规范用法)

虽然你提到不想手动算状态栏高度,但LocalWindowInsets是Jetpack Compose官方提供的系统内边距API,完全属于规范用法,不是临时 workaround。通过它可以直接拿到状态栏的顶部内边距,用来修正Text的坐标:

@Composable
internal fun Demo(modifier: Modifier = Modifier) {
    val density = LocalDensity.current
    val windowInsets = LocalWindowInsets.current
    // 获取状态栏顶部内边距的像素值
    val statusBarTopInsetPx = with(density) {
        windowInsets.statusBars.top.toDp().toPx()
    }

    // 动态获取TopAppBar的实际高度(避免硬编码60.dp)
    var topBarHeightPx by remember { mutableStateOf(0f) }
    var wineTitleOffset by remember { mutableStateOf(Offset.Zero) }

    // 计算透明度动画进度:当Text顶部碰到TopAppBar底部时启动动画
    val adjustedTextY = wineTitleOffset.y - statusBarTopInsetPx
    val nameFraction = ((topBarHeightPx - adjustedTextY) / topBarHeightPx).coerceIn(0f, 1f)
    // 动画方向:从完全不透明到完全透明
    val textColor = Color.Black.copy(alpha = lerp(1f, 0f, nameFraction))

    Scaffold(
        modifier = modifier,
        contentWindowInsets = WindowInsets.systemBars,
        topBar = {
            TopAppBar(
                title = { Text("Demo Top Bar") },
                modifier = Modifier.onGloballyPositioned { coords ->
                    // 动态获取TopAppBar的实际高度
                    topBarHeightPx = coords.size.height.toFloat()
                }
            )
        }
    ) { innerPadding ->
        LazyColumn(modifier = Modifier.padding(innerPadding)) {
            item { Spacer(modifier = Modifier.height(60.dp)) }
            items(10) {
                Text(text = "Hello, Peter")
            }
            item {
                Text(
                    text = "EXAMPLE TEXT",
                    fontWeight = FontWeight.Bold,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .onGloballyPositioned { coordinates ->
                            wineTitleOffset = coordinates.positionInWindow()
                        },
                    textAlign = TextAlign.Center,
                    color = textColor
                )
            }
        }
    }
}

方案优势:

  • 直接通过官方API获取状态栏内边距,无需硬编码设备相关参数
  • 动态获取TopAppBar高度,适配不同Material版本、自定义TopAppBar的场景
  • 坐标修正逻辑清晰,动画触发时机准确

方案二:用LayoutCoordinates.localPositionOf获取相对父布局的坐标

这个方法更直观:直接获取Text相对于Scaffold内容区域的坐标,天然排除状态栏、TopAppBar的高度,完全不需要处理系统内边距:

@Composable
internal fun Demo(modifier: Modifier = Modifier) {
    var topBarHeightPx by remember { mutableStateOf(0f) }
    var contentLayoutCoords by remember { mutableStateOf<LayoutCoordinates?>(null) }
    var wineTitleRelativeOffset by remember { mutableStateOf(Offset.Zero) }

    // 动画进度:当Text顶部到达TopAppBar底部时开始透明
    val nameFraction = ((topBarHeightPx - wineTitleRelativeOffset.y) / topBarHeightPx).coerceIn(0f, 1f)
    val textColor = Color.Black.copy(alpha = lerp(1f, 0f, nameFraction))

    Scaffold(
        modifier = modifier,
        contentWindowInsets = WindowInsets.systemBars,
        topBar = {
            TopAppBar(
                title = { Text("Demo Top Bar") },
                modifier = Modifier.onGloballyPositioned { coords ->
                    topBarHeightPx = coords.size.height.toFloat()
                }
            )
        }
    ) { innerPadding ->
        LazyColumn(
            modifier = Modifier
                .padding(innerPadding)
                .onGloballyPositioned { coords ->
                    contentLayoutCoords = coords
                }
        ) {
            item { Spacer(modifier = Modifier.height(60.dp)) }
            items(10) {
                Text(text = "Hello, Peter")
            }
            item {
                Text(
                    text = "EXAMPLE TEXT",
                    fontWeight = FontWeight.Bold,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .onGloballyPositioned { textCoords ->
                            contentLayoutCoords?.let { parentCoords ->
                                // 获取Text相对于LazyColumn(Scaffold内容区域)的坐标
                                wineTitleRelativeOffset = parentCoords.localPositionOf(textCoords, Offset.Zero)
                            }
                        },
                    textAlign = TextAlign.Center,
                    color = textColor
                )
            }
        }
    }
}

方案优势:

  • 完全脱离系统窗口坐标的干扰,直接基于父布局计算相对位置
  • 动画触发逻辑更直观:当Text的Y坐标等于TopAppBar高度时,刚好碰到TopAppBar底部
  • 无需处理任何系统内边距参数

额外注意事项:

  1. 不要嵌套Scaffold:你的原代码里MainActivity和Demo各有一个Scaffold,会导致innerPadding重复应用,布局偏移出错,建议只保留一个Scaffold
  2. 动画方向要匹配需求:原代码的lerp(0f, 1f, nameFraction)会让文字从透明变清晰,和“滑到TopAppBar下变透明”的需求相反,需要调整为lerp(1f, 0f, nameFraction)

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:22:59