Add circular reveal animation to DayCell with animated state transitions
Replace hard-switched background/border modifiers with updateTransition-based animated properties: revealProgress for circular indicator radius, contentColor and selectedColor via animateColor, borderAlpha for today stroke. Use drawBehind for custom circle drawing with tween(250, FastOutSlowInEasing) spec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2411416eb7
commit
8913e5ff0d
@ -1,8 +1,10 @@
|
|||||||
package plus.rua.project.ui
|
package plus.rua.project.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.animation.animateColor
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
@ -10,13 +12,22 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
enum class DayCellState {
|
||||||
|
NORMAL, OTHER_MONTH, TODAY, SELECTED, SELECTED_TODAY
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个日期单元格,显示日期数字并支持选中/今天/非当月状态。
|
* 单个日期单元格,显示日期数字并支持选中/今天/非当月状态。
|
||||||
*
|
*
|
||||||
@ -36,26 +47,84 @@ fun DayCell(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val contentColor = when {
|
val currentState = when {
|
||||||
isSelected && isToday -> MaterialTheme.colorScheme.onPrimaryContainer
|
isSelected && isToday -> DayCellState.SELECTED_TODAY
|
||||||
isSelected -> MaterialTheme.colorScheme.onPrimary
|
isSelected -> DayCellState.SELECTED
|
||||||
isToday -> MaterialTheme.colorScheme.primary
|
isToday -> DayCellState.TODAY
|
||||||
!isCurrentMonth -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
!isCurrentMonth -> DayCellState.OTHER_MONTH
|
||||||
else -> MaterialTheme.colorScheme.onSurface
|
else -> DayCellState.NORMAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val transition = updateTransition(targetState = currentState, label = "dayCell")
|
||||||
|
|
||||||
|
val revealProgress by transition.animateFloat(
|
||||||
|
transitionSpec = { tween(250, easing = FastOutSlowInEasing) },
|
||||||
|
label = "revealProgress"
|
||||||
|
) { state ->
|
||||||
|
when (state) {
|
||||||
|
DayCellState.SELECTED, DayCellState.SELECTED_TODAY -> 1f
|
||||||
|
else -> 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentColor by transition.animateColor(
|
||||||
|
transitionSpec = { tween(250, easing = FastOutSlowInEasing) },
|
||||||
|
label = "contentColor"
|
||||||
|
) { state ->
|
||||||
|
when (state) {
|
||||||
|
DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
DayCellState.SELECTED -> MaterialTheme.colorScheme.onPrimary
|
||||||
|
DayCellState.TODAY -> MaterialTheme.colorScheme.primary
|
||||||
|
DayCellState.OTHER_MONTH -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||||
|
DayCellState.NORMAL -> MaterialTheme.colorScheme.onSurface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedColor by transition.animateColor(
|
||||||
|
transitionSpec = { tween(250, easing = FastOutSlowInEasing) },
|
||||||
|
label = "selectedColor"
|
||||||
|
) { state ->
|
||||||
|
when (state) {
|
||||||
|
DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.primaryContainer
|
||||||
|
DayCellState.SELECTED -> MaterialTheme.colorScheme.primary
|
||||||
|
else -> Color.Transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val borderAlpha by transition.animateFloat(
|
||||||
|
transitionSpec = { tween(250, easing = FastOutSlowInEasing) },
|
||||||
|
label = "borderAlpha"
|
||||||
|
) { state ->
|
||||||
|
when (state) {
|
||||||
|
DayCellState.TODAY -> 1.5f
|
||||||
|
else -> 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val todayBorderColor = MaterialTheme.colorScheme.primary
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.then(
|
.drawBehind {
|
||||||
when {
|
if (revealProgress > 0f) {
|
||||||
isSelected && isToday -> Modifier.background(MaterialTheme.colorScheme.primaryContainer)
|
val maxRadius = size.minDimension / 2f
|
||||||
isSelected -> Modifier.background(MaterialTheme.colorScheme.primary)
|
drawCircle(
|
||||||
isToday -> Modifier.border(BorderStroke(1.5.dp, MaterialTheme.colorScheme.primary), CircleShape)
|
color = selectedColor,
|
||||||
else -> Modifier
|
radius = revealProgress * maxRadius,
|
||||||
|
center = Offset(size.width / 2f, size.height / 2f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
if (borderAlpha > 0f) {
|
||||||
|
drawCircle(
|
||||||
|
color = todayBorderColor.copy(alpha = borderAlpha.coerceAtMost(1f)),
|
||||||
|
radius = size.minDimension / 2f,
|
||||||
|
center = Offset(size.width / 2f, size.height / 2f),
|
||||||
|
style = Stroke(width = borderAlpha.coerceAtMost(1.5f) * 1.5.dp.toPx())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
.clickable(onClick = onClick),
|
.clickable(onClick = onClick),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user