Skip to content

Instantly share code, notes, and snippets.

@L10n42
Created July 5, 2024 09:36
Show Gist options
  • Save L10n42/5625e88371b978a8874727d668425b95 to your computer and use it in GitHub Desktop.
Save L10n42/5625e88371b978a8874727d668425b95 to your computer and use it in GitHub Desktop.
DNA Helix Animation in Jetpack Compose
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.sin
/**
* A Composable function that draws an animated DNA Helix.
*
* This function creates a horizontal DNA Helix animation that spans the canvas width,
* using its height as the diameter for the helix.
*
* @param modifier Modifier to be applied to the Canvas
* @param firstColor Color of the first set of points
* @param secondColor Color of the second set of points
* @param pointSize Diameter of the points
* @param lineWidth Width of the lines connecting the points
* @param spacing Distance between consecutive points
* @param shifting Horizontal shift for the points
* @param curvature Curvature of the helix
* @param cycleDuration Duration of one animation cycle in milliseconds
* @param lineBrush Function to define the brush for the lines
*/
@Composable
fun DNAHelix(
modifier: Modifier,
firstColor: Color,
secondColor: Color,
pointSize: Dp = 5.dp,
lineWidth: Dp = 1.5.dp,
spacing: Dp = 10.dp,
shifting: Dp = 0.dp,
curvature: Float = 16f,
cycleDuration: Int = 3000,
lineBrush: (firstPoint: Offset, secondPoint: Offset) -> Brush = { fp, sp ->
Brush.linearGradient(
colors = listOf(firstColor, secondColor),
start = fp,
end = sp
)
}
) {
val helixTransition = rememberInfiniteTransition()
val animatedAngle by helixTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = cycleDuration, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
Canvas(modifier) {
val spacingPx = spacing.toPx()
val pointsCount = (size.width / spacingPx).toInt()
val helixRadius = size.height / 2
val pointRadiusPx = pointSize.toPx() / 2
val lineWidthPx = lineWidth.toPx()
val shiftingPx = shifting.toPx()
for (i in 1 until pointsCount) {
val currentAngle = (animatedAngle + i * curvature) % 360
val xOffset = i * spacingPx
val firstPoint = calculateCoordinates(currentAngle, helixRadius, xOffset - shiftingPx, helixRadius)
val secondPoint = calculateCoordinates(currentAngle + 180, helixRadius, xOffset + shiftingPx, helixRadius)
drawLine(
brush = lineBrush(firstPoint, secondPoint),
strokeWidth = lineWidthPx,
start = firstPoint,
end = secondPoint
)
drawCircle(
color = firstColor,
radius = pointRadiusPx,
center = firstPoint
)
drawCircle(
color = secondColor,
radius = pointRadiusPx,
center = secondPoint
)
}
}
}
private fun calculateCoordinates(angle: Float, radius: Float, centerX: Float, centerY: Float): Offset {
val y = centerY + radius * sin(Math.toRadians(angle.toDouble())).toFloat()
return Offset(centerX, y)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment