Add year view with Hero Zoom transition

- CalendarViewModel: add isYearView, yearViewProgress, yearViewYear state
  with toggleYearView(), selectMonthFromYearView(), year navigation methods
- YearGridView: new 4x3 month grid with year navigation header
- MonthHeader: onClick now toggles year view, added "今天" button
- CalendarMonthView: overlay year view with graphicsLayer anchor-based
  scale transition, hide BottomCard during year view
This commit is contained in:
meyou 2026-05-16 16:29:17 +08:00
parent 16b73c4373
commit 995693cb5d

View File

@ -0,0 +1,154 @@
package plus.rua.project.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* 年度网格视图显示 4×3 月份网格支持年份切换
*
* @param year 显示的年份
* @param selectedMonth 当前选中月份1-12
* @param onMonthClick 月份点击回调
* @param onYearChange 年份切换回调
* @param modifier 外部布局修饰符
*/
@Composable
fun YearGridView(
year: Int,
selectedMonth: Int,
onMonthClick: (Int) -> Unit,
onYearChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 年份导航行
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.clip(CircleShape)
.clickable { onYearChange(year - 1) }
.padding(horizontal = 16.dp, vertical = 8.dp)
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "${year}",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.clip(CircleShape)
.clickable { onYearChange(year + 1) }
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
// 4×3 月份网格
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.SpaceEvenly
) {
(0 until 4).forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
(0 until 3).forEach { col ->
val month = row * 3 + col + 1
MonthCell(
month = month,
isSelected = month == selectedMonth,
onClick = { onMonthClick(month) },
modifier = Modifier.weight(1f)
)
}
}
}
}
}
}
/**
* 年视图中的月份单元格
*/
@Composable
private fun MonthCell(
month: Int,
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val contentColor = if (isSelected) {
MaterialTheme.colorScheme.onPrimary
} else {
MaterialTheme.colorScheme.onSurface
}
val bgColor = MaterialTheme.colorScheme.primary
Box(
modifier = modifier
.aspectRatio(1f)
.padding(6.dp)
.clip(CircleShape)
.drawBehind {
if (isSelected) {
drawCircle(
color = bgColor,
radius = size.minDimension / 2f,
center = Offset(size.width / 2f, size.height / 2f)
)
}
}
.clickable(onClick = onClick),
contentAlignment = Alignment.Center
) {
Text(
text = "${month}",
color = contentColor,
fontSize = 16.sp,
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
textAlign = TextAlign.Center
)
}
}