Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhirkevich Alexander Y authored and Zhirkevich Alexander Y committed Jul 16, 2024
1 parent f5419b4 commit 314efaa
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.compose.ui.unit.Dp
import io.github.alexzhirkevich.compottie.avp.animator.FloatAnimator
import io.github.alexzhirkevich.compottie.avp.animator.PaintAnimator
import io.github.alexzhirkevich.compottie.avp.animator.PathAnimator
import io.github.alexzhirkevich.compottie.avp.animator.endTime
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized

Expand Down Expand Up @@ -94,6 +95,9 @@ internal class AnimatedImageVector internal constructor(
*/
internal val genId: Int = generateImageVectorId(),
) {

internal val duration = root.duration

/**
* Builder used to construct a Vector graphic tree.
* This is useful for caching the result of expensive operations used to construct
Expand Down Expand Up @@ -370,7 +374,9 @@ internal class AnimatedImageVector internal constructor(
}
}

internal sealed class AnimatedVectorNode
internal sealed class AnimatedVectorNode {
abstract val duration : Float
}

/**
* Defines a group of paths or subgroups, plus transformation information.
Expand Down Expand Up @@ -430,8 +436,12 @@ internal class AnimatedVectorGroup internal constructor(
* Child Vector nodes that are part of this group, this can contain
* paths or other groups
*/
val children: List<AnimatedVectorNode> = emptyList()
) : AnimatedVectorNode()
val children: List<AnimatedVectorNode> = emptyList(),
) : AnimatedVectorNode() {

override val duration: Float =
children.maxOf(AnimatedVectorNode::duration)
}

/**
* Leaf node of a Vector graphics tree. This specifies a path shape and parameters
Expand Down Expand Up @@ -512,8 +522,26 @@ internal class AnimatedVectorPath internal constructor(
* Specifies the offset of the trim region (allows showed region to include the start and end),
* in the range from 0 to 1. The default is 0.
*/
val trimPathOffset: FloatAnimator
) : AnimatedVectorNode()
val trimPathOffset: FloatAnimator,
) : AnimatedVectorNode() {

init {
pathData.fillType = pathFillType
}

override val duration: Float = maxOf(
pathData.endTime,
fill?.endTime ?: 0f,
fillAlpha.endTime,
stroke?.endTime ?: 0f,
strokeAlpha.endTime,
strokeLineWidth.endTime,
strokeLineMiter.endTime,
trimPathStart.endTime,
trimPathEnd.endTime,
trimPathOffset.endTime
)
}

private fun <T> ArrayList<T>.push(value: T): Boolean = add(value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ internal class AnimatedPathComponent(
override fun DrawScope.draw(time: Float) {
updateFill(time)
updateStroke(time)
updateRenderPath(time,vector.pathData.animate(time))
updateRenderPath(time, vector.pathData.animate(time))

if (vector.fill != null){
drawContext.canvas.drawPath(renderPath, fillPaint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
package io.github.alexzhirkevich.compottie.avp

import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.drawscope.DrawScope
Expand All @@ -17,8 +19,15 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import io.github.alexzhirkevich.compottie.avp.animator.ObjectAnimator
import io.github.alexzhirkevich.compottie.avp.xml.drawable
import io.github.alexzhirkevich.compottie.avp.xml.parseAnimationTargets
import io.github.alexzhirkevich.compottie.avp.xml.parseObjectAnimators
import io.github.alexzhirkevich.compottie.avp.xml.toAnimatedImageVector
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.InternalResourceApi
Expand All @@ -32,25 +41,26 @@ import kotlin.math.roundToInt
@Composable
public fun rememberAnimatedVectorPainter(
resource : DrawableResource,
animations : List<ObjectAnimator<*,*>>,
isAtAnd : Boolean
) : Painter {

val environment = LocalComposeEnvironment.current.rememberEnvironment()
val path = resource.getResourceItemByEnvironment(environment).path
val path = resource.getResourceItemByEnvironment(environment).path

val density = LocalDensity.current
val resourceReader = LocalResourceReader.current

val painter by produceState<Painter>(EmptyPainter, density) {
val image = resourceReader.read(path).toXmlElement()
val vector = image.toAnimatedImageVector(density, emptyMap())
value = AnimatedVectorPainter(
val vector = produceState<AnimatedImageVector?>(null, density) {
value = loadXmlVector(density, path, resourceReader::read)
}

return remember(vector, density) {
AnimatedVectorPainter(
vector,
density,
{ 0f }
)
}
return painter
}

private object EmptyPainter : Painter() {
Expand All @@ -63,45 +73,80 @@ private object EmptyPainter : Painter() {


private class AnimatedVectorPainter(
private val vector: AnimatedImageVector,
private val vector: State<AnimatedImageVector?>,
density : Density,
time : () -> Float
) : Painter() {

override val intrinsicSize: Size = density.run {
DpSize(
vector.defaultWidth,
vector.defaultHeight
).toSize()
override val intrinsicSize: Size by derivedStateOf {
density.run {
DpSize(
vector.value?.defaultWidth ?: 1.dp,
vector.value?.defaultHeight ?: 1.dp,
).toSize()
}
}

private val viewportSize = IntSize(
vector.viewportWidth.roundToInt(),
vector.viewportHeight.roundToInt()
)
private val viewportSize by derivedStateOf {
IntSize(
vector.value?.viewportWidth?.roundToInt() ?: 1,
vector.value?.viewportHeight?.roundToInt() ?: 1
)
}

private val root = AnimatedGroupComponent(vector.root)
private val root by derivedStateOf {
vector.value?.root?.let(::AnimatedGroupComponent)
}

private val time by derivedStateOf { time() }

override fun DrawScope.onDraw() {
val scale = ContentScale.FillBounds.computeScaleFactor(intrinsicSize, size)

val offset = Alignment.Center.align(
size = viewportSize,
space = IntSize(
size.width.roundToInt(),
size.height.roundToInt()
),
layoutDirection = layoutDirection
)

scale(scale.scaleX, scale.scaleY) {
translate(offset.x.toFloat(), offset.y.toFloat()) {
root.run {
root?.run {
val scale = ContentScale.FillBounds.computeScaleFactor(intrinsicSize, size)

val offset = Alignment.Center.align(
size = viewportSize,
space = IntSize(
size.width.roundToInt(),
size.height.roundToInt()
),
layoutDirection = layoutDirection
)

scale(scale.scaleX, scale.scaleY) {
translate(offset.x.toFloat(), offset.y.toFloat()) {
draw(time)
}
}
}
}
}

private suspend fun loadXmlVector(
density: Density,
resource : String,
readBytes : suspend (String) -> ByteArray,
) : AnimatedImageVector {

val xml = readBytes(resource).toXmlElement()
val drawable = readBytes(xml.drawable())

val animators = coroutineScope {
xml.parseAnimationTargets()
.map {
async {
it.name to readBytes(it.animation)
.toXmlElement()
.parseObjectAnimators()
.associateBy(ObjectAnimator<*,*>::property)
}
}
.awaitAll()
.toMap()
}

return drawable.toXmlElement().toAnimatedImageVector(
density = density,
animators = animators
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.alexzhirkevich.compottie.avp.animator

import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.ui.util.lerp
import io.github.alexzhirkevich.compottie.avp.xml.AnimatedVectorProperty

Expand All @@ -14,13 +15,17 @@ public fun FloatAnimator(
property: AnimatedVectorProperty<FloatAnimator>,
delay : Float = 0f,
interpolator: Easing = LinearEasing,
repeatCount: Int = 1,
repeatMode: RepeatMode = RepeatMode.Restart
) : FloatAnimator = DynamicFloatAnimator(
duration = duration,
valueFrom = valueFrom,
valueTo = valueTo,
delay = delay,
easing = interpolator,
property = property
property = property,
repeatCount = repeatCount,
repeatMode = repeatMode
)

internal class DynamicFloatAnimator(
Expand All @@ -29,7 +34,9 @@ internal class DynamicFloatAnimator(
override val valueTo: Float,
override val delay: Float,
override val easing: Easing,
override val property: AnimatedVectorProperty<FloatAnimator>
override val property: AnimatedVectorProperty<FloatAnimator>,
override val repeatCount: Int,
override val repeatMode: RepeatMode
) : FloatAnimator() {

override fun interpolate(progress: Float): Float {
Expand All @@ -39,13 +46,16 @@ internal class DynamicFloatAnimator(

internal class StaticFloatAnimator(
private val value : Float,
override val property: AnimatedVectorProperty<FloatAnimator>
override val property: AnimatedVectorProperty<FloatAnimator>,
) : FloatAnimator() {

override val delay: Float get() = 0f
override val duration: Float get() = 0f
override val valueFrom: Float get() = value
override val valueTo: Float get() = value
override val easing: Easing get() = LinearEasing
override val repeatCount: Int get() = 1
override val repeatMode: RepeatMode get() = RepeatMode.Restart

override fun interpolate(progress: Float): Float {
return value
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.alexzhirkevich.compottie.avp.animator

import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.RepeatMode
import io.github.alexzhirkevich.compottie.avp.xml.AnimatedVectorProperty

public sealed class ObjectAnimator<T, R> {
Expand All @@ -17,16 +18,33 @@ public sealed class ObjectAnimator<T, R> {

public abstract val easing: Easing

public abstract val repeatCount : Int

public abstract val repeatMode : RepeatMode

protected abstract fun interpolate(progress: Float): R

internal fun animate(time: Float): R {

val progress = if (time < delay) {
0f
} else {
((time - delay) / duration).coerceIn(0f, 1f)
val cycle = ((time - delay) / duration)

if (cycle > repeatCount) {
1f
} else {
(cycle - cycle.toInt()).coerceIn(0f, 1f).let {
if (repeatMode == RepeatMode.Reverse && cycle.toInt() % 2 == 0){
1f - it
} else it
}
}
}

return interpolate(easing.transform(progress))
}
}
}

internal val ObjectAnimator<*,*>.endTime : Float
get() = delay + duration * repeatCount
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.alexzhirkevich.compottie.avp.animator

import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shader
import io.github.alexzhirkevich.compottie.avp.xml.AnimatedVectorProperty
Expand All @@ -21,7 +22,9 @@ internal class DynamicPaintAnimator(
override val valueTo: ColorData,
override val delay: Float,
override val easing: Easing,
override val property: AnimatedVectorProperty<PaintAnimator>
override val property: AnimatedVectorProperty<PaintAnimator>,
override val repeatCount: Int,
override val repeatMode: RepeatMode
) : PaintAnimator() {

private val paintData = PaintData()
Expand Down Expand Up @@ -57,6 +60,8 @@ internal class StaticPaintAnimator(
override val valueFrom: ColorData get() = value
override val valueTo: ColorData get() = value
override val easing: Easing get() = LinearEasing
override val repeatCount: Int get() = 1
override val repeatMode: RepeatMode get() = RepeatMode.Restart

private val paint = PaintData()

Expand Down
Loading

0 comments on commit 314efaa

Please sign in to comment.