我想获取Jetpack Compose中我的按钮(或其他元素)的高度。你知道如何获取吗?
如果您想在组合后获取按钮的高度,则可以使用:OnGloballyPositionedModifier。
它返回一个LayoutCoordinates对象,其中包含您的按钮的大小。
使用onGloballyPositioned
修饰符的示例:
@Composable
fun OnGloballyPositionedExample() {
// Get local density from composable
val localDensity = LocalDensity.current
// Create element height in pixel state
var columnHeightPx by remember {
mutableStateOf(0f)
}
// Create element height in dp state
var columnHeightDp by remember {
mutableStateOf(0.dp)
}
Column(
modifier = Modifier
.onGloballyPositioned { coordinates ->
// Set column height using the LayoutCoordinates
columnHeightPx = coordinates.size.height.toFloat()
columnHeightDp = with(localDensity) { coordinates.size.height.toDp() }
}
) {
Text(text = "Column height in pixel: $columnHeightPx")
Text(text = "Column height in dp: $columnHeightDp")
}
}
Modifier.onSizeChanged
或Modifier.onGloballyPositioned
需要您拥有MutableState
,当您在这些函数内设置时会触发重新组合,这在某些情况下可能会引起注意。如果是这种情况,请使用BoxWithConstraints
,并且在某些条件下,最大高度约束不等于实际高度,则需要使用SubcomposeLayout。 - Thracian一个完整的解决方案如下:
@Composable
fun GetHeightCompose() {
// get local density from composable
val localDensity = LocalDensity.current
var heightIs by remember {
mutableStateOf(0.dp)
}
Box(modifier = Modifier.fillMaxSize()) {
// Important column should not be inside a Surface in order to be measured correctly
Column(
Modifier
.onGloballyPositioned { coordinates ->
heightIs = with(localDensity) { coordinates.size.height.toDp() }
}) {
Text(text = "If you want to know the height of this column with text and button in Dp it is: $heightIs")
Button(onClick = { /*TODO*/ }) {
Text(text = "Random Button")
}
}
}
}
Modifier.onSizeChanged{}
或Modifier.globallyPositioned{}
可能会导致无限重新组合,如果你不小心的话,就像在OPs问题中一个Composable的大小影响另一个Composable时一样。
使用MutableState中的onSizeChanged大小值来更新布局会导致在接下来的帧中读取新的大小值并重新组合布局,从而导致一帧延迟。您可以使用onSizeChanged来影响绘图操作。使用Layout或SubcomposeLayout使一个组件的大小影响另一个组件的大小。即使用户可以注意到帧之间的变化,如果绘制出现问题也不好看。例如:Column {
var sizeInDp by remember { mutableStateOf(DpSize.Zero) }
val density = LocalDensity.current
Box(modifier = Modifier
.onSizeChanged {
sizeInDp = density.run {
DpSize(
it.width.toDp(),
it.height.toDp()
)
}
}
.size(200.dp)
.background(Color.Red))
Text(
"Hello World",
modifier = Modifier
.background(Color.White)
.size(sizeInDp)
)
}
**
* SubcomposeLayout that [SubcomposeMeasureScope.subcompose]s [mainContent]
* and gets total size of [mainContent] and passes this size to [dependentContent].
* This layout passes exact size of content unlike
* BoxWithConstraints which returns [Constraints] that doesn't match Composable dimensions under
* some circumstances
*
* @param placeMainContent when set to true places main content. Set this flag to false
* when dimensions of content is required for inside [mainContent]. Just measure it then pass
* its dimensions to any child composable
*
* @param mainContent Composable is used for calculating size and pass it
* to Composables that depend on it
*
* @param dependentContent Composable requires dimensions of [mainContent] to set its size.
* One example for this is overlay over Composable that should match [mainContent] size.
*
*/
@Composable
fun DimensionSubcomposeLayout(
modifier: Modifier = Modifier,
placeMainContent: Boolean = true,
mainContent: @Composable () -> Unit,
dependentContent: @Composable (Size) -> Unit
) {
SubcomposeLayout(
modifier = modifier
) { constraints: Constraints ->
// Subcompose(compose only a section) main content and get Placeable
val mainPlaceables: List<Placeable> = subcompose(SlotsEnum.Main, mainContent)
.map {
it.measure(constraints.copy(minWidth = 0, minHeight = 0))
}
// Get max width and height of main component
var maxWidth = 0
var maxHeight = 0
mainPlaceables.forEach { placeable: Placeable ->
maxWidth += placeable.width
maxHeight = placeable.height
}
val dependentPlaceables: List<Placeable> = subcompose(SlotsEnum.Dependent) {
dependentContent(Size(maxWidth.toFloat(), maxHeight.toFloat()))
}
.map { measurable: Measurable ->
measurable.measure(constraints)
}
layout(maxWidth, maxHeight) {
if (placeMainContent) {
mainPlaceables.forEach { placeable: Placeable ->
placeable.placeRelative(0, 0)
}
}
dependentPlaceables.forEach { placeable: Placeable ->
placeable.placeRelative(0, 0)
}
}
}
}
enum class SlotsEnum { Main, Dependent }
使用方法
val content = @Composable {
Box(
modifier = Modifier
.size(200.dp)
.background(Color.Red)
)
}
val density = LocalDensity.current
DimensionSubcomposeLayout(
mainContent = { content() },
dependentContent = { size: Size ->
content()
val dpSize = density.run {size.toDpSize() }
Box(Modifier.size(dpSize).border(3.dp, Color.Green))
},
placeMainContent = false
)
或者
DimensionSubcomposeLayout(
mainContent = { content() },
dependentContent = { size: Size ->
val dpSize = density.run {size.toDpSize() }
Box(Modifier.size(dpSize).border(3.dp, Color.Green))
}
)
BoxWithConstraints
:Button(onClick = {}){
BoxWithConstraints{
val height = maxHeight
}
}
我成功地实现了一个解决方法,可以通过使从中读取的组合现在成为无状态的来减少(而不是完全消除)使用Modifier.onSizeChanged
时的重新组合次数。虽然这不是最好的方法,但它能够完成工作。
@Composable
fun TextToMeasure(
currentHeight: Dp,
onHeightChanged: (Dp) -> Unit,
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
Text(
text = "Hello Android",
modifier = modifier
.onSizeChanged { size ->
val newHeight = with(density) { size.height.toDp }
if (newHeight != currentHeight) {
onHeightChanged(newHeight)
}
},
)
}
@Composable
fun MainScreen() {
val height = remember { mutableStateOf(0.dp) }
TextToMeasure(
currentHeight = height.value,
onHeightChanged = { height.value = it },
)
}
Modifier.onSizeChanged()
。 - CommonsWare