Reputation: 45
My goal is to have a small UI element (24dp bubble) that is draggable and is shown over the system screens and other apps. Yes, like XRecorder 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 when I set WindowManager Layout Params to MATCH_PARENT. If I don't, the bubble is clipped when dragged outside of the area of its initial position.
This is the Composable
@Composable
fun Overlay(
modifier: Modifier = Modifier,
move: (Int, Int) -> Unit,
) {
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
val context = LocalContext.current
val onClick: () -> Unit = remember {
{
context.startService(
Intent(
ACTION_SCREENSHOT,
Uri.EMPTY,
context,
AppService::class.java
)
)
}
}
IconButton(
modifier = modifier
.wrapContentSize()
.offset {
IntOffset(offsetX.roundToInt(), offsetY.roundToInt())
}
.background(
color = MaterialTheme.colorScheme.primary,
shape = CircleShape,
)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offsetX += dragAmount.x
offsetY += dragAmount.y
move(
dragAmount.x.roundToInt(),
dragAmount.y.roundToInt()
)
}
},
onClick = onClick,
content = {
Icon(
imageVector = Icons.Rounded.Screenshot,
tint = MaterialTheme.colorScheme.onPrimary,
contentDescription = "Take a screenshot"
)
}
)
}
And this is the ComposeView:
// Create a ComposeView to host the overlay UI
val view = ComposeView(this).also {
// Set the content of the ComposeView to the Overlay composable function
it.setContent {
MaterialTheme {
Overlay { x, y ->
overlayParams.x += x
overlayParams.y += y
windowManager.updateViewLayout(view, overlayParams)
}
}
}
// Trick the ComposeView into thinking we are tracking lifecycle events
savedStateRegistryController.performRestore(null)
handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
// Set the view tree lifecycle owner, view model store owner, and saved state registry owner
it.setViewTreeLifecycleOwner(this)
it.setViewTreeViewModelStoreOwner(this)
it.setViewTreeSavedStateRegistryOwner(this)
}
I also launch a foreground service and ask for ACTION_MANAGE_OVERLAY_PERMISSION ( Draw over other apps).
Upvotes: 1
Views: 359
Reputation: 61
You need to update the position of the ComposeView by calling WindowManger.updateViewLayout() method. Have a look at this implementation: AssistAccessibilityService.kt
in this example the position is updated here:
bubbleComposeView.setContent {
Bubble(onRequestTranslate = { dx, dy ->
Log.i("AssistAccessibilityService", "onRequestTranslate: $dx, $dy")
position.x += dx
position.y += dy
windowManager.updateViewLayout(bubbleComposeView, getLayoutParams())
}, onRequestFinished = {
GlobalPreferences.saveInt(PREF_KEY_POSITION_X, position.x)
GlobalPreferences.saveInt(PREF_KEY_POSITION_Y, position.y)
}, onClick = {
if (!panelVisible()) {
showPanel()
}
Log.i("AssistAccessibilityService", "onClick")
}, modifier = Modifier.width(64.dp).height(64.dp))
}
Upvotes: 0