Aron
Aron

Reputation: 146

System-wide screen overlay: a draggable UI element made in Compose?

My goal is to have a small UI element (50dp bubble/square) that is draggable and is shown over the system screens and other apps. Yes, like Messenger chat bubbles.

I have successfully set up a ComposeView and used it with WindowManager to display it over other apps. The small view is also draggable.

The problem is I want to be able to drag it anywhere on the screen, but without blocking touch input outside of the square's area. The blocking happens because I use a Box with the modifier fillMaxSize(). If I don't use fillMaxSize(), the square is clipped when dragged outside of the 50dp x 50dp area of its initial position.

This is the Composable

@Composable
fun OverlayScreen(
) {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .offset {
                IntOffset(offsetX.roundToInt(), offsetY.roundToInt())
            }) {
        Box(
            modifier = Modifier
                .height(50.dp)
                .width(50.dp)
                .pointerInput(Unit) {
                    detectDragGestures { change, dragAmount ->
                        change.consume()
                        offsetX += dragAmount.x
                        offsetY += dragAmount.y
                    }
                }
                .background(color = Color(100, 9, 100))

        ) {
            Text("Hello Compose!")
        }
    }
}

And this is the ComposeView:

val bubbleOverlay = ComposeView(context).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                MaterialTheme {
                    OverlayScreen()
                }
            }
        }

I also launch a foreground service and ask for ACTION_MANAGE_OVERLAY_PERMISSION ( Draw over other apps).

Upvotes: 3

Views: 1027

Answers (1)

Aron
Aron

Reputation: 146

Found the answer.

All you need to do is let Compose handle the dragging and send the drag amount for X and Y out to a function that updates the View's position in the Window.

Set your layout params:

mParams = WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
            PixelFormat.TRANSLUCENT
        )

Create a function to update the View's position in the Window:

private fun moveOverlay(x: Int, y: Int) {
    mParams.x += x
    mParams.y += y
    mWindowManager.updateViewLayout(bubbleOverlay, mParams)
}

Pass that function in to Compose

        bubbleOverlay = ComposeView(context).apply {
        setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
        setContent {
            MaterialTheme {
                OverlayScreen(
                    ::moveOverlay
                )
            }
        }
    }

And make sure the Composable doesn't fill the whole screen, so don't use fillMaxSize().

Upvotes: 4

Related Questions