Last active
September 18, 2020 04:08
-
-
Save alexjlockwood/536dcf7e781b0defd6e17d0d5ffe345d to your computer and use it in GitHub Desktop.
Example implementation of a CircularProgressIndicator using Jetpack Compose https://twitter.com/alexjlockwood/status/1300599202448199681
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.alexjlockwood.circularprogressindicator | |
import android.os.Bundle | |
import android.view.animation.PathInterpolator | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.compose.animation.core.* | |
import androidx.compose.animation.transition | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.layout.fillMaxHeight | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.material.MaterialTheme | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.SolidColor | |
import androidx.compose.ui.graphics.StrokeCap | |
import androidx.compose.ui.graphics.StrokeJoin | |
import androidx.compose.ui.graphics.vector.VectorAssetBuilder | |
import androidx.compose.ui.graphics.vector.addPathNodes | |
import androidx.compose.ui.graphics.vector.group | |
import androidx.compose.ui.platform.setContent | |
import androidx.compose.ui.unit.dp | |
class MainActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContent { | |
MaterialTheme { | |
CircularProgressIndicator(Modifier.fillMaxWidth().fillMaxHeight()) | |
} | |
} | |
} | |
} | |
@Composable | |
private fun CircularProgressIndicator(modifier: Modifier = Modifier) { | |
val state = transition( | |
definition = CircularProgressIndicatorTransition, | |
initState = 0, | |
toState = 1, | |
) | |
Image( | |
asset = VectorAssetBuilder( | |
defaultWidth = 48.dp, | |
defaultHeight = 48.dp, | |
viewportWidth = 48f, | |
viewportHeight = 48f, | |
).group( | |
translationX = 24f, | |
translationY = 24f, | |
rotate = state[RotationProp], | |
) { | |
addPath( | |
pathData = remember { addPathNodes("m 0 -18 a 18 18 0 1 1 0 36 a 18 18 0 1 1 0 -36") }, | |
stroke = SolidColor(MaterialTheme.colors.primary), | |
strokeLineCap = StrokeCap.Square, | |
strokeLineJoin = StrokeJoin.Miter, | |
strokeLineWidth = 4f, | |
trimPathStart = state[TrimPathStartProp], | |
trimPathEnd = state[TrimPathEndProp], | |
trimPathOffset = state[TrimPathOffsetProp], | |
) | |
}.build(), | |
modifier = modifier, | |
) | |
} | |
private val TrimPathStartProp = FloatPropKey() | |
private val TrimPathEndProp = FloatPropKey() | |
private val TrimPathOffsetProp = FloatPropKey() | |
private val RotationProp = FloatPropKey() | |
private val TrimPathStartEasing = PathEasing(android.graphics.Path().apply { | |
lineTo(0.5f, 0f) | |
cubicTo(0.7f, 0f, 0.6f, 1f, 1f, 1f) | |
}) | |
private val TrimPathEndEasing = PathEasing(android.graphics.Path().apply { | |
cubicTo(0.2f, 0f, 0.1f, 1f, 0.5f, 0.96f) | |
cubicTo(0.96f, 0.96f, 1f, 1f, 1f, 1f) | |
}) | |
private class PathEasing(path: android.graphics.Path) : Easing { | |
private val pathInterpolator = PathInterpolator(path) | |
override fun invoke(fraction: Float) = pathInterpolator.getInterpolation(fraction) | |
} | |
private val CircularProgressIndicatorTransition = transitionDefinition<Int> { | |
state(0) { | |
this[TrimPathStartProp] = 0f | |
this[TrimPathEndProp] = 0.03f | |
this[TrimPathOffsetProp] = 0f | |
this[RotationProp] = 0f | |
} | |
state(1) { | |
this[TrimPathStartProp] = 0.75f | |
this[TrimPathEndProp] = 0.78f | |
this[TrimPathOffsetProp] = 0.25f | |
this[RotationProp] = 720f | |
} | |
transition(fromState = 0, toState = 1) { | |
TrimPathStartProp using repeatable( | |
iterations = AnimationConstants.Infinite, | |
animation = tween(durationMillis = 1333, easing = TrimPathStartEasing) | |
) | |
TrimPathEndProp using repeatable( | |
iterations = AnimationConstants.Infinite, | |
animation = tween(durationMillis = 1333, easing = TrimPathEndEasing) | |
) | |
TrimPathOffsetProp using repeatable( | |
iterations = AnimationConstants.Infinite, | |
animation = tween(durationMillis = 1333, easing = LinearEasing) | |
) | |
RotationProp using repeatable( | |
iterations = AnimationConstants.Infinite, | |
animation = tween(durationMillis = 4444, easing = LinearEasing) | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment