Add new backdrop module

This commit is contained in:
Kyant0 committed 2025-09-13 10:23:13 +08:00
1 parent 090374a06d
commit 3eaf794064
17 files changed
+336 -243

No files matched your search

+1
View File
@@ -0,0 +1 @@
/build
+47
View File
@@ -0,0 +1,47 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
id("dev.shreyaspatil.compose-compiler-report-generator")
}
android {
namespace = "com.kyant.backdrop"
compileSdk = 36
buildToolsVersion = "36.0.0"
defaultConfig {
minSdk = 21
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_21
freeCompilerArgs.addAll(
"-jvm-default=no-compatibility",
)
}
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
}
View File
Whitespace-only changes.
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,21 @@
package com.kyant.backdrop
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.layout.LayoutCoordinates
fun Modifier.backdrop(backdrop: Backdrop): Modifier {
val element = backdrop.element
return if (element != null) {
this then element
} else {
this
}
}
interface Backdrop {
val element: Modifier.Element?
fun DrawScope.drawChild(coordinates: LayoutCoordinates)
}
@@ -0,0 +1,18 @@
package com.kyant.backdrop
import androidx.compose.ui.graphics.drawscope.DrawScope
sealed interface BackdropDrawScope : DrawScope {
fun drawBackdrop()
}
class SimpleBackdropDrawScope(
val drawScope: DrawScope,
val onDrawBackdrop: DrawScope.() -> Unit
) : BackdropDrawScope, DrawScope by drawScope {
override fun drawBackdrop() {
onDrawBackdrop()
}
}
@@ -0,0 +1,116 @@
package com.kyant.backdrop
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
@Composable
fun rememberLayerBackdrop(backgroundColor: Color?): LayerBackdrop {
val graphicsLayer = rememberGraphicsLayer()
return remember(graphicsLayer, backgroundColor) {
LayerBackdrop(
graphicsLayer = graphicsLayer,
backgroundColor = backgroundColor
)
}
}
@Stable
class LayerBackdrop(
val graphicsLayer: GraphicsLayer,
val backgroundColor: Color?
) : Backdrop {
override val element: Modifier.Element?
get() = LayerBackdropElement(this)
var backdropPosition: Offset by mutableStateOf(Offset.Zero)
override fun DrawScope.drawChild(coordinates: LayoutCoordinates) {
val position = coordinates.positionOnScreen() - backdropPosition
translate(-position.x, -position.y) {
if (backgroundColor != null && backgroundColor.isSpecified) {
drawContext.canvas.nativeCanvas.drawColor(backgroundColor.toArgb())
}
drawLayer(graphicsLayer)
}
}
}
private class LayerBackdropElement(
val backdrop: LayerBackdrop
) : ModifierNodeElement<LayerBackdropNode>() {
override fun create(): LayerBackdropNode {
return LayerBackdropNode(backdrop = backdrop)
}
override fun update(node: LayerBackdropNode) {
node.update(backdrop = backdrop)
}
override fun InspectorInfo.inspectableProperties() {
name = "layerBackdrop"
properties["backdrop"] = backdrop
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LayerBackdropElement) return false
if (backdrop != other.backdrop) return false
return true
}
override fun hashCode(): Int {
return backdrop.hashCode()
}
}
private class LayerBackdropNode(
var backdrop: LayerBackdrop
) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
override fun ContentDrawScope.draw() {
drawContent()
backdrop.graphicsLayer.record {
backdrop.backgroundColor?.let { drawRect(it) }
this@draw.drawContent()
}
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
if (coordinates.isAttached) {
backdrop.backdropPosition = coordinates.positionOnScreen()
}
}
fun update(backdrop: LayerBackdrop) {
this.backdrop = backdrop
}
}