Use own cache drawing to prevent delegating

This commit is contained in:
Kyant0 committed 2025-09-17 12:54:39 +08:00
1 parent 71fe874b4e
commit 5953764cbb
8 files changed
+160 -66

No files matched your search

@@ -8,9 +8,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.draw.CacheDrawScope
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
@@ -43,7 +43,7 @@ internal abstract class BackdropEffectScopeImpl : BackdropEffectScope {
return runtimeShaders.getOrPut(key) { RuntimeShader(string) }
}
fun applyDrawScope(scope: CacheDrawScope) {
fun applyDrawScope(scope: DrawScope) {
density = scope.density
fontScale = scope.fontScale
size = scope.size
@@ -2,19 +2,22 @@ package com.kyant.backdrop
import android.os.Build
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.CacheDrawModifierNode
import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.asComposeRenderEffect
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.Constraints
@@ -85,7 +88,7 @@ private class ContentBackdropElement(
node.onDrawBehind = onDrawBehind
node.onDrawSurface = onDrawSurface
node.effects = effects
node.drawNode.invalidateDrawCache()
node.invalidateDrawCache()
}
override fun InspectorInfo.inspectableProperties() {
@@ -122,7 +125,7 @@ private class ContentBackdropNode(
var onDrawBehind: (DrawScope.() -> Unit)?,
var onDrawSurface: (DrawScope.() -> Unit)?,
var effects: BackdropEffectScope.() -> Unit
) : LayoutModifierNode, DelegatingNode() {
) : LayoutModifierNode, DrawModifierNode, ObserverModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
@@ -138,22 +141,14 @@ private class ContentBackdropNode(
null
}
val drawNode = delegate(CacheDrawModifierNode {
private var isCacheValid = false
private val cacheDrawBlock: DrawScope.() -> Unit = {
if (effectScope != null) {
effectScope.applyDrawScope(this)
effects(effectScope)
graphicsLayer?.renderEffect = effectScope.renderEffect?.asComposeRenderEffect()
}
onDrawWithContent {
onDrawBehind?.invoke(this)
graphicsLayer?.let { layer ->
layer.record { this@onDrawWithContent.drawContent() }
drawLayer(layer)
}
onDrawSurface?.invoke(this)
}
})
}
private val layerBlock: GraphicsLayerScope.() -> Unit = {
clip = true
@@ -171,6 +166,31 @@ private class ContentBackdropNode(
}
}
override fun ContentDrawScope.draw() {
if (!isCacheValid) {
observeReads { cacheDrawBlock() }
isCacheValid = true
}
onDrawBehind?.invoke(this)
graphicsLayer?.let { layer ->
layer.record { this@draw.drawContent() }
drawLayer(layer)
}
onDrawSurface?.invoke(this)
}
override fun onObservedReadsChanged() {
invalidateDrawCache()
}
fun invalidateDrawCache() {
isCacheValid = false
invalidateDraw()
}
override fun onAttach() {
val graphicsContext = requireGraphicsContext()
graphicsLayer = graphicsContext.createGraphicsLayer()
@@ -5,10 +5,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.CacheDrawModifierNode
import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.asComposeRenderEffect
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
@@ -16,10 +16,13 @@ import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.Constraints
@@ -104,7 +107,7 @@ private class DrawBackdropElement(
node.onDrawBackdrop = onDrawBackdrop
node.onDrawSurface = onDrawSurface
node.effects = effects
node.drawNode.invalidateDrawCache()
node.invalidateDrawCache()
}
override fun InspectorInfo.inspectableProperties() {
@@ -149,7 +152,7 @@ private class DrawBackdropNode(
var onDrawBackdrop: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit,
var onDrawSurface: (DrawScope.() -> Unit)?,
var effects: BackdropEffectScope.() -> Unit
) : LayoutModifierNode, GlobalPositionAwareModifierNode, DelegatingNode() {
) : LayoutModifierNode, DrawModifierNode, GlobalPositionAwareModifierNode, ObserverModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
@@ -166,28 +169,14 @@ private class DrawBackdropNode(
null
}
val drawNode = delegate(CacheDrawModifierNode {
private var isCacheValid = false
private val cacheDrawBlock: DrawScope.() -> Unit = {
if (effectScope != null) {
effectScope.applyDrawScope(this)
effects(effectScope)
graphicsLayer?.renderEffect = effectScope.renderEffect?.asComposeRenderEffect()
}
onDrawWithContent {
onDrawBehind?.invoke(this)
val layer = graphicsLayer
val recordBlock = recordBlock
if (layer != null && recordBlock != null) {
layer.record(block = recordBlock)
drawLayer(layer)
}
onDrawSurface?.invoke(this)
drawContent()
}
})
}
private val layerBlock: GraphicsLayerScope.() -> Unit = {
clip = true
@@ -205,12 +194,41 @@ private class DrawBackdropNode(
}
}
override fun ContentDrawScope.draw() {
if (!isCacheValid) {
observeReads { cacheDrawBlock() }
isCacheValid = true
}
onDrawBehind?.invoke(this)
val layer = graphicsLayer
val recordBlock = recordBlock
if (layer != null && recordBlock != null) {
layer.record(block = recordBlock)
drawLayer(layer)
}
onDrawSurface?.invoke(this)
drawContent()
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
if (coordinates.isAttached) {
recordBlock = { with(backdrop) { onDrawBackdrop { drawBackdrop(coordinates) } } }
}
}
override fun onObservedReadsChanged() {
invalidateDrawCache()
}
fun invalidateDrawCache() {
isCacheValid = false
invalidateDraw()
}
override fun onAttach() {
val graphicsContext = requireGraphicsContext()
graphicsLayer = graphicsContext.createGraphicsLayer()
@@ -4,12 +4,14 @@ import android.graphics.BlurMaskFilter
import android.graphics.Paint
import android.graphics.RectF
import android.os.Build
import androidx.compose.ui.draw.CacheDrawModifierNode
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.asComposePaint
import androidx.compose.ui.graphics.asComposeRenderEffect
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.isUnspecified
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
@@ -18,9 +20,12 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.Constraints
@@ -41,7 +46,7 @@ internal class HighlightElement(
override fun update(node: HighlightNode) {
node.shapeProvider = shapeProvider
node.highlight = highlight
node.drawNode.invalidateDrawCache()
node.invalidateDrawCache()
}
override fun InspectorInfo.inspectableProperties() {
@@ -70,7 +75,7 @@ internal class HighlightElement(
internal class HighlightNode(
var shapeProvider: BackdropShapeProvider,
var highlight: () -> Highlight?
) : LayoutModifierNode, DelegatingNode() {
) : LayoutModifierNode, DrawModifierNode, ObserverModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
@@ -80,13 +85,16 @@ internal class HighlightNode(
style = Paint.Style.STROKE
}
val drawNode = delegate(CacheDrawModifierNode {
val highlight = highlight()
private var isCacheValid = false
private var _highlight: Highlight? = null
private val cacheDrawBlock: DrawScope.() -> Unit = cacheDrawBlock@{
val highlight = highlight().also { _highlight = it }
val graphicsLayer = graphicsLayer
if (highlight == null || graphicsLayer == null ||
highlight.width.value <= 0f || highlight.color.isUnspecified
) {
return@CacheDrawModifierNode onDrawWithContent { drawContent() }
_highlight = null
return@cacheDrawBlock
}
val size = size
@@ -129,16 +137,7 @@ internal class HighlightNode(
}
}
}
onDrawWithContent {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
graphicsLayer.renderEffect =
highlight.style().createRenderEffect(size, strokeWidth)?.asComposeRenderEffect()
}
drawContent()
drawLayer(graphicsLayer)
}
})
}
private val layerBlock: GraphicsLayerScope.() -> Unit = {
clip = true
@@ -156,6 +155,35 @@ internal class HighlightNode(
}
}
override fun ContentDrawScope.draw() {
if (!isCacheValid) {
observeReads { cacheDrawBlock() }
isCacheValid = true
}
drawContent()
val highlight = _highlight
val graphicsLayer = graphicsLayer
if (highlight != null && graphicsLayer != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val strokeWidth = ceil(highlight.width.toPx().fastCoerceAtMost(size.minDimension / 2f))
graphicsLayer.renderEffect =
highlight.style().createRenderEffect(size, strokeWidth)?.asComposeRenderEffect()
}
drawLayer(graphicsLayer)
}
}
override fun onObservedReadsChanged() {
invalidateDrawCache()
}
fun invalidateDrawCache() {
isCacheValid = false
invalidateDraw()
}
override fun onAttach() {
val graphicsContext = requireGraphicsContext()
graphicsLayer = graphicsContext.createGraphicsLayer()
@@ -6,11 +6,13 @@ import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.RectF
import android.os.Build
import androidx.compose.ui.draw.CacheDrawModifierNode
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.asComposePaint
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.isUnspecified
import androidx.compose.ui.graphics.layer.CompositingStrategy
@@ -18,8 +20,11 @@ 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.toArgb
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.IntSize
@@ -38,7 +43,7 @@ internal class ShadowElement(
override fun update(node: ShadowNode) {
node.shapeProvider = shapeProvider
node.shadow = shadow
node.drawNode.invalidateDrawCache()
node.invalidateDrawCache()
}
override fun InspectorInfo.inspectableProperties() {
@@ -67,7 +72,7 @@ internal class ShadowElement(
internal class ShadowNode(
var shapeProvider: BackdropShapeProvider,
var shadow: () -> Shadow?
) : DelegatingNode() {
) : DrawModifierNode, ObserverModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
@@ -82,13 +87,16 @@ internal class ShadowNode(
}
}
val drawNode = delegate(CacheDrawModifierNode {
val shadow = shadow()
private var _shadow: Shadow? = null
private var isCacheValid = false
private val cacheDrawBlock: DrawScope.() -> Unit = cacheDrawBlock@{
val shadow = shadow().also { _shadow = it }
val graphicsLayer = graphicsLayer
if (shadow == null || graphicsLayer == null ||
shadow.elevation.value <= 0f || shadow.color.isUnspecified
) {
return@CacheDrawModifierNode onDrawWithContent { drawContent() }
_shadow = null
return@cacheDrawBlock
}
val size = size
@@ -138,14 +146,34 @@ internal class ShadowNode(
}
}
}
}
onDrawWithContent {
translate(-(elevation - offset.x), -(elevation - offset.y)) {
override fun ContentDrawScope.draw() {
if (!isCacheValid) {
observeReads { cacheDrawBlock() }
isCacheValid = true
}
val shadow = _shadow
val graphicsLayer = graphicsLayer
if (shadow != null && graphicsLayer != null) {
val elevation = shadow.elevation.toPx()
translate(-(elevation - shadow.offset.x.toPx()), -(elevation - shadow.offset.y.toPx())) {
drawLayer(graphicsLayer)
}
drawContent()
}
})
drawContent()
}
override fun onObservedReadsChanged() {
invalidateDrawCache()
}
fun invalidateDrawCache() {
isCacheValid = false
invalidateDraw()
}
override fun onAttach() {
val graphicsContext = requireGraphicsContext()
Binary file not shown.