1

I have tried to use this answer where he uses OnGloballyPositionedModifier but it is not returning the correct absolute Pos of my children.I tried positionInWindow(), positionInRoot(). It appears to work when the column does the spacing but not when I do it with offset.

@Composable
fun GetAbsolutePos(
    content: @Composable () -> Unit,
) {
    Box(modifier = Modifier
        .onGloballyPositioned { coordinates ->
            println("My coordinates: " + coordinates.positionInRoot())
        }
    ) {
        content()
    }

}


@Preview
@Composable
fun test() {
    GetAbsolutePos() {
        Box(
            modifier = Modifier
                .width(PixelToDp(pixelSize = 200))
                .offset(x = PixelToDp(pixelSize = 100), y = PixelToDp(pixelSize = 50))
                .height(PixelToDp(pixelSize = 200))
                .background(Color.Red)
        ) {
            GetAbsolutePos() {
                Column(
                ) {
                    GetAbsolutePos() {
                        Box(
                            Modifier
                                .size(PixelToDp(pixelSize = 20))
                                .background(Color.Green)
                        )
                    }
                    GetAbsolutePos() {
                        Box(
                            Modifier
                                .size(PixelToDp(pixelSize = 20))
                                .background(Color.Blue)
                        )
                    }
                }
            }
        }
    }
}

which prints the following

I/System.out: My coordinates: Offset(0.0, 0.0)    // wrong I used offset, it should be 100x50
I/System.out: My coordinates: Offset(100.0, 50.0) // correct
I/System.out: My coordinates: Offset(100.0, 50.0) // correct
I/System.out: My coordinates: Offset(100.0, 70.0) // correct? so it works on column spacing but not with offset????

Is there a way to get the Absolute Position of my composable function in relation to the whole ANDROID WINDOW?

Edit1:

@Composable
fun PixelToDp(pixelSize: Int): Dp {
    return with(LocalDensity.current) { pixelSize.toDp() }
}

enter image description here

EDIT:2

The order of modifier should not matter since the function onGloballyPositioned states that.

This callback will be invoked at least once when the LayoutCoordinates are available, and every time the element's position changes within the window.

The same is true for onSizeChanged and can be observed by running the code below (same example as in the answer by @Thracian.)

Invoked with the size of the modified Compose UI element when the element is first measured or when the size of the element changes.

Since the function is literally a callback the order shouldn't matter. If im wrong with this pls feel free to correct me.

    Box(modifier = Modifier
        .onSizeChanged {
            println(" Size 1: $it")
        }
        .size(PixelToDp(pixelSize = 150))
        .onSizeChanged {
            println(" Size 2: $it")
        }
        .background(Color.Red)
    )

Prints

I/System.out:  Size 2: 150 x 150
I/System.out:  Size 1: 150 x 150
Ojav
  • 678
  • 6
  • 22

1 Answers1

6

You are not taking order of modifiers matter into consideration when you check position of your Composables

Box(modifier = Modifier
    .onGloballyPositioned {
      val positionInRoot =  it.positionInRoot()
        println(" POSITION 1: $positionInRoot")
    }
    .offset {
        IntOffset(100,50)
    }
    .onGloballyPositioned {
        val positionInRoot =  it.positionInRoot()
        println(" POSITION 2: $positionInRoot")
    }
    .size(100.dp)
    .background(Color.Red)
)

Prints:

 POSITION 1: Offset(0.0, 0.0)
 POSITION 2: Offset(100.0, 50.0)

In this answer i explained how order of modifiers change result for some of the modifiers including Modifier.offset

https://stackoverflow.com/a/74145347/5457853

I made an example to show interaction with siblings, other modifiers and position based on how modifiers applied agains Modifier.offset or Modifier.graphicsLayer

@Composable
private fun OffsetAndTranslationExample() {
    val context = LocalContext.current

    var value by remember { mutableStateOf(0f) }

    var positionBeforeOffset by remember {
        mutableStateOf("Offset.Zero")
    }

    var positionAfterOffset by remember {
        mutableStateOf("Offset.Zero")
    }



    Row(modifier = Modifier.border(2.dp, Color.Red)) {
        Box(
            modifier = Modifier
                .onGloballyPositioned {
                    positionBeforeOffset = "Position before offset: ${it.positionInRoot()}"
                }
                .offset {
                    IntOffset(value.toInt(), 0)
                }
                .onGloballyPositioned {
                    positionAfterOffset = "Position after offset: ${it.positionInRoot()}"
                }
                .clickable {
                    Toast
                        .makeText(context, "One with offset is clicked", Toast.LENGTH_SHORT)
                        .show()
                }
                .zIndex(2f)
                .shadow(2.dp)
                .border(2.dp, Color.Green)
                .background(Orange400)
                .size(120.dp)
        )
        Box(
            modifier = Modifier
                .zIndex(1f)
                .background(Blue400)
                .size(120.dp)
                .clickable {
                    Toast
                        .makeText(context, "Static composable is clicked", Toast.LENGTH_SHORT)
                        .show()
                }
        )
    }

    Spacer(modifier = Modifier.height(40.dp))

    Row(modifier = Modifier.border(2.dp, Color.Red)) {
        Box(
            modifier = Modifier
                .graphicsLayer {
                    translationX = value
                }
                .clickable {
                    Toast
                        .makeText(context, "One with graphicsLayer is clicked", Toast.LENGTH_SHORT)
                        .show()
                }
                .zIndex(2f)
                .shadow(2.dp)
                .border(2.dp, Color.Green)
                .background(Orange400)
                .size(120.dp)
        )
        Box(
            modifier = Modifier
                .zIndex(1f)
                .background(Blue400)
                .size(120.dp)
                .clickable {
                    Toast
                        .makeText(context, "Static composable is clicked", Toast.LENGTH_SHORT)
                        .show()
                }
        )
    }

    Spacer(modifier = Modifier.height(5.dp))
    Text("Offset/Translation: ${value.round2Digits()}")
    Slider(
        value = value,
        onValueChange = {
            value = it
        },
        valueRange = 0f..1000f
    )

    Text(positionBeforeOffset)
    Text(positionAfterOffset)
}

As can be seen changing offset of Orange Composable doesn't effect position of Blue Composable but it changes where touch position is bounded and global position changes before and after offset is applied

enter image description here

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Hey this kinda makes sense but also kinda doesn't (atleast for me). Can you check `Edit:2` and explain to me why `onSizeChanged` gets reinvoked with the same `Order of modifiers` but `onGloballyPositioned` is not and how to achieve the same result? Still thank you <3 – Ojav Nov 15 '22 at 08:02
  • 1
    For the size Modifiers if you don't use requiredSize any size modifiers that chained doesn't change the dimensions of your Composable first one is applied. Modifier.size(100).size(500) or any other non required modifiers doesn't change size it's always measured 100.dp – Thracian Nov 15 '22 at 08:11
  • 1
    I have a detailed answer about size modifiers here https://stackoverflow.com/a/73258726/5457853 – Thracian Nov 15 '22 at 08:12
  • So `OnGloballyPositionedModifier` is never truly the absolute position of a composable since it is only taking the offset of the parent but never of the children into account? Or in other words all modifiers after the function call are not considered – Ojav Nov 15 '22 at 08:43
  • So my only option is to modify the position inside my function/before the call. – Ojav Nov 15 '22 at 08:44
  • 1
    It's more like about offset or graphicsLayer actually. It's absolute position, yes. But using offset or graphicsLayer you can change interaction positions – Thracian Nov 15 '22 at 08:44
  • 1
    @Ojav added an example to show to visualize what i meant. You can check it out – Thracian Nov 15 '22 at 09:56
  • Hey thanks again for everything <3. To anyone who joins later here I also recommend to create a [`custom layout`](https://developer.android.com/jetpack/compose/layouts/custom) where you can use `placeable.placeRelativeWithLayer(offsetValue.x, offsetValue.y)` which is used by the `.offset modifier` and to also try `placeable.place(x,y)` as pointed out by @Thracian in our discussion chat – Ojav Nov 15 '22 at 13:00