How to apply PathEffect without using Stroke with Jetpack Compose?

727 Views Asked by At

I'm building a gooey effect as in gif below by combining PathEffects but as far as i know PathEffect can be applied to Stroke style only. Is there a Compose way to apply PathEffect while filling circles?

enter image description here

val discretePathEffect = DiscretePathEffect(pathMeasure.length / segmentCount, 0f)
val cornerPathEffect = PathEffect.cornerPathEffect(50f)


val chainPathEffect = PathEffect.chainPathEffect(
    outer = cornerPathEffect,
    inner = discretePathEffect.toComposePathEffect()
)

pathLeft.op(pathLeft, pathRight, PathOperation.Union)
pathMeasure.setPath(pathLeft, true)


drawPath(
    path = pathLeft,
    brush = brush,
    style = Stroke(
        4.dp.toPx(),
        pathEffect = chainPathEffect
    )
)

I need filled circles instead of stroke

1

There are 1 best solutions below

1
On BEST ANSWER

I solved this using Compose paint

val paint = remember {
    Paint()
}

and drawing with

with(drawContext.canvas) {
    this.drawPath(
        newPath,
        paint
    )
}

enter image description here

full implementation

@Composable
private fun GooeyEffectSample2() {

    val pathDynamic = remember { Path() }
    val pathStatic = remember { Path() }

    /**
     * Current position of the pointer that is pressed or being moved
     */
    var currentPosition by remember { mutableStateOf(Offset.Unspecified) }

    val segmentCount = 20
    val pathMeasure = remember {
        PathMeasure()
    }

    val modifier = Modifier
        .pointerInput(Unit) {
            detectDragGestures { change, _ ->
                currentPosition = change.position
            }
        }
        .fillMaxSize()

    val paint = remember {
        Paint()
    }

    var isPaintSetUp by remember {
        mutableStateOf(false)
    }

    Canvas(modifier = modifier) {
        val center = size.center

        val position = if (currentPosition == Offset.Unspecified) {
            center
        } else currentPosition

        pathDynamic.reset()
        pathDynamic.addOval(
            Rect(
                center = position,
                radius = 150f
            )
        )

        pathStatic.reset()
        pathStatic.addOval(
            Rect(
                center = Offset(center.x, center.y),
                radius = 100f
            )
        )

        pathMeasure.setPath(pathDynamic, true)

        val discretePathEffect = DiscretePathEffect(pathMeasure.length / segmentCount, 0f)
        val cornerPathEffect = CornerPathEffect(50f)


        val chainPathEffect = PathEffect.chainPathEffect(
            outer = cornerPathEffect.toComposePathEffect(),
            inner = discretePathEffect.toComposePathEffect()
        )

        if (!isPaintSetUp) {

            paint.shader = LinearGradientShader(
                from = Offset.Zero,
                to = Offset(size.width, size.height),
                colors = listOf(
                    Color(0xffFFEB3B),
                    Color(0xffE91E63)
                ),
                tileMode = TileMode.Clamp
            )
            isPaintSetUp = true
            paint.pathEffect = chainPathEffect
        }

        val newPath = Path.combine(PathOperation.Union, pathDynamic, pathStatic)

        with(drawContext.canvas) {
            this.drawPath(
                newPath,
                paint
            )
        }
    }
}