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

如何获取视图绝对位置以实现遮罩层局部清晰效果?

问题:Compose全屏模糊遮罩中元素位置偏移不准确

我想要创建全屏模糊遮罩效果,仅保留部分元素(如示例中的"Bye"文本)清晰,其余内容模糊。
我采用两个Box实现:一个承载主内容,另一个作为遮罩层。通过onGloballyPositioned监听"Bye"文本的全局位置,再用这些坐标在遮罩层重建该视图。
但遇到的问题是,返回的坐标值严重不准确,测试中发现onGloballyPositioned返回的值是实际值的2.8倍,且该系数随设备变化。

示例代码:

package com.example.myapplication

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var root: Offset by remember { mutableStateOf(Offset.Zero) }
            var parent: Offset by remember { mutableStateOf(Offset.Zero) }
            var window: Offset by remember { mutableStateOf(Offset.Zero) }
            var positionText by remember { mutableStateOf("") }
            var blur by remember { mutableStateOf(0.dp) }

            Box(Modifier.fillMaxSize().blur(blur).clickable(onClick = {
                if (blur > 0.dp) {
                    blur = 0.dp
                } else {
                    blur = 4.dp
                }
            })) {
                Column {
                    Text(text = "Hi")
                    Text(text = "Hi")
                    Text(text = "Hi")
                    Text(text = "Hi")
                    Text(text = "Hi")
                    Text(text = "Bye", modifier = Modifier.onGloballyPositioned {
                        root = it.positionInRoot()
                        parent = it.positionInParent()
                        window = it.positionInWindow()
                        positionText = "positionInParent: $parent\npositionInRoot: $root\npositionInWindow: $window"
                    })
                    Text(text = "Hi")
                    Text(text = "Hi")
                    Text(text = "Hi")
                    Text(text = positionText)
                }
            }

            Box(Modifier.fillMaxSize()) {
                Text(
                    modifier = Modifier.offset(x = root.x.dp, y = root.y.dp),
                    text = "Byeeeeeeeeeeeee",
                    color = Color.Blue
                )
                Text(
                    modifier = Modifier.offset(x = parent.x.dp, y = parent.y.dp),
                    text = "Byeeeeeee",
                    color = Color.Green
                )
                Text(
                    modifier = Modifier.offset(x = window.x.dp, y = window.y.dp),
                    text = "Byeee",
                    color = Color.Magenta
                )
            }
        }
    }
}

示例效果图:
示例输出


解决方案

问题根源

onGloballyPositioned返回的positionInRoot()、positionInParent()等方法的结果是像素(px)单位,而直接用.dp转换是错误的——dp是密度无关单位,不同设备的dp与px转换系数(屏幕密度density)不同,比如2.8x密度的设备1dp=2.8px,直接将px转成dp会导致偏移放大density倍。

修复步骤

  1. 使用LocalDensity.current进行单位转换,将像素值正确转为dp;
  2. 或直接使用像素单位的offset重载方法,避免单位转换错误;
  3. 确保遮罩层元素与原元素的尺寸、样式一致,保证位置匹配精准。

修改后的代码

package com.example.myapplication

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalDensity

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            val density = LocalDensity.current
            var rootOffset: Offset by remember { mutableStateOf(Offset.Zero) }
            var blur by remember { mutableStateOf(0.dp) }
            // 保存原文本尺寸,确保遮罩层匹配
            var textWidth: Dp by remember { mutableStateOf(0.dp) }
            var textHeight: Dp by remember { mutableStateOf(0.dp) }

            // 主内容层:带模糊效果
            Box(Modifier.fillMaxSize().blur(blur).clickable(onClick = {
                blur = if (blur > 0.dp) 0.dp else 4.dp
            })) {
                Column {
                    repeat(5) { Text(text = "Hi") }
                    Text(
                        text = "Bye",
                        fontSize = 16.sp, // 固定字号保证匹配
                        modifier = Modifier.onGloballyPositioned { layoutCoordinates ->
                            rootOffset = layoutCoordinates.positionInRoot()
                            // 像素尺寸转dp
                            textWidth = with(density) { layoutCoordinates.size.width.toDp() }
                            textHeight = with(density) { layoutCoordinates.size.height.toDp() }
                        }
                    )
                    repeat(3) { Text(text = "Hi") }
                }
            }

            // 遮罩层:显示清晰的目标文本
            Box(Modifier.fillMaxSize()) {
                Text(
                    text = "Bye",
                    fontSize = 16.sp, // 与原文本样式一致
                    color = Color.Black, // 与原文本颜色一致
                    modifier = Modifier.offset(
                        // 像素偏移转dp
                        x = with(density) { rootOffset.x.toDp() },
                        y = with(density) { rootOffset.y.toDp() }
                    )
                )
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 14:42:04