Replace year view month cells with mini calendar grids
Each month in the 4x3 year grid now shows a compact calendar with day numbers, matching the iOS Calendar year view style. Today is highlighted with a filled circle. Selected month title uses primary color.
This commit is contained in:
parent
731a1bb6a1
commit
c996d026cc
@ -246,6 +246,7 @@ fun CalendarMonthView(
|
|||||||
YearGridView(
|
YearGridView(
|
||||||
year = viewModel.yearViewYear,
|
year = viewModel.yearViewYear,
|
||||||
selectedMonth = if (viewModel.yearViewYear == currentYear) currentMonth else 0,
|
selectedMonth = if (viewModel.yearViewYear == currentYear) currentMonth else 0,
|
||||||
|
today = today,
|
||||||
onMonthClick = { month ->
|
onMonthClick = { month ->
|
||||||
viewModel.selectMonthFromYearView(month)
|
viewModel.selectMonthFromYearView(month)
|
||||||
// 同步 CalendarPager 到目标月份
|
// 同步 CalendarPager 到目标月份
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -14,6 +12,7 @@ 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.remember
|
||||||
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
|
||||||
@ -23,9 +22,18 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlinx.datetime.DatePeriod
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.minus
|
||||||
|
import kotlinx.datetime.number
|
||||||
|
import kotlinx.datetime.plus
|
||||||
|
import kotlinx.datetime.todayIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年度网格视图,显示 4×3 月份网格,支持年份切换。
|
* 年度网格视图,显示 4×3 精简月历网格,支持年份切换。
|
||||||
|
*
|
||||||
|
* 每格显示一个精简版月历(月份标题 + 日期数字网格),
|
||||||
|
* 选中月份高亮,点击进入该月。
|
||||||
*
|
*
|
||||||
* @param year 显示的年份
|
* @param year 显示的年份
|
||||||
* @param selectedMonth 当前选中月份(1-12)
|
* @param selectedMonth 当前选中月份(1-12)
|
||||||
@ -37,6 +45,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
fun YearGridView(
|
fun YearGridView(
|
||||||
year: Int,
|
year: Int,
|
||||||
selectedMonth: Int,
|
selectedMonth: Int,
|
||||||
|
today: LocalDate,
|
||||||
onMonthClick: (Int) -> Unit,
|
onMonthClick: (Int) -> Unit,
|
||||||
onYearChange: (Int) -> Unit,
|
onYearChange: (Int) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
@ -62,13 +71,13 @@ fun YearGridView(
|
|||||||
.clickable { onYearChange(year - 1) }
|
.clickable { onYearChange(year - 1) }
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
|
||||||
Text(
|
Text(
|
||||||
text = "${year}年",
|
text = "${year}年",
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "›",
|
text = "›",
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
@ -81,12 +90,12 @@ fun YearGridView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4×3 月份网格
|
// 4×3 月历网格
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 8.dp),
|
||||||
verticalArrangement = Arrangement.SpaceEvenly
|
verticalArrangement = Arrangement.SpaceEvenly
|
||||||
) {
|
) {
|
||||||
(0 until 4).forEach { row ->
|
(0 until 4).forEach { row ->
|
||||||
@ -96,9 +105,11 @@ fun YearGridView(
|
|||||||
) {
|
) {
|
||||||
(0 until 3).forEach { col ->
|
(0 until 3).forEach { col ->
|
||||||
val month = row * 3 + col + 1
|
val month = row * 3 + col + 1
|
||||||
MonthCell(
|
MiniMonth(
|
||||||
|
year = year,
|
||||||
month = month,
|
month = month,
|
||||||
isSelected = month == selectedMonth,
|
isSelected = month == selectedMonth,
|
||||||
|
today = today,
|
||||||
onClick = { onMonthClick(month) },
|
onClick = { onMonthClick(month) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
@ -110,45 +121,106 @@ fun YearGridView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年视图中的月份单元格。
|
* 精简版月历:月份标题 + 日期数字网格。
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun MonthCell(
|
private fun MiniMonth(
|
||||||
|
year: Int,
|
||||||
month: Int,
|
month: Int,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
|
today: LocalDate,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val contentColor = if (isSelected) {
|
val days = remember(year, month) { generateMiniMonthDays(year, month) }
|
||||||
MaterialTheme.colorScheme.onPrimary
|
val titleColor = if (isSelected) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.onSurface
|
MaterialTheme.colorScheme.onSurface
|
||||||
}
|
}
|
||||||
val bgColor = MaterialTheme.colorScheme.primary
|
val dayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||||
|
val otherMonthColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)
|
||||||
|
val todayBgColor = MaterialTheme.colorScheme.primary
|
||||||
|
|
||||||
Box(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.aspectRatio(1f)
|
.padding(2.dp)
|
||||||
.padding(6.dp)
|
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// 月份标题
|
||||||
|
Text(
|
||||||
|
text = "${month}月",
|
||||||
|
color = titleColor,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
// 日期网格
|
||||||
|
days.chunked(7).forEach { week ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
week.forEach { dayData ->
|
||||||
|
val isToday = dayData.date == today
|
||||||
|
val color = when {
|
||||||
|
!dayData.isCurrentMonth -> otherMonthColor
|
||||||
|
isToday -> MaterialTheme.colorScheme.primary
|
||||||
|
else -> dayColor
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (isToday) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
if (isSelected) {
|
|
||||||
drawCircle(
|
drawCircle(
|
||||||
color = bgColor,
|
color = todayBgColor,
|
||||||
radius = size.minDimension / 2f,
|
radius = size.minDimension / 2f,
|
||||||
center = Offset(size.width / 2f, size.height / 2f)
|
center = Offset(size.width / 2f, size.height / 2f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.clickable(onClick = onClick),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
text = "${month}月",
|
text = if (dayData.isCurrentMonth) dayData.date.day.toString() else "",
|
||||||
color = contentColor,
|
color = if (isToday) MaterialTheme.colorScheme.onPrimary else color,
|
||||||
fontSize = 16.sp,
|
fontSize = 7.sp,
|
||||||
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
|
textAlign = TextAlign.Center,
|
||||||
textAlign = TextAlign.Center
|
lineHeight = 10.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class MiniDayData(
|
||||||
|
val date: LocalDate,
|
||||||
|
val isCurrentMonth: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION") // monthNumber 无替代 API
|
||||||
|
private fun generateMiniMonthDays(year: Int, month: Int): List<MiniDayData> {
|
||||||
|
val firstOfMonth = LocalDate(year, month, 1)
|
||||||
|
val offset = firstOfMonth.dayOfWeek.ordinal
|
||||||
|
val startDate = firstOfMonth.minus(DatePeriod(days = offset))
|
||||||
|
val nextMonth = if (month == 12) LocalDate(year + 1, 1, 1) else LocalDate(year, month + 1, 1)
|
||||||
|
val daysInMonth = nextMonth.minus(DatePeriod(days = 1)).day
|
||||||
|
val rows = ((offset + daysInMonth - 1) / 7) + 1
|
||||||
|
val totalDays = rows * 7
|
||||||
|
|
||||||
|
return (0 until totalDays).map { i ->
|
||||||
|
val date = startDate.plus(DatePeriod(days = i))
|
||||||
|
MiniDayData(
|
||||||
|
date = date,
|
||||||
|
isCurrentMonth = date.month.number == month && date.year == year
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user