I am trying to create a path animation of Svg in the desktop compose app.
fun SvgDocument.drawCompletedFills(buildPaths: List<BuildPath>, drawScope: DrawScope, canvas: Canvas, targetLength: Float) {
var accumulatedLength = 0f
val matrix = computeSkiaMatrix(drawScope)
buildPaths.forEach { built ->
val pathEnd = accumulatedLength + built.length
if (targetLength >= pathEnd - COMPLETION_EPSILON) {
val fillPaint = built.svgNode.createFillPaint()
val transformedPath = built.skiaPath.transform(matrix)
canvas.nativeCanvas.drawPath(transformedPath, fillPaint)
}
accumulatedLength += built.length
}
}
above method runs the path animation , draw on the canvas but as soon as the next frame runs the previously ran animated path disappear.
Below method works and draw as intended
fun SvgDocument.drawPartialStrokes(
buildPaths : List<BuildPath>,
drawScope: DrawScope,
canvas: Canvas,
targetLength: Float,
) {
var remainingLength = targetLength
buildPaths.forEach { built ->
if (remainingLength <= 0f) return@forEach
//print("\n built path length: ${built.length} with targetLength = $targetLength and remainingLength = $remainingLength")
val pathDrawLength = remainingLength.coerceAtMost(built.length)
if (pathDrawLength > 0f) {
val tracedPath = Path()
val pathMeasure = PathMeasure().apply {
setPath(built.skiaPath, false)
}
var lengthLeftOnPath = pathDrawLength
do {
val contourLength = pathMeasure.length
if (lengthLeftOnPath <= 0f) break
val segmentLength = lengthLeftOnPath.coerceAtMost(contourLength)
if (segmentLength > 0f) {
pathMeasure.getSegment(
0f,
segmentLength,
tracedPath,
true
)
}
lengthLeftOnPath -= segmentLength
} while (pathMeasure.nextContour())
//val composePath = tracedPath.toComposePath(this, drawScope)
val strokePaint = built.svgNode.createStrokePaint()
canvas.nativeCanvas.drawPath(tracedPath.transform(computeSkiaMatrix(drawScope)),strokePaint)
}
remainingLength -= pathDrawLength
}
}
both of the above methods is called in the below canvas
@Composable
fun SvgDocument.pathTrace(modifier: Modifier = Modifier, buildPaths: List<BuildPath>, totalLength: Float, progress: Float) {
Canvas(modifier) {
val clampedProgress = progress.coerceIn(0f, 1f)
val targetLength = clampedProgress * totalLength
drawIntoCanvas { canvas ->
//canvas.nativeCanvas.saveLayer(null,null)
drawPartialStrokes(buildPaths, this, canvas, targetLength)
drawCompletedFills(buildPaths, this, canvas, targetLength)
//canvas.nativeCanvas.restore()
}
}
}
if I comment out the drawCompletedFills method , partialSroke works and draw the path but if run both , animation flickers and only the last path stays on the screen
@Composable
fun SvgDocument.pathTraceSvgDocument(modifier: Modifier = Modifier, speedMs: Int = 10000, pauseMs: Int = 3000, easing: Easing = LinearEasing) {
val buildPaths = remember(this) { buildPaths() }
val totalLength = buildPaths.getTotalLength()
val progress = remember { Animatable(0f) }
LaunchedEffect(this, speedMs, pauseMs, easing) {
while (true) {
progress.snapTo(0f)
progress.animateTo(targetValue = 1f, animationSpec = tween(durationMillis = speedMs, easing = easing))
kotlinx.coroutines.delay(pauseMs.toLong())
}
}
pathTrace(modifier = modifier.fillMaxSize(), buildPaths = buildPaths, totalLength = totalLength, progress = progress.value)
}
I have tried many things, including save the layer and restore; instead of skia, I tried using object of compose graphics , Paint and matrix stuff ( this uses skia internally i guess)
I took the help of AI as well , but all in vain.
for full code reference here is the link : Github repo