如何获取视图绝对位置以实现遮罩层局部清晰效果?
问题: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倍。
修复步骤
- 使用
LocalDensity.current进行单位转换,将像素值正确转为dp; - 或直接使用像素单位的
offset重载方法,避免单位转换错误; - 确保遮罩层元素与原元素的尺寸、样式一致,保证位置匹配精准。
修改后的代码
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
相关产品推荐
相关产品推荐

