Created
October 26, 2020 05:10
-
-
Save Bruno125/b1a87d80805c91c105c5827310e716ad to your computer and use it in GitHub Desktop.
This file contains 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
import androidx.compose.animation.RectPropKey | |
import androidx.compose.animation.core.FloatPropKey | |
import androidx.compose.animation.core.keyframes | |
import androidx.compose.animation.core.transitionDefinition | |
import androidx.compose.animation.core.tween | |
import androidx.compose.animation.transition | |
import androidx.compose.foundation.Canvas | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.Text | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.layout.* | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.material.Surface | |
import androidx.compose.material.icons.Icons | |
import androidx.compose.material.icons.filled.ChatBubble | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.clip | |
import androidx.compose.ui.draw.drawOpacity | |
import androidx.compose.ui.draw.drawShadow | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.geometry.Radius | |
import androidx.compose.ui.geometry.Rect | |
import androidx.compose.ui.geometry.Size | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.platform.DensityAmbient | |
import androidx.compose.ui.unit.Dp | |
import androidx.compose.ui.unit.dp | |
import androidx.ui.tooling.preview.Preview | |
private val circleSize = 60.dp | |
private val expandedSize = 300.dp | |
enum class IndicatorState { Collapsed, Expanded } | |
data class AnimationProperties( | |
val size: RectPropKey, | |
val colors: List<Color>, | |
val colorSizes: List<RectPropKey>, | |
val elevation: FloatPropKey, | |
val contentAlpha: FloatPropKey | |
) | |
fun createProperties(colors: List<Color>): AnimationProperties { | |
return AnimationProperties( | |
size = RectPropKey("Container size"), | |
elevation = FloatPropKey("Elevation"), | |
colors = colors, | |
colorSizes = colors.mapIndexed { index, _ -> | |
RectPropKey("Animated color size $index") | |
}, | |
contentAlpha = FloatPropKey("Content alpha") | |
) | |
} | |
fun rect(w: Float, h: Float) = Rect(Offset(0), Size(w, h)) | |
fun colorsTransition( | |
properties: AnimationProperties, | |
width: Float, | |
height: Float, | |
) = transitionDefinition<IndicatorState> { | |
val zero = rect(0f, 0f) | |
val expanded = rect(width, height) | |
state(IndicatorState.Collapsed) { | |
this[properties.size] = zero | |
this[properties.elevation] = 0f | |
this[properties.contentAlpha] = 0f | |
properties.colorSizes.forEach { this[it] = zero } | |
} | |
state(IndicatorState.Expanded) { | |
this[properties.size] = expanded | |
this[properties.elevation] = 32f | |
this[properties.contentAlpha] = 1f | |
properties.colorSizes.forEach { this[it] = expanded } | |
} | |
val n = properties.colors.size | |
val breakpoints = (1..n+2).map { 150 * it } | |
val offset = 100 | |
transition(fromState = IndicatorState.Collapsed, toState = IndicatorState.Expanded) { | |
properties.elevation using keyframes { 0f at breakpoints[0] } | |
properties.size using keyframes { | |
rect(w = height, h = height) at breakpoints[0] | |
durationMillis = breakpoints[1] | |
} | |
properties.colorSizes.forEachIndexed { index, sizeProperty -> | |
sizeProperty using keyframes { | |
val start = index + 2 | |
var remaining = start | |
while(remaining > 0) { | |
val size = height / remaining | |
val time = breakpoints[start - remaining] - if(remaining != start) offset else 0 | |
rect(size, size) at time | |
remaining -= 1 | |
} | |
durationMillis = breakpoints[start] | |
} | |
} | |
properties.contentAlpha using tween( | |
delayMillis = breakpoints.last(), | |
durationMillis = 300 | |
) | |
} | |
} | |
@Composable | |
fun RainbowLayout( | |
modifier: Modifier = Modifier, | |
background: Color = Color.White, | |
animatedColors: List<Color> = emptyList(), | |
content: @Composable RowScope.()->Unit = { } | |
) { | |
val circleSizePx = with(DensityAmbient.current) { circleSize.toPx() } | |
val expandedSizePx = with(DensityAmbient.current) { expandedSize.toPx() } | |
val properties = remember { createProperties(animatedColors) } | |
val transition = transition( | |
definition = remember { colorsTransition(properties, expandedSizePx, circleSizePx) }, | |
initState = IndicatorState.Collapsed, | |
toState = IndicatorState.Expanded | |
) | |
val contentSize = transition[properties.size].size | |
val contentSizeW = with(DensityAmbient.current) { contentSize.width.toDp() } | |
val contentSizeH = with(DensityAmbient.current) { contentSize.height.toDp() } | |
val shape = RoundedCornerShape(50) | |
Box(modifier | |
.width(contentSizeW.coerceAtLeast(circleSize)) | |
.height(contentSizeH.coerceAtLeast(circleSize)) | |
.drawShadow(elevation = Dp(transition[properties.elevation]), shape = shape) | |
) { | |
Canvas(Modifier | |
.align(Alignment.Center) | |
.width(contentSizeW) | |
.height(contentSizeH) | |
.clip(shape) | |
.background(background) | |
) { | |
fun centerFor(rect: Size) = Offset(center.x - rect.width / 2, center.y - rect.height / 2) | |
fun radiusFor(rect: Size) = Radius(rect.height / 2) | |
animatedColors.forEachIndexed { i, color -> | |
val size = transition[properties.colorSizes[i]].size | |
drawRoundRect( | |
color = color, | |
topLeft = centerFor(size), | |
size = size, | |
radius = radiusFor(size) | |
) | |
} | |
} | |
val contentAlpha = transition[properties.contentAlpha] | |
if(contentAlpha != 0f) { | |
Row( | |
modifier = Modifier.fillMaxSize().drawOpacity(contentAlpha), | |
horizontalArrangement = Arrangement.Center, | |
verticalAlignment = Alignment.CenterVertically, | |
) { | |
content() | |
} | |
} | |
} | |
} | |
@Preview | |
@Composable | |
private fun RainbowLayoutPreview() { | |
Surface(Modifier.fillMaxSize(), color = Color.White) { | |
Column(horizontalAlignment = Alignment.CenterHorizontally) { | |
Spacer(Modifier.height(8.dp)) | |
RainbowLayout(animatedColors = listOf( | |
Color(0xFF820263), | |
Color(0xFFD90368), | |
Color(0xFFEADEDA), | |
)) { | |
Image(asset = Icons.Filled.ChatBubble) | |
Spacer(Modifier.width(16.dp)) | |
Text("Chat") | |
} | |
Spacer(Modifier.height(8.dp)) | |
RainbowLayout( | |
background = Color(0xFF2C2846), | |
animatedColors = listOf( | |
Color(0xFF3A726F), | |
Color(0xFF88B387), | |
Color.White | |
) | |
) { | |
Text("Test 1") | |
} | |
Spacer(Modifier.height(8.dp)) | |
RainbowLayout(animatedColors = listOf( | |
Color(0xFF684551), | |
Color(0xFFD5B0AC), | |
Color(0xFFCEA0AE), | |
)) { | |
Text("Test 2") | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment