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

如何在Jetpack Compose中实现类似CoordinatorLayout的View锚定

问题:在Jetpack Compose中实现View锚定到Toolbar底部的逻辑

在Android原生开发中,我们可以通过CoordinatorLayout的锚定属性将View绑定到其他View,比如:

<FloatingActionButton
   app:layout_anchor="@id/appBar"
   app:layout_anchorGravity="bottom|end" />

现在需要在Jetpack Compose中实现相同逻辑,将DetailsContent中的Button锚定到DetailsToolbar的底部。现有相关代码如下:

现有DetailsContent代码

@Composable
private fun DetailsContent(
    scrollState: ScrollState,
    onNamePosition: (Float) -> Unit,
    item: Poster,
    sendNotification: () -> Unit,
    contentAlpha: () -> Float,
) {
    Column(
        modifier = Modifier.verticalScroll(scrollState)
    ) {
        Box(
            modifier = Modifier.height(Dimens.DetailBoxImageHeight)
        ) {
            Image(
                painter = rememberImagePainter(item.poster),
                contentDescription = null,
                modifier = Modifier
                    .align(Alignment.TopCenter)
                    .fillMaxWidth()
                    .clip(shape = MaterialTheme.shapes.medium)
                    .alpha(contentAlpha()),
                contentScale = ContentScale.Crop,
            )
            Button(
                onClick = sendNotification,
                shape = RoundedCornerShape(36.dp),
                modifier = Modifier
                    .width(126.dp)
                    .align(Alignment.BottomCenter)
                    .offset(y = 23.5.dp)
            ) {
                Text(text = "DeepLink")
            }
        }
        Spacer(Modifier.height(32.dp))
        Text(
            modifier = Modifier.padding(8.dp),
            text = item.description,
            style = typography.body2,
            fontSize = 18.sp,
            color = MaterialTheme.colors.onSurface
        )
    }
}

外层布局代码

Box(Modifier.fillMaxSize()) {
        DetailsContent(
            scrollState = scrollState,
            onNamePosition = { newNamePosition ->
                if (detailScroller.namePosition == Float.MIN_VALUE) {
                    detailScroller =
                        detailScroller.copy(namePosition = newNamePosition)
                }
            },
            item = item,
            sendNotification = sendNotification,
            contentAlpha = { contentAlpha.value }
        )
        DetailsToolbar(
            toolbarState, item.name, pressOnBack,
            contentAlpha = { contentAlpha.value }
        )
    }

解决方案

要实现Button锚定到DetailsToolbar底部的效果,需要调整布局层级并利用Compose的布局修饰符实现动态位置控制:

1. 调整Button的布局层级

将Button从DetailsContent内部的Box中移出,放到外层的根Box容器中,使其与DetailsToolbar处于同一层级,这样才能直接基于Toolbar的位置进行锚定。

修改后的DetailsContent(移除Button):

@Composable
private fun DetailsContent(
    scrollState: ScrollState,
    onNamePosition: (Float) -> Unit,
    item: Poster,
    contentAlpha: () -> Float,
) {
    Column(
        modifier = Modifier.verticalScroll(scrollState)
    ) {
        Box(
            modifier = Modifier.height(Dimens.DetailBoxImageHeight)
        ) {
            Image(
                painter = rememberImagePainter(item.poster),
                contentDescription = null,
                modifier = Modifier
                    .align(Alignment.TopCenter)
                    .fillMaxWidth()
                    .clip(shape = MaterialTheme.shapes.medium)
                    .alpha(contentAlpha()),
                contentScale = ContentScale.Crop,
            )
        }
        Spacer(Modifier.height(32.dp))
        Text(
            modifier = Modifier.padding(8.dp),
            text = item.description,
            style = typography.body2,
            fontSize = 18.sp,
            color = MaterialTheme.colors.onSurface
        )
    }
}

2. 实现锚定逻辑

在根Box中添加Button,通过align和offset修饰符将其定位到DetailsToolbar的底部,同时适配Toolbar的折叠状态:

Box(Modifier.fillMaxSize()) {
    DetailsContent(
        scrollState = scrollState,
        onNamePosition = { newNamePosition ->
            if (detailScroller.namePosition == Float.MIN_VALUE) {
                detailScroller = detailScroller.copy(namePosition = newNamePosition)
            }
        },
        item = item,
        contentAlpha = { contentAlpha.value }
    )
    DetailsToolbar(
        toolbarState, item.name, pressOnBack,
        contentAlpha = { contentAlpha.value }
    )
    
    // 锚定到Toolbar底部的Button
    Button(
        onClick = sendNotification,
        shape = RoundedCornerShape(36.dp),
        modifier = Modifier
            .width(126.dp)
            .align(Alignment.TopCenter)
            .offset(y = with(LocalDensity.current) {
                // 根据Toolbar的折叠状态动态计算偏移量
                val maxToolbarHeight = Dimens.ToolbarExpandedHeight.toPx()
                val minToolbarHeight = Dimens.ToolbarCollapsedHeight.toPx()
                // 计算当前Toolbar的高度,加上偏移量(原代码中的23.5dp)
                val currentToolbarHeight = lerp(maxToolbarHeight, minToolbarHeight, toolbarState.progress)
                currentToolbarHeight + 23.5.dp.toPx()
            })
    ) {
        Text(text = "DeepLink")
    }
}

3. 关键说明

  • 层级调整:只有当Button和Toolbar处于同一父容器时,才能直接基于Toolbar的位置进行布局定位。
  • 动态偏移:利用toolbarState.progress(折叠进度,0为完全展开,1为完全折叠)计算Toolbar的当前高度,结合固定偏移量,确保Button始终贴合Toolbar底部。
  • 单位转换:使用LocalDensity.current将dp转换为px,保证在不同设备上的位置一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:38:10