Created
June 28, 2022 21:34
-
-
Save KONFeature/2f84436e1c0a1926505cac934d470f90 to your computer and use it in GitHub Desktop.
Service ready to display complete view (with the right context to access the window manager and layout inflater if needed, but also access to a saved state registry and a view model store owner). Then a compose overlay service, that use the first one to push a compose view as system overlay, and also proposing a simple draggable box that can be …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Intent | |
import android.graphics.PixelFormat | |
import android.os.IBinder | |
import android.view.Gravity | |
import android.view.WindowManager | |
import androidx.compose.foundation.gestures.detectDragGestures | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.BoxScope | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.input.pointer.consumeAllChanges | |
import androidx.compose.ui.input.pointer.pointerInput | |
import androidx.compose.ui.platform.ComposeView | |
import androidx.lifecycle.ViewTreeLifecycleOwner | |
import androidx.lifecycle.ViewTreeViewModelStoreOwner | |
import androidx.savedstate.setViewTreeSavedStateRegistryOwner | |
import kotlin.math.roundToInt | |
/** | |
* Service that is ready to display compose overlay view | |
* @author Quentin Nivelais | |
*/ | |
abstract class ComposeOverlayViewService : ViewReadyService() { | |
// Build the layout param for our popup | |
private val layoutParams by lazy { | |
WindowManager.LayoutParams( | |
WindowManager.LayoutParams.WRAP_CONTENT, | |
WindowManager.LayoutParams.WRAP_CONTENT, | |
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, | |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, | |
PixelFormat.TRANSLUCENT | |
).apply { | |
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL | |
} | |
} | |
// The current offset of our overlay composable | |
private var overlayOffset by mutableStateOf(Offset.Zero) | |
// Access our window manager | |
private val windowManager by lazy { | |
overlayContext.getSystemService(WindowManager::class.java) | |
} | |
// Build our compose view | |
private val composeView by lazy { | |
ComposeView(overlayContext) | |
} | |
override fun onBind(intent: Intent): IBinder? = null | |
override fun onCreate() { | |
super.onCreate() | |
// Bound the compose lifecycle, view model and view tree saved state, into our view service | |
ViewTreeLifecycleOwner.set(composeView, this) | |
ViewTreeViewModelStoreOwner.set(composeView) { viewModelStore } | |
composeView.setViewTreeSavedStateRegistryOwner(this) | |
// Set the content of our compose view | |
composeView.setContent { Content() } | |
// Push the compose view into our window manager | |
windowManager.addView(composeView, layoutParams) | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
// Remove our compose view from the window manager | |
windowManager.removeView(composeView) | |
} | |
@Composable | |
abstract fun Content() | |
/** | |
* Draggable box container (not used by default, since not every overlay should be draggable) | |
*/ | |
@Composable | |
internal fun OverlayDraggableContainer(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit) = | |
Box( | |
modifier = modifier.pointerInput(Unit) { | |
detectDragGestures { change, dragAmount -> | |
change.consumeAllChanges() | |
// Update our current offset | |
val newOffset = overlayOffset + dragAmount | |
overlayOffset = newOffset | |
// Update the layout params, and then the view | |
layoutParams.apply { | |
x = overlayOffset.x.roundToInt() | |
y = overlayOffset.y.roundToInt() | |
} | |
windowManager.updateViewLayout(composeView, layoutParams) | |
} | |
}, | |
content = content | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyComposeOverlayService : ComposeOverlayViewService() { | |
override fun onBind(intent: Intent): IBinder? = null | |
@Composable | |
override fun Content() = OverlayDraggableContainer { | |
Text("My super component") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.hardware.display.DisplayManager | |
import android.view.Display | |
import android.view.WindowManager | |
import androidx.lifecycle.LifecycleService | |
import androidx.lifecycle.ViewModelStore | |
import androidx.lifecycle.ViewModelStoreOwner | |
import androidx.savedstate.SavedStateRegistry | |
import androidx.savedstate.SavedStateRegistryController | |
import androidx.savedstate.SavedStateRegistryOwner | |
/** | |
* Service that is ready to display view, provide a ui context on the primary screen, and all the tools needed to built a view with state managment, view model etc | |
* @author Quentin Nivelais | |
*/ | |
abstract class ViewReadyService : LifecycleService(), SavedStateRegistryOwner, ViewModelStoreOwner { | |
/** | |
* Build our saved state registry controller | |
*/ | |
private val savedStateRegistryController: SavedStateRegistryController by lazy(LazyThreadSafetyMode.NONE) { | |
SavedStateRegistryController.create(this) | |
} | |
/** | |
* Build our view model store | |
*/ | |
private val internalViewModelStore: ViewModelStore by lazy { | |
ViewModelStore() | |
} | |
/** | |
* Context dedicated to the view | |
*/ | |
internal val overlayContext: Context by lazy { | |
// Get the default display | |
val defaultDisplay: Display = getSystemService(DisplayManager::class.java).getDisplay(Display.DEFAULT_DISPLAY) | |
// Create a display context, and then the window context | |
createDisplayContext(defaultDisplay) | |
.createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null) | |
} | |
override fun onCreate() { | |
super.onCreate() | |
// Restore the last saved state registry | |
savedStateRegistryController.performRestore(null) | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
} | |
override val savedStateRegistry: SavedStateRegistry | |
get() = savedStateRegistryController.savedStateRegistry | |
override fun getViewModelStore(): ViewModelStore = internalViewModelStore | |
} |
你好,很棒的代码,它对我很有帮助,但是我发现一个小问题,OverlayDraggableContainer 的 Offset 需要判断 Gravity
我是这样做的
val x = if (layoutParams.gravity and Gravity.END xor Gravity.END != 0) { overlayOffset.x + dragAmount.x } else { overlayOffset.x - dragAmount.x }
val y = if (layoutParams.gravity and Gravity.BOTTOM xor Gravity.BOTTOM != 0) { overlayOffset.y + dragAmount.y } else { overlayOffset.y - dragAmount.y }
overlayOffset = Offset(x,y)
layoutParams.apply {
this.x = overlayOffset.x.roundToInt()
this.y = overlayOffset.y.roundToInt()
}
This is an incredibly useful gist, @KONFeature! Would you mind attaching a license to it? Thanks 👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I cannot find the library for ViewTreeSavedStateRegistryOwner for some reason it's not working, can you copy and paste the libraries used in your project