Add transformBlock param to liquidGlass modifiers
This commit is contained in:
1 parent
d1953995f9
commit
5c0514fe5b
7 files changed
+171
-112
No files matched your search
@@ -90,7 +90,7 @@ See [here](./glassmusic/src/main/java/com/kyant/glassmusic/BottomTabs.kt#L123) f
|
||||
|
||||
### Tips
|
||||
|
||||
The following case is not supported:
|
||||
- The following case is not supported:
|
||||
|
||||
```kotlin
|
||||
LiquidGlassProvider(providerState) {
|
||||
@@ -107,6 +107,17 @@ Box {
|
||||
}
|
||||
```
|
||||
|
||||
- Apply the scale correctly:
|
||||
|
||||
```kotlin
|
||||
Modifier
|
||||
.scale(scaleX, scaleY)
|
||||
.liquidGlass(
|
||||
// ...
|
||||
transformBlock = { scale(1f / scaleX, 1f / scaleY, Offset.Zero) }
|
||||
)
|
||||
```
|
||||
|
||||
## Comparisons
|
||||
|
||||
Android device: Google Pixel 4 XL (the smallest width is adjusted to 440 dp to match the density of the iOS device)
|
||||
|
||||
Binary file not shown.
@@ -21,11 +21,17 @@ internal class GlassShapeElement(
|
||||
) : ModifierNodeElement<GlassShapeNode>() {
|
||||
|
||||
override fun create(): GlassShapeNode {
|
||||
return GlassShapeNode(style, compositingStrategy)
|
||||
return GlassShapeNode(
|
||||
style = style,
|
||||
compositingStrategy = compositingStrategy
|
||||
)
|
||||
}
|
||||
|
||||
override fun update(node: GlassShapeNode) {
|
||||
node.update(style, compositingStrategy)
|
||||
node.update(
|
||||
style = style,
|
||||
compositingStrategy = compositingStrategy
|
||||
)
|
||||
}
|
||||
|
||||
override fun InspectorInfo.inspectableProperties() {
|
||||
@@ -72,7 +78,10 @@ internal class GlassShapeNode(
|
||||
compositingStrategy = this@GlassShapeNode.compositingStrategy
|
||||
}
|
||||
|
||||
override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult {
|
||||
override fun MeasureScope.measure(
|
||||
measurable: Measurable,
|
||||
constraints: Constraints
|
||||
): MeasureResult {
|
||||
val placeable = measurable.measure(constraints)
|
||||
|
||||
return layout(placeable.width, placeable.height) {
|
||||
|
||||
@@ -9,21 +9,6 @@ import com.kyant.liquidglass.shadow.GlassShadow
|
||||
|
||||
/**
|
||||
* The style of the liquid glass.
|
||||
*
|
||||
* @param shape
|
||||
* The shape of the glass.
|
||||
*
|
||||
* @param innerRefraction
|
||||
* The inner refraction effect of the liquid glass.
|
||||
*
|
||||
* @param material
|
||||
* The material properties of the liquid glass.
|
||||
*
|
||||
* @param highlight
|
||||
* The highlight effect applied to the liquid glass.
|
||||
*
|
||||
* @param shadow
|
||||
* The shadow effect applied to the liquid glass.
|
||||
*/
|
||||
@Immutable
|
||||
data class GlassStyle(
|
||||
|
||||
@@ -18,7 +18,9 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.asAndroidColorFilter
|
||||
import androidx.compose.ui.graphics.asComposeRenderEffect
|
||||
import androidx.compose.ui.graphics.drawscope.DrawTransform
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||
import androidx.compose.ui.graphics.layer.drawLayer
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
@@ -44,24 +46,28 @@ import kotlinx.coroutines.launch
|
||||
fun Modifier.liquidGlass(
|
||||
state: LiquidGlassProviderState,
|
||||
style: GlassStyle,
|
||||
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen
|
||||
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
|
||||
transformBlock: (DrawTransform.() -> Unit)? = null
|
||||
): Modifier =
|
||||
this.liquidGlass(
|
||||
state = state,
|
||||
luminanceSampler = null,
|
||||
compositingStrategy = compositingStrategy,
|
||||
transformBlock = transformBlock,
|
||||
style = { style }
|
||||
)
|
||||
|
||||
fun Modifier.liquidGlass(
|
||||
state: LiquidGlassProviderState,
|
||||
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
|
||||
transformBlock: (DrawTransform.() -> Unit)? = null,
|
||||
style: () -> GlassStyle
|
||||
): Modifier =
|
||||
this.liquidGlass(
|
||||
state = state,
|
||||
luminanceSampler = null,
|
||||
compositingStrategy = compositingStrategy,
|
||||
transformBlock = transformBlock,
|
||||
style = style
|
||||
)
|
||||
|
||||
@@ -70,12 +76,14 @@ fun Modifier.liquidGlass(
|
||||
state: LiquidGlassProviderState,
|
||||
style: GlassStyle,
|
||||
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
|
||||
transformBlock: (DrawTransform.() -> Unit)? = null,
|
||||
luminanceSampler: LuminanceSampler? = null
|
||||
): Modifier =
|
||||
this.liquidGlass(
|
||||
state = state,
|
||||
luminanceSampler = luminanceSampler,
|
||||
compositingStrategy = compositingStrategy,
|
||||
transformBlock = transformBlock,
|
||||
style = { style }
|
||||
)
|
||||
|
||||
@@ -84,6 +92,7 @@ fun Modifier.liquidGlass(
|
||||
state: LiquidGlassProviderState,
|
||||
luminanceSampler: LuminanceSampler? = null,
|
||||
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
|
||||
transformBlock: (DrawTransform.() -> Unit)? = null,
|
||||
style: () -> GlassStyle
|
||||
): Modifier =
|
||||
this
|
||||
@@ -103,7 +112,8 @@ fun Modifier.liquidGlass(
|
||||
LiquidGlassElement(
|
||||
state = state,
|
||||
style = style,
|
||||
luminanceSampler = luminanceSampler
|
||||
luminanceSampler = luminanceSampler,
|
||||
transformBlock = transformBlock
|
||||
) then GlassBrushElement(
|
||||
style = style
|
||||
) then GlassHighlightElement(
|
||||
@@ -118,14 +128,16 @@ fun Modifier.liquidGlass(
|
||||
private class LiquidGlassElement(
|
||||
val state: LiquidGlassProviderState,
|
||||
val style: () -> GlassStyle,
|
||||
val luminanceSampler: LuminanceSampler?
|
||||
val luminanceSampler: LuminanceSampler?,
|
||||
val transformBlock: (DrawTransform.() -> Unit)?
|
||||
) : ModifierNodeElement<LiquidGlassNode>() {
|
||||
|
||||
override fun create(): LiquidGlassNode {
|
||||
return LiquidGlassNode(
|
||||
state = state,
|
||||
style = style,
|
||||
luminanceSampler = luminanceSampler
|
||||
luminanceSampler = luminanceSampler,
|
||||
transformBlock = transformBlock
|
||||
)
|
||||
}
|
||||
|
||||
@@ -133,7 +145,8 @@ private class LiquidGlassElement(
|
||||
node.update(
|
||||
state = state,
|
||||
style = style,
|
||||
luminanceSampler = luminanceSampler
|
||||
luminanceSampler = luminanceSampler,
|
||||
transformBlock = transformBlock
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,6 +155,7 @@ private class LiquidGlassElement(
|
||||
properties["state"] = state
|
||||
properties["style"] = style
|
||||
properties["luminanceSampler"] = luminanceSampler
|
||||
properties["transformBlock"] = transformBlock
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -151,6 +165,7 @@ private class LiquidGlassElement(
|
||||
if (state != other.state) return false
|
||||
if (style != other.style) return false
|
||||
if (luminanceSampler != other.luminanceSampler) return false
|
||||
if (transformBlock != other.transformBlock) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -159,6 +174,7 @@ private class LiquidGlassElement(
|
||||
var result = state.hashCode()
|
||||
result = 31 * result + style.hashCode()
|
||||
result = 31 * result + (luminanceSampler?.hashCode() ?: 0)
|
||||
result = 31 * result + (transformBlock?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -167,7 +183,8 @@ private class LiquidGlassElement(
|
||||
private class LiquidGlassNode(
|
||||
var state: LiquidGlassProviderState,
|
||||
var style: () -> GlassStyle,
|
||||
var luminanceSampler: LuminanceSampler?
|
||||
var luminanceSampler: LuminanceSampler?,
|
||||
var transformBlock: (DrawTransform.() -> Unit)?
|
||||
) : GlobalPositionAwareModifierNode, ObserverModifierNode, DelegatingNode() {
|
||||
|
||||
override val shouldAutoInvalidate: Boolean = false
|
||||
@@ -177,6 +194,7 @@ private class LiquidGlassNode(
|
||||
|
||||
private var samplerJob: Job? = null
|
||||
private var samplerLayer: GraphicsLayer? = null
|
||||
private var samplerChanged = true
|
||||
|
||||
private var currentStyle = style()
|
||||
|
||||
@@ -197,7 +215,7 @@ private class LiquidGlassNode(
|
||||
private var _innerRefractionRenderEffect: RenderEffect? = null
|
||||
|
||||
private var _renderEffect: androidx.compose.ui.graphics.RenderEffect? = null
|
||||
|
||||
private var transformBlockChanged = true
|
||||
private var _position: Offset? = null
|
||||
|
||||
private val drawNode = delegate(CacheDrawModifierNode {
|
||||
@@ -309,15 +327,19 @@ private class LiquidGlassNode(
|
||||
}
|
||||
|
||||
val position = position
|
||||
if (renderEffectChanged || _position != position) {
|
||||
_position = position
|
||||
val positionChanged = _position != position
|
||||
_position = position
|
||||
if (renderEffectChanged || transformBlockChanged || positionChanged) {
|
||||
transformBlockChanged = false
|
||||
graphicsLayer?.record {
|
||||
translate(-position.x, -position.y) {
|
||||
drawLayer(state.graphicsLayer)
|
||||
}
|
||||
}
|
||||
if (samplerJob != null) {
|
||||
samplerLayer?.record {
|
||||
val transformBlock = transformBlock
|
||||
if (transformBlock != null) {
|
||||
withTransform(transformBlock) {
|
||||
translate(-position.x, -position.y) {
|
||||
drawLayer(state.graphicsLayer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
translate(-position.x, -position.y) {
|
||||
drawLayer(state.graphicsLayer)
|
||||
}
|
||||
@@ -325,6 +347,23 @@ private class LiquidGlassNode(
|
||||
}
|
||||
}
|
||||
|
||||
if (samplerChanged) {
|
||||
if (luminanceSampler != null) {
|
||||
samplerLayer =
|
||||
requireGraphicsContext().createGraphicsLayer().apply {
|
||||
compositingStrategy = androidx.compose.ui.graphics.layer.CompositingStrategy.Offscreen
|
||||
}
|
||||
}
|
||||
}
|
||||
if (samplerChanged || positionChanged) {
|
||||
samplerLayer?.record {
|
||||
translate(-position.x, -position.y) {
|
||||
drawLayer(state.graphicsLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
samplerChanged = false
|
||||
|
||||
onDrawBehind {
|
||||
graphicsLayer?.let { layer ->
|
||||
drawLayer(layer)
|
||||
@@ -350,12 +389,63 @@ private class LiquidGlassNode(
|
||||
compositingStrategy = androidx.compose.ui.graphics.layer.CompositingStrategy.Offscreen
|
||||
}
|
||||
|
||||
luminanceSampler?.let { sampler ->
|
||||
samplerLayer =
|
||||
graphicsContext.createGraphicsLayer().apply {
|
||||
compositingStrategy = androidx.compose.ui.graphics.layer.CompositingStrategy.Offscreen
|
||||
}
|
||||
updateStyle()
|
||||
updateSampler()
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
samplerJob?.cancel()
|
||||
samplerJob = null
|
||||
|
||||
val graphicsContext = requireGraphicsContext()
|
||||
graphicsLayer?.let { layer ->
|
||||
graphicsContext.releaseGraphicsLayer(layer)
|
||||
graphicsLayer = null
|
||||
}
|
||||
samplerLayer?.let { layer ->
|
||||
graphicsContext.releaseGraphicsLayer(layer)
|
||||
samplerLayer = null
|
||||
samplerChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
fun update(
|
||||
state: LiquidGlassProviderState,
|
||||
style: () -> GlassStyle,
|
||||
luminanceSampler: LuminanceSampler?,
|
||||
transformBlock: (DrawTransform.() -> Unit)?
|
||||
) {
|
||||
if (this.state != state ||
|
||||
this.style != style
|
||||
) {
|
||||
this.state = state
|
||||
this.style = style
|
||||
updateStyle()
|
||||
}
|
||||
if (this.luminanceSampler != luminanceSampler) {
|
||||
this.luminanceSampler = luminanceSampler
|
||||
updateSampler()
|
||||
}
|
||||
if (this.transformBlock != transformBlock) {
|
||||
this.transformBlock = transformBlock
|
||||
transformBlockChanged = true
|
||||
drawNode.invalidateDrawCache()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStyle() {
|
||||
observeReads { currentStyle = style() }
|
||||
drawNode.invalidateDrawCache()
|
||||
}
|
||||
|
||||
private fun updateSampler() {
|
||||
samplerJob?.cancel()
|
||||
samplerJob = null
|
||||
samplerLayer?.let { layer ->
|
||||
requireGraphicsContext().releaseGraphicsLayer(layer)
|
||||
samplerLayer = null
|
||||
}
|
||||
this.luminanceSampler?.let { sampler ->
|
||||
samplerJob =
|
||||
coroutineScope.launch {
|
||||
while (isActive) {
|
||||
@@ -366,44 +456,7 @@ private class LiquidGlassNode(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateStyle()
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
val graphicsContext = requireGraphicsContext()
|
||||
graphicsLayer?.let { layer ->
|
||||
graphicsContext.releaseGraphicsLayer(layer)
|
||||
graphicsLayer = null
|
||||
}
|
||||
|
||||
samplerJob?.cancel()
|
||||
samplerJob = null
|
||||
|
||||
samplerLayer?.let { layer ->
|
||||
graphicsContext.releaseGraphicsLayer(layer)
|
||||
samplerLayer = null
|
||||
}
|
||||
}
|
||||
|
||||
fun update(
|
||||
state: LiquidGlassProviderState,
|
||||
style: () -> GlassStyle,
|
||||
luminanceSampler: LuminanceSampler?
|
||||
) {
|
||||
if (this.state != state ||
|
||||
this.style != style ||
|
||||
this.luminanceSampler != luminanceSampler
|
||||
) {
|
||||
this.state = state
|
||||
this.style = style
|
||||
this.luminanceSampler = luminanceSampler
|
||||
updateStyle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStyle() {
|
||||
observeReads { currentStyle = style() }
|
||||
samplerChanged = true
|
||||
drawNode.invalidateDrawCache()
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -21,16 +21,8 @@ class ContinuousLuminanceSampler(
|
||||
val onAnimateToLuminance: ((animationSpec: FiniteAnimationSpec<Float>, luminance: Float) -> Unit)? = null
|
||||
) : LuminanceSampler {
|
||||
|
||||
private val luminanceAnimation = Animatable(0f)
|
||||
|
||||
private val animationSpec =
|
||||
tween<Float>(durationMillis.toInt(), 0, easing)
|
||||
|
||||
override val sampleIntervalMillis: Long = 0L
|
||||
|
||||
override val luminance: Float
|
||||
get() = luminanceAnimation.value.fastCoerceIn(0f, 1f)
|
||||
|
||||
private val impulseLuminanceSampler =
|
||||
ImpulseLuminanceSampler(
|
||||
sampleIntervalMillis = sampleIntervalMillis,
|
||||
@@ -38,8 +30,16 @@ class ContinuousLuminanceSampler(
|
||||
scaledSize = scaledSize
|
||||
)
|
||||
|
||||
private val luminanceAnimation = Animatable(0f)
|
||||
|
||||
override val luminance: Float
|
||||
get() = luminanceAnimation.value.fastCoerceIn(0f, 1f)
|
||||
|
||||
private var hasValidValue = false
|
||||
|
||||
private val animationSpec =
|
||||
tween<Float>(durationMillis.toInt(), 0, easing)
|
||||
|
||||
override suspend fun sample(graphicsLayer: GraphicsLayer) {
|
||||
impulseLuminanceSampler.sample(graphicsLayer)
|
||||
val sampledLuminance = impulseLuminanceSampler.luminance
|
||||
|
||||
+28
-27
@@ -41,39 +41,40 @@ class ImpulseLuminanceSampler(
|
||||
val scaledHeight = scaledSize.height
|
||||
|
||||
if (!graphicsLayer.isReleased && graphicsLayer.size.width > 0 && graphicsLayer.size.height > 0) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap()
|
||||
val scaledBitmap =
|
||||
val bitmap =
|
||||
withContext(Dispatchers.IO) {
|
||||
graphicsLayer.toImageBitmap().asAndroidBitmap()
|
||||
}
|
||||
val scaledBitmap =
|
||||
withContext(Dispatchers.IO) {
|
||||
Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, false)
|
||||
.copy(Bitmap.Config.ARGB_8888, false)
|
||||
|
||||
var sumLuminance = 0f
|
||||
val totalPixels = scaledWidth * scaledHeight
|
||||
|
||||
for (y in 0 until scaledHeight) {
|
||||
for (x in 0 until scaledWidth) {
|
||||
val pixel = scaledBitmap.getPixel(x, y)
|
||||
val red = (pixel shr 16) and 0xFF
|
||||
val green = (pixel shr 8) and 0xFF
|
||||
val blue = pixel and 0xFF
|
||||
|
||||
val pixelLuminance = (0.299f * red + 0.587f * green + 0.114f * blue) / 255f
|
||||
sumLuminance += pixelLuminance
|
||||
}
|
||||
}
|
||||
|
||||
val newLuminance =
|
||||
((sumLuminance / totalPixels / precision).fastRoundToInt() * precision).fastCoerceIn(0f, 1f)
|
||||
if (!newLuminance.isNaN()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
onChangeToLuminance?.invoke(newLuminance)
|
||||
luminance = newLuminance
|
||||
}
|
||||
}
|
||||
var sumLuminance = 0f
|
||||
val totalPixels = scaledWidth * scaledHeight
|
||||
|
||||
scaledBitmap.recycle()
|
||||
bitmap.recycle()
|
||||
for (y in 0 until scaledHeight) {
|
||||
for (x in 0 until scaledWidth) {
|
||||
val pixel = scaledBitmap.getPixel(x, y)
|
||||
val red = (pixel shr 16) and 0xFF
|
||||
val green = (pixel shr 8) and 0xFF
|
||||
val blue = pixel and 0xFF
|
||||
|
||||
val pixelLuminance = (0.299f * red + 0.587f * green + 0.114f * blue) / 255f
|
||||
sumLuminance += pixelLuminance
|
||||
}
|
||||
}
|
||||
|
||||
val newLuminance =
|
||||
((sumLuminance / totalPixels / precision).fastRoundToInt() * precision).fastCoerceIn(0f, 1f)
|
||||
if (!newLuminance.isNaN()) {
|
||||
onChangeToLuminance?.invoke(newLuminance)
|
||||
luminance = newLuminance
|
||||
}
|
||||
|
||||
scaledBitmap.recycle()
|
||||
bitmap.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user