I am using Jetpack Compose and I want to create a circle with custom shadow/gradient effects. As far as I know there is no way to create that with composable objects inside DrawScope
and I have to use NativeCanvas
instead. That works fine for my case, but as I remember when we use View and we write something in the onDraw()
method, we SHOULD NOT INITIALIZE NEW OBJECTS there. Since the method is called on each 30/60fps when using animation and creating new objects for each call will lead to poor performance.
Where is the proper place to define those object BlurMaskFilter
, RadialGradient
, Paint
so they could be re-initialized only when the size of the composable is changes?
I was wondering if I should define them as lateinit var
outside the function and then use SideEffect
, to initialize them?
I forgot to mention that I am using InfiniteTransition
, and then using the state to change shapes that are drawn inside the NativeCanvas
!
Box(
modifier = Modifier
.size(widthDp, widthDp)
.drawBehind {
drawIntoCanvas { canvas ->
canvas.nativeCanvas.apply {
val blurMask = BlurMaskFilter(
15f,
BlurMaskFilter.Blur.NORMAL
)
val radialGradient = android.graphics.RadialGradient(
100f, 100f, 50f,
intArrayOf(android.graphics.Color.WHITE, android.graphics.Color.BLACK),
floatArrayOf(0f, 0.9f), android.graphics.Shader.TileMode.CLAMP
)
val paint = Paint().asFrameworkPaint().apply {
shader = radialGradient
maskFilter = blurMask
color = android.graphics.Color.WHITE
}
drawCircle(100f, 100f, 50f, paint)
}
}
}
) {
}
There are two ways to keep some objects between recompositions in Compose - using remember
or representation models. For this particular case remember
is a better fit.
If you have a static size given by Modifier.size(widthDp, widthDp)
, it is easy to calculate everything in advance:
val density = LocalDensity.current
val paint = remember(widthDp) {
// in case you need to use width in your calculations
val widthPx = with(density) {
widthDp.toPx()
}
val blurMask = BlurMaskFilter(
15f,
BlurMaskFilter.Blur.NORMAL
)
val radialGradient = android.graphics.RadialGradient(
100f, 100f, 50f,
intArrayOf(android.graphics.Color.WHITE, android.graphics.Color.BLACK),
floatArrayOf(0f, 0.9f), android.graphics.Shader.TileMode.CLAMP
)
Paint().asFrameworkPaint().apply {
shader = radialGradient
maskFilter = blurMask
color = android.graphics.Color.WHITE
}
}
If you don't have a static size, for example you want to use Modifier.fillMaxSize
, you can use Modifier.onSizeChanged
to get the real size and update your Paint
- that's why I pass size
as key
in the remember
call - it will recalculate the value when the key
changes.
val (size, updateSize) = remember { mutableStateOf<IntSize?>(null) }
val paint = remember(size) {
if (size == null) {
Paint()
} else {
Paint().apply {
// your code
}
}
}
Box(
modifier = Modifier
.fillMaxSize()
.onSizeChanged(updateSize)
.drawBehind {
// ...
}
)