Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kazemihabib/ab886094b33907764a04eee63b0d257e to your computer and use it in GitHub Desktop.
Save kazemihabib/ab886094b33907764a04eee63b0d257e to your computer and use it in GitHub Desktop.
Android Jetpack Compose caret animation
/*
* Copyright 2020 Prat Tana. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.animation.*
import androidx.compose.Composable
import androidx.compose.remember
import androidx.ui.animation.animate
import androidx.ui.core.Modifier
import androidx.ui.foundation.Box
import androidx.ui.foundation.Canvas
import androidx.ui.foundation.Clickable
import androidx.ui.foundation.ContentGravity
import androidx.ui.graphics.*
import androidx.ui.layout.Container
import androidx.ui.layout.LayoutSize
import androidx.ui.material.MaterialTheme
import androidx.ui.material.ripple.Ripple
import androidx.ui.unit.dp
const val CaretAnimationDuration = 200
private val StrokeWidth = 3.dp
private val CaretSize = 20.dp
private val YOffset = FloatPropKey()
private val Fraction = FloatPropKey()
enum class CaretState {
Up,
Down
}
@Composable
fun ExpandCollapseButton(
buttonState: CaretState,
onStateChange: ((CaretState) -> Unit)?,
modifier: Modifier = Modifier.None,
animationStiffness: Float = Spring.StiffnessMedium,
color: Color = MaterialTheme.colors().secondary
) {
Container(modifier) {
Ripple(bounded = false) {
Clickable(onClick = {
onStateChange
?.let {
it(
if (buttonState == CaretState.Down)
CaretState.Up
else
CaretState.Down
)
}
}) {
Box(
modifier = LayoutSize(28.dp, 28.dp),
gravity = ContentGravity.Center
) {
DrawCaret(buttonState, color, animationStiffness)
}
}
}
}
}
private fun lerp(start: Float, stop: Float, fraction: Float): Float =
(1 - fraction) * start + fraction * stop
@Composable
private fun DrawCaret(
caretState: CaretState,
activeColor: Color,
animationStiffness: Float
) {
val strokeWidthDp = StrokeWidth
val paint = remember {
Paint().apply {
isAntiAlias = true
strokeCap = StrokeCap.round
color = activeColor
style = PaintingStyle.stroke
}
}
val animationBuilder = remember {
PhysicsBuilder<Float>(stiffness = animationStiffness)
}
val t = animate(if (caretState == CaretState.Up) 1f else 0f, animationBuilder)
val animatedYOffset = lerp(0f, 1f, t)
val fraction = lerp(-1f, 1f, t)
Canvas(
modifier = LayoutSize(CaretSize)
) {
val strokeWidth = strokeWidthDp.toPx().value
paint.strokeWidth = strokeWidth
val height = size.height.value / 4
val x = size.height.value / 4
val yOffset = (size.height.value - height) / 2
val path = Path()
path.moveTo(x, yOffset + height * animatedYOffset)
path.relativeLineTo(x, -height * fraction)
path.relativeLineTo(x, height * fraction)
drawPath(path, paint)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment