修复年视图布局和动画问题

- 年视图每个小月历添加星期行头部
- 日期列用 weight(1f) 对齐,去掉 CircleShape 裁剪
- 取消前一个动画 Job 防止快速点击时动画丢失
This commit is contained in:
meyou 2026-05-16 16:49:10 +08:00
parent 8dad07c0a0
commit 142d0c235a
2 changed files with 38 additions and 15 deletions

View File

@ -8,6 +8,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.DatePeriod import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
@ -58,6 +59,8 @@ class CalendarViewModel(
private val _collapseAnimatable = Animatable(0f) private val _collapseAnimatable = Animatable(0f)
val collapseProgress: Float get() = _collapseAnimatable.value val collapseProgress: Float get() = _collapseAnimatable.value
private var yearViewJob: Job? = null
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口 @Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
val currentMonth: Int get() = selectedDate.month.number val currentMonth: Int get() = selectedDate.month.number
@ -87,7 +90,8 @@ class CalendarViewModel(
*/ */
fun toggleYearView() { fun toggleYearView() {
if (isCollapsed) return if (isCollapsed) return
coroutineScope.launch { yearViewJob?.cancel()
yearViewJob = coroutineScope.launch {
if (isYearView) { if (isYearView) {
_yearViewAnimatable.animateTo( _yearViewAnimatable.animateTo(
0f, tween(400, easing = FastOutSlowInEasing) 0f, tween(400, easing = FastOutSlowInEasing)
@ -112,7 +116,8 @@ class CalendarViewModel(
val date = if (yearViewYear == today.year && today.month.number == month) today val date = if (yearViewYear == today.year && today.month.number == month) today
else LocalDate(yearViewYear, month, 1) else LocalDate(yearViewYear, month, 1)
selectedDate = date selectedDate = date
coroutineScope.launch { yearViewJob?.cancel()
yearViewJob = coroutineScope.launch {
_yearViewAnimatable.animateTo( _yearViewAnimatable.animateTo(
0f, tween(400, easing = FastOutSlowInEasing) 0f, tween(400, easing = FastOutSlowInEasing)
) )

View File

@ -27,16 +27,18 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus import kotlinx.datetime.minus
import kotlinx.datetime.number import kotlinx.datetime.number
import kotlinx.datetime.plus import kotlinx.datetime.plus
import kotlinx.datetime.todayIn
private val WEEKDAY_LABELS = listOf("", "", "", "", "", "", "")
/** /**
* 年度网格视图显示 4×3 精简月历网格支持年份切换 * 年度网格视图显示 4×3 精简月历网格支持年份切换
* *
* 每格显示一个精简版月历月份标题 + 日期数字网格 * 每格显示一个精简版月历月份标题 + 星期行 + 日期数字网格
* 选中月份高亮点击进入该月 * 选中月份高亮点击进入该月
* *
* @param year 显示的年份 * @param year 显示的年份
* @param selectedMonth 当前选中月份1-12 * @param selectedMonth 当前选中月份1-12
* @param today 今天的日期
* @param onMonthClick 月份点击回调 * @param onMonthClick 月份点击回调
* @param onYearChange 年份切换回调 * @param onYearChange 年份切换回调
* @param modifier 外部布局修饰符 * @param modifier 外部布局修饰符
@ -95,7 +97,7 @@ fun YearGridView(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
.padding(horizontal = 8.dp), .padding(horizontal = 4.dp),
verticalArrangement = Arrangement.SpaceEvenly verticalArrangement = Arrangement.SpaceEvenly
) { ) {
(0 until 4).forEach { row -> (0 until 4).forEach { row ->
@ -121,7 +123,7 @@ fun YearGridView(
} }
/** /**
* 精简版月历月份标题 + 日期数字网格 * 精简版月历月份标题 + 星期行 + 日期数字网格
*/ */
@Composable @Composable
private fun MiniMonth( private fun MiniMonth(
@ -138,6 +140,7 @@ private fun MiniMonth(
} else { } else {
MaterialTheme.colorScheme.onSurface MaterialTheme.colorScheme.onSurface
} }
val weekdayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
val dayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) val dayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
val otherMonthColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) val otherMonthColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)
val todayBgColor = MaterialTheme.colorScheme.primary val todayBgColor = MaterialTheme.colorScheme.primary
@ -145,19 +148,33 @@ private fun MiniMonth(
Column( Column(
modifier = modifier modifier = modifier
.padding(2.dp) .padding(2.dp)
.clip(CircleShape)
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(vertical = 4.dp), .padding(vertical = 2.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// 月份标题 // 月份标题
Text( Text(
text = "${month}", text = "${month}",
color = titleColor, color = titleColor,
fontSize = 10.sp, fontSize = 9.sp,
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
// 星期行
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
WEEKDAY_LABELS.forEach { label ->
Text(
text = label,
color = weekdayColor,
fontSize = 6.sp,
textAlign = TextAlign.Center,
modifier = Modifier.weight(1f)
)
}
}
// 日期网格 // 日期网格
days.chunked(7).forEach { week -> days.chunked(7).forEach { week ->
Row( Row(
@ -165,14 +182,15 @@ private fun MiniMonth(
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.SpaceEvenly
) { ) {
week.forEach { dayData -> week.forEach { dayData ->
val isToday = dayData.date == today val isToday = dayData.date == today && dayData.isCurrentMonth
val color = when { val color = when {
!dayData.isCurrentMonth -> otherMonthColor !dayData.isCurrentMonth -> otherMonthColor
isToday -> MaterialTheme.colorScheme.primary isToday -> MaterialTheme.colorScheme.onPrimary
else -> dayColor else -> dayColor
} }
Box( Box(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center,
modifier = Modifier.weight(1f)
) { ) {
if (isToday) { if (isToday) {
Box( Box(
@ -189,10 +207,10 @@ private fun MiniMonth(
} }
Text( Text(
text = if (dayData.isCurrentMonth) dayData.date.day.toString() else "", text = if (dayData.isCurrentMonth) dayData.date.day.toString() else "",
color = if (isToday) MaterialTheme.colorScheme.onPrimary else color, color = color,
fontSize = 7.sp, fontSize = 6.sp,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
lineHeight = 10.sp lineHeight = 9.sp
) )
} }
} }