-
-
Save handstandsam/6ecff2f39da72c0b38c07aa80bbb5a2f to your computer and use it in GitHub Desktop.
import android.os.Bundle | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.LifecycleRegistry | |
import androidx.savedstate.SavedStateRegistry | |
import androidx.savedstate.SavedStateRegistryController | |
import androidx.savedstate.SavedStateRegistryOwner | |
internal class MyLifecycleOwner : SavedStateRegistryOwner { | |
private var mLifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) | |
private var mSavedStateRegistryController: SavedStateRegistryController = SavedStateRegistryController.create(this) | |
/** | |
* @return True if the Lifecycle has been initialized. | |
*/ | |
val isInitialized: Boolean | |
get() = true | |
override fun getLifecycle(): Lifecycle { | |
return mLifecycleRegistry | |
} | |
fun setCurrentState(state: Lifecycle.State) { | |
mLifecycleRegistry.currentState = state | |
} | |
fun handleLifecycleEvent(event: Lifecycle.Event) { | |
mLifecycleRegistry.handleLifecycleEvent(event) | |
} | |
override fun getSavedStateRegistry(): SavedStateRegistry { | |
return mSavedStateRegistryController.savedStateRegistry | |
} | |
fun performRestore(savedState: Bundle?) { | |
mSavedStateRegistryController.performRestore(savedState) | |
} | |
fun performSave(outBundle: Bundle) { | |
mSavedStateRegistryController.performSave(outBundle) | |
} | |
} |
import android.app.AlertDialog | |
import android.app.Service | |
import android.content.Intent | |
import android.graphics.PixelFormat | |
import android.os.Build | |
import android.os.IBinder | |
import android.util.TypedValue | |
import android.view.Window | |
import android.view.WindowManager | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.layout.wrapContentSize | |
import androidx.compose.material.Text | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.platform.ComposeView | |
import androidx.compose.ui.unit.sp | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.ViewModelStore | |
import androidx.lifecycle.ViewTreeLifecycleOwner | |
import androidx.lifecycle.ViewTreeViewModelStoreOwner | |
import androidx.savedstate.ViewTreeSavedStateRegistryOwner | |
import com.viatek.fitnation.echelon_android.R | |
class OverlayService : Service() { | |
val windowManager get() = getSystemService(WINDOW_SERVICE) as WindowManager | |
override fun onCreate() { | |
super.onCreate() | |
setTheme(R.style.ThemeOverlay_AppCompat_Light) | |
showOverlay() | |
} | |
private fun showOverlay() { | |
val layoutFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY | |
} else { | |
WindowManager.LayoutParams.TYPE_PHONE | |
} | |
val params = WindowManager.LayoutParams( | |
WindowManager.LayoutParams.WRAP_CONTENT, | |
WindowManager.LayoutParams.WRAP_CONTENT, | |
layoutFlag, | |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, | |
PixelFormat.TRANSLUCENT | |
) | |
val composeView = ComposeView(this) | |
composeView.setContent { | |
Text( | |
text = "Hello", | |
color = Color.Black, | |
fontSize = 50.sp, | |
modifier = Modifier | |
.wrapContentSize() | |
.background(Color.Green) | |
) | |
} | |
// Trick The ComposeView into thinking we are tracking lifecycle | |
val viewModelStore = ViewModelStore() | |
val lifecycleOwner = MyLifecycleOwner() | |
lifecycleOwner.performRestore(null) | |
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) | |
ViewTreeLifecycleOwner.set(composeView, lifecycleOwner) | |
ViewTreeViewModelStoreOwner.set(composeView) { viewModelStore } | |
ViewTreeSavedStateRegistryOwner.set(composeView, lifecycleOwner) | |
windowManager.addView(composeView, params) | |
} | |
override fun onBind(intent: Intent): IBinder? { | |
return null | |
} | |
} |
@wilinz @FreePhoenix888 I fixed the ComposeView not recomposing by, in addition to lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
, adding
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
Reached the same conclusions on some of the comments, specially the updated APIs and the requirement to trigger the START and RESUME lifecycle events so recomposition worked. I found the snippet here, that's why I was unable to find the updates suggested by commenters: https://www.jetpackcompose.app/snippets/OverlayService See my code here: https://github.com/xrubioj/JetpackComposeOverlayTest
Does anyone know how to handle on back button pressed in this service to close the service/remove the overlay when the back button is pressed?
give permission in manifest file::
permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
in MainActivity code is ::
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
)
startActivityForResult(intent, 101)
}
setContent {
TikDownloaderTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val context = LocalContext.current
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
AddFloatingButtonOverlay(windowManager)
}
}
}
}
}
@composable
fun AddFloatingButtonOverlay(windowManager: WindowManager) {
val context = LocalContext.current
val composeView = remember {
ComposeView(context).apply {
setContent {
Box {
FloatingButton(onClick = { })
}
}
}
}
// Set layout parameters for the floating button
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
android.graphics.PixelFormat.TRANSLUCENT
)
// Set the position of the floating button on the screen
params.x = 100 // Set the X position
params.y = 100 // Set the Y position
composeView.setViewTreeLifecycleOwner(LocalLifecycleOwner.current)
composeView.setViewTreeSavedStateRegistryOwner(LocalSavedStateRegistryOwner.current)
windowManager.addView(composeView, params)
}
@composable
fun FloatingButton(onClick: () -> Unit) {
FloatingActionButton(
onClick = onClick,
modifier = androidx.compose.ui.Modifier.padding(16.dp)
) {
Text(text = "Floating Overlay")
}
}
Thanks for this, I used this code to create a floating timer https://github.com/tberghuis/FloatingCountdownTimer
HI ! thanks a lot for your beautiful project that helped me to understand some things. the readme and the documentation are quite short. So as the project has a lot of dependency injections it took me a while to understand the overlay mechanism. Do you have a site or a place you posted a kind of step by step documentation build or something else ?
@GC-public-projects there is no documentation as my implementation is hacked together using this gist and some stackoverflow answers.
I think Google should provide an official sample implementation now that XML views are no longer encouraged.
There is an open source library to create overlay view with jetpackcompose
https://github.com/KuhakuPixel/UberAlles/
(DISCLAIMER: I am the author and still in early stage of development, would appreciate your feedback and fix :D )