Skip to content

Commit

Permalink
Create layout modifier
Browse files Browse the repository at this point in the history
  • Loading branch information
gpeal committed Dec 29, 2023
1 parent a44c4ed commit c33f220
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import android.graphics.Typeface
import androidx.annotation.FloatRange
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -20,7 +18,6 @@ import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.ScaleFactor
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.AsyncUpdates
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieDrawable
Expand Down Expand Up @@ -100,8 +97,7 @@ fun LottieAnimation(
val bounds = composition.bounds
Canvas(
modifier = modifier
.width((bounds.width() / dpScale).dp)
.aspectRatio(bounds.width() / bounds.height().toFloat())
.lottieSize((bounds.width() * dpScale).roundToInt(), (bounds.height() * dpScale).roundToInt())
) {
drawIntoCanvas { canvas ->
val compositionSize = Size(bounds.width().toFloat(), bounds.height().toFloat())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.airbnb.lottie.compose

import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.constrain

@Stable
internal fun Modifier.lottieSize(width: Int, height: Int) = this.then(LottieAnimationSizeElement(width, height))

internal data class LottieAnimationSizeElement(
val width: Int,
val height: Int,
) : ModifierNodeElement<LottieAnimationSizeNode>() {
override fun create(): LottieAnimationSizeNode {
return LottieAnimationSizeNode(width, height)
}

override fun update(node: LottieAnimationSizeNode) {
node.width = width
node.height = height
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LottieAnimationSizeElement) return false

if (width != other.width) return false
if (height != other.height) return false
return true
}

override fun hashCode(): Int {
var result = width.hashCode()
result = 31 * result + height.hashCode()
return result
}
}


internal class LottieAnimationSizeNode(
var width: Int,
var height: Int,
) : Modifier.Node(), LayoutModifierNode {
override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult {
val constrainedSize = constraints.constrain(IntSize(width, height))
val wrappedConstraints = when {
// There is enough space for the width and height so we don't need to constrain.
constrainedSize.width >= width && constrainedSize.height >= height -> Constraints(
minWidth = width,
maxWidth = width,
minHeight = height,
maxHeight = height,
)
// There is enough space in the width but not height so we need to constrain the height.
constrainedSize.width >= width -> Constraints(
minWidth = constrainedSize.height * width / height,
maxWidth = constrainedSize.height * width / height,
minHeight = constrainedSize.height,
maxHeight = constrainedSize.height,
)
// There is enough space in the height but not width so we need to constrain the width.
constrainedSize.height >= height -> Constraints(
minWidth = constrainedSize.width,
maxWidth = constrainedSize.width,
minHeight = constrainedSize.width * height / width,
maxHeight = constrainedSize.width * height / width,
)
else -> {
val constraintAspectRation = constrainedSize.height / constrainedSize.width.toFloat()
val aspectRatio = height / width.toFloat()
if (constraintAspectRation >= aspectRatio) {
Constraints(
minWidth = constrainedSize.width,
maxWidth = constrainedSize.width,
minHeight = constrainedSize.width * height / width,
maxHeight = constrainedSize.width * height / width,
)
} else {
Constraints(
minWidth = constrainedSize.height * width / height,
maxWidth = constrainedSize.height * width / height,
minHeight = constrainedSize.height,
maxHeight = constrainedSize.height,
)
}
}
}


val placeable = measurable.measure(wrappedConstraints)
return layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ class ComposeScaleTypesTestCase : SnapshotTestCase {
LottieAnimation(
composition = largeSquareComposition,
progress = { 1f },
contentScale = ContentScale.FillWidth,
renderMode = renderMode,
modifier = Modifier.fillMaxWidth(),
)
Expand All @@ -190,13 +189,12 @@ class ComposeScaleTypesTestCase : SnapshotTestCase {
snapshotComposable("Compose constrained size", "Row", renderHardwareAndSoftware = true) { renderMode ->
Row(
horizontalArrangement = Arrangement.spacedBy(24.dp),
modifier = Modifier
.height(128.dp),
modifier = Modifier.height(128.dp),
) {
val progress by animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
LottieAnimation(
composition = largeSquareComposition,
progress = { progress },
progress = { 1f },
renderMode = renderMode,
modifier = Modifier.fillMaxHeight(),
)
Text("Other content")
Expand Down

0 comments on commit c33f220

Please sign in to comment.