From 6082a468f6f6638c4f20dbe2dbc6a000fd9bbe39 Mon Sep 17 00:00:00 2001 From: Kyant0 Date: Mon, 23 Jun 2025 20:40:54 +0800 Subject: [PATCH] Update border --- app/build.gradle.kts | 5 ++ .../playground/BlurredAnimatedContent.kt | 72 ------------------- .../com/kyant/glass/playground/Preview.kt | 32 ++++++--- .../kyant/liquidglass/LiquidGlassModifier.kt | 55 ++++++++------ ...assState.kt => LiquidGlassShadersCache.kt} | 28 ++------ 5 files changed, 63 insertions(+), 129 deletions(-) delete mode 100644 app/src/main/java/com/kyant/glass/playground/BlurredAnimatedContent.kt rename library/src/main/java/com/kyant/liquidglass/{LiquidGlassState.kt => LiquidGlassShadersCache.kt} (56%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 800b460..3427d0b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -90,5 +90,10 @@ dependencies { implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.material.ripple) + implementation("androidx.compose.runtime:runtime") { + version { + strictly("1.9.0-alpha04") + } + } implementation(libs.androidx.graphics.shapes) } diff --git a/app/src/main/java/com/kyant/glass/playground/BlurredAnimatedContent.kt b/app/src/main/java/com/kyant/glass/playground/BlurredAnimatedContent.kt deleted file mode 100644 index f8acd28..0000000 --- a/app/src/main/java/com/kyant/glass/playground/BlurredAnimatedContent.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.kyant.glass.playground - -import android.graphics.RenderEffect -import android.graphics.Shader -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedContentScope -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.ContentTransform -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.togetherWith -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.asComposeRenderEffect -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.drop - -@Composable -fun BlurredAnimatedContent( - targetState: () -> S, - modifier: Modifier = Modifier, - transitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = { - (fadeIn(animationSpec = tween(220, delayMillis = 90)) + - scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90))) - .togetherWith(fadeOut(animationSpec = tween(90))) - }, - contentAlignment: Alignment = Alignment.TopStart, - label: String = "AnimatedContent", - contentKey: (targetState: S) -> Any? = { it }, - content: @Composable() AnimatedContentScope.(targetState: S) -> Unit -) { - val blurRadius = remember { Animatable(0f) } - - LaunchedEffect(Unit) { - snapshotFlow { targetState() } - .drop(1) - .collectLatest { - blurRadius.animateTo(24f, spring(stiffness = 2000f)) - blurRadius.animateTo(0f, spring(stiffness = 2000f)) - } - } - - AnimatedContent( - targetState(), - modifier = modifier.graphicsLayer { - val blurRadiusPx = blurRadius.value.dp.toPx() - if (blurRadiusPx > 0f) { - renderEffect = - RenderEffect.createBlurEffect( - blurRadiusPx, - blurRadiusPx, - Shader.TileMode.DECAL - ).asComposeRenderEffect() - } - }, - transitionSpec = transitionSpec, - contentAlignment = contentAlignment, - label = label, - contentKey = contentKey, - content = content - ) -} diff --git a/app/src/main/java/com/kyant/glass/playground/Preview.kt b/app/src/main/java/com/kyant/glass/playground/Preview.kt index bf76bdd..70c3d91 100644 --- a/app/src/main/java/com/kyant/glass/playground/Preview.kt +++ b/app/src/main/java/com/kyant/glass/playground/Preview.kt @@ -1,11 +1,10 @@ package com.kyant.glass.playground -import android.graphics.BlendMode import android.graphics.RenderEffect import android.graphics.RuntimeShader import android.graphics.Shader +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.Image -import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectTransformGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -20,10 +19,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asComposeRenderEffect +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer @@ -161,11 +164,20 @@ fun Preview() { clip = true shape = RoundedRectangle(state.cornerRadius.value) } - .border( - width = 2.dp, - color = Color.White.copy(alpha = 0.35f), - shape = RoundedRectangle(state.cornerRadius.value) - ) + .drawWithCache { // border + val shape = RoundedRectangle(state.cornerRadius.value) + val outline = shape.createOutline(size, layoutDirection, this) + + onDrawWithContent { + drawContent() + drawOutline( + outline = outline, + color = Color.White.copy(alpha = 0.2f), + style = Stroke(3.dp.toPx()), + blendMode = BlendMode.Plus + ) + } + } .graphicsLayer { // white point & chroma boost val contrast = state.contrast.value val whitePoint = state.whitePoint.value @@ -408,7 +420,7 @@ fun Preview() { RenderEffect.createBlendModeEffect( blurredBleedRenderEffect, refractionRenderEffect, - BlendMode.SRC_OVER + android.graphics.BlendMode.SRC_OVER ).asComposeRenderEffect() } else { refractionRenderEffect.asComposeRenderEffect() @@ -470,8 +482,8 @@ fun Preview() { ) } - BlurredAnimatedContent( - { state.isInConfigurationMode }, + AnimatedContent( + state.isInConfigurationMode, Modifier .padding(16.dp) .safeDrawingPadding() diff --git a/library/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt b/library/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt index 175f958..4be3c1f 100644 --- a/library/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt +++ b/library/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt @@ -1,16 +1,22 @@ package com.kyant.liquidglass -import android.graphics.BlendMode import android.graphics.RenderEffect import android.graphics.Shader import android.os.Build -import androidx.compose.foundation.border import androidx.compose.runtime.Composable +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.draw.drawWithCache +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asComposeRenderEffect -import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.layout.boundsInRoot @@ -20,16 +26,13 @@ import androidx.compose.ui.unit.dp @Composable fun Modifier.liquidGlass( style: LiquidGlassStyle, - providerState: LiquidGlassProviderState = LocalLiquidGlassProviderState.current, - state: LiquidGlassState = rememberLiquidGlassState() + providerState: LiquidGlassProviderState = LocalLiquidGlassProviderState.current ): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val shadersCache = remember { LiquidGlassShadersCache() } + var rect: Rect? by remember { mutableStateOf(null) } + this - .border( - width = 1.dp, - color = Color.White.copy(alpha = 0.2f), - shape = style.shape - ) .graphicsLayer { clip = true shape = style.shape @@ -52,7 +55,7 @@ fun Modifier.liquidGlass( val refractionRenderEffect = RenderEffect.createChainEffect( RenderEffect.createRuntimeShaderEffect( - state.refractionShader.apply { + shadersCache.refractionShader.apply { setFloatUniform("size", size.width, size.height) setFloatUniform("cornerRadius", cornerRadiusPx) @@ -72,7 +75,7 @@ fun Modifier.liquidGlass( val bleedRenderEffect = RenderEffect.createChainEffect( RenderEffect.createRuntimeShaderEffect( - state.bleedShader.apply { + shadersCache.bleedShader.apply { setFloatUniform("size", size.width, size.height) setFloatUniform("cornerRadius", cornerRadiusPx) @@ -102,7 +105,7 @@ fun Modifier.liquidGlass( RenderEffect.createBlendModeEffect( blurredBleedRenderEffect, refractionRenderEffect, - BlendMode.SRC_OVER + android.graphics.BlendMode.SRC_OVER ) } else { refractionRenderEffect @@ -115,7 +118,7 @@ fun Modifier.liquidGlass( if (contrast != 0f || whitePoint != 0f || chromaMultiplier != 1f) { RenderEffect.createChainEffect( RenderEffect.createRuntimeShaderEffect( - state.colorManipulationShader.apply { + shadersCache.colorManipulationShader.apply { setFloatUniform("contrast", contrast) setFloatUniform("whitePoint", whitePoint) setFloatUniform("chromaMultiplier", chromaMultiplier) @@ -128,23 +131,29 @@ fun Modifier.liquidGlass( refractionAndBleedRenderEffect.asComposeRenderEffect() } - state.graphicsLayer.renderEffect = renderEffect + val graphicsLayer = obtainGraphicsLayer() + graphicsLayer.renderEffect = renderEffect + + val outline = style.shape.createOutline(size, layoutDirection, this) onDrawBehind { - val rect = state.rect ?: return@onDrawBehind - state.graphicsLayer.record { - withTransform({ - clipRect(0f, 0f, size.width, size.height) - translate(-rect.left, -rect.top) - }) { + val rect = rect ?: return@onDrawBehind + graphicsLayer.record { + translate(-rect.left, -rect.top) { drawLayer(providerState.graphicsLayer) } } - drawLayer(state.graphicsLayer) + drawLayer(graphicsLayer) + drawOutline( + outline = outline, + color = Color.White.copy(alpha = 0.2f), + style = Stroke(3.dp.toPx()), + blendMode = BlendMode.Plus + ) } } .onGloballyPositioned { layoutCoordinates -> - state.rect = providerState.rect?.let { + rect = providerState.rect?.let { layoutCoordinates.boundsInRoot().translate(-it.topLeft) } } diff --git a/library/src/main/java/com/kyant/liquidglass/LiquidGlassState.kt b/library/src/main/java/com/kyant/liquidglass/LiquidGlassShadersCache.kt similarity index 56% rename from library/src/main/java/com/kyant/liquidglass/LiquidGlassState.kt rename to library/src/main/java/com/kyant/liquidglass/LiquidGlassShadersCache.kt index 2de3dc9..ae1754d 100644 --- a/library/src/main/java/com/kyant/liquidglass/LiquidGlassState.kt +++ b/library/src/main/java/com/kyant/liquidglass/LiquidGlassShadersCache.kt @@ -3,34 +3,14 @@ package com.kyant.liquidglass import android.graphics.RuntimeShader import android.os.Build import androidx.annotation.RequiresApi -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.layer.GraphicsLayer -import androidx.compose.ui.graphics.rememberGraphicsLayer -@Composable -fun rememberLiquidGlassState(): LiquidGlassState { - val graphicsLayer = rememberGraphicsLayer() - return remember(graphicsLayer) { - LiquidGlassState(graphicsLayer) - } -} - -class LiquidGlassState internal constructor( - internal val graphicsLayer: GraphicsLayer -) { - - internal var rect: Rect? by mutableStateOf(null) +internal class LiquidGlassShadersCache() { private var _colorManipulationShader: RuntimeShader? = null private var _refractionShader: RuntimeShader? = null private var _bleedShader: RuntimeShader? = null - internal val colorManipulationShader: RuntimeShader + val colorManipulationShader: RuntimeShader @RequiresApi(Build.VERSION_CODES.TIRAMISU) get() { if (_colorManipulationShader == null) { @@ -39,7 +19,7 @@ class LiquidGlassState internal constructor( return _colorManipulationShader!! } - internal val refractionShader: RuntimeShader + val refractionShader: RuntimeShader @RequiresApi(Build.VERSION_CODES.TIRAMISU) get() { if (_refractionShader == null) { @@ -48,7 +28,7 @@ class LiquidGlassState internal constructor( return _refractionShader!! } - internal val bleedShader: RuntimeShader + val bleedShader: RuntimeShader @RequiresApi(Build.VERSION_CODES.TIRAMISU) get() { if (_bleedShader == null) {