androidandroid-jetpack-composepopupandroid-window

How to get Popup global position in Compose?


I want to determine the Popup position. I use androidx.compose.ui.window.Popup

As usual in Compose it's possible to do it, using Modifier : onGloballyPositioned

But there is 2 issues:

  1. Popup does not have a modifier as a parameter.
  2. When I tried to use onGloballyPositioned in the child view, it returns zero offset, as I understood it considers Popup like a root layout.

The popup is positioned relative to its parent, and I can use parent position, offset and alignment to calculate it, maybe there are some others (more simplest) approach to get the Popup global position?


Solution

  • This actually very good and tricky question which is something i have figured out recently while building a Tooltip.

    Popup Position is calculated inside PopupPositionProvider you pass or it's implicitly set by overload function you pass Offset or Alignment

    This is the default AlignmentOffsetPositionProvider for Popup i copied from original source code.

    internal class AlignmentOffsetPositionProvider(
        val alignment: Alignment,
        val offset: IntOffset
    ) : PopupPositionProvider {
        override fun calculatePosition(
            anchorBounds: IntRect,
            windowSize: IntSize,
            layoutDirection: LayoutDirection,
            popupContentSize: IntSize
        ): IntOffset {
            // TODO: Decide which is the best way to round to result without reimplementing Alignment.align
            var popupPosition = IntOffset(0, 0)
    
            // Get the aligned point inside the parent
            val parentAlignmentPoint = alignment.align(
                IntSize.Zero,
                IntSize(anchorBounds.width, anchorBounds.height),
                layoutDirection
            )
            // Get the aligned point inside the child
            val relativePopupPos = alignment.align(
                IntSize.Zero,
                IntSize(popupContentSize.width, popupContentSize.height),
                layoutDirection
            )
    
            // Add the position of the parent
            popupPosition += IntOffset(anchorBounds.left, anchorBounds.top)
    
            // Add the distance between the parent's top left corner and the alignment point
            popupPosition += parentAlignmentPoint
    
            // Subtract the distance between the children's top left corner and the alignment point
            popupPosition -= IntOffset(relativePopupPos.x, relativePopupPos.y)
    
            // Add the user offset
            val resolvedOffset = IntOffset(
                offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
                offset.y
            )
            popupPosition += resolvedOffset
    
            return popupPosition
        }
    }
    

    You can get popupPosition from this by sending a class that holds value. However when your popUp has padding you need to take that into consideration because popupContentSize is width or height of the content plus paddings.

    And second thing to note is if your alignment is close any edge, let's say to start it returns negative values but when PopupProperties have clippingEnabled = true which is default value it will be reset to zero on screen while mathematical value is negative for instance. You should also take that into consideration.

    Last but not least popup top position is always based on window. Even it's at (0, 0) in your Composable it will return (0, statusBarHeight) this is another tricky thing to consider.

    And yes, Popup creates another window and because of that Modifier.globallyPositioned doesn't work if you use it.positionInRoot/Parent/Window but you can still check top left with it.positionOnScreen

    I have some examples with Popup which you can check and see logs for positions and other properties in link below and there are other samples as well.

    https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/d23c6fcdd0da2393335dc2234cea17e3cb0895e8/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter3_layout/Tutorial3_11Popup1.kt#L472