|
/* |
|
* 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) |
|
} |
|
} |