年视图标题固定 + 交叉淡入淡出 + 移除 fadeIn/fadeOut
年视图标题行从 HorizontalPager 内移到外部,左右滑动时标题不随 pager 滚动。 年份切换时标题文字用垂直滑动动画(与 MonthHeader 一致,移除 fadeIn/fadeOut)。 月/年视图左右滑动改为交叉淡入淡出,修复原实现中间全白的问题。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
302e6556dd
commit
7250d08fb7
@ -41,6 +41,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@ -331,12 +332,9 @@ fun CalendarMonthView(
|
||||
}
|
||||
}
|
||||
|
||||
// 年视图层:仅在年视图激活时渲染;HorizontalPager 支持左右滑动切年。
|
||||
// 年视图层:标题固定,HorizontalPager 只包裹网格。
|
||||
if (viewModel.isYearView) {
|
||||
HorizontalPager(
|
||||
state = yearPagerState,
|
||||
beyondViewportPageCount = 1,
|
||||
flingBehavior = PagerDefaults.flingBehavior(state = yearPagerState),
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
@ -346,7 +344,32 @@ fun CalendarMonthView(
|
||||
transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY)
|
||||
}
|
||||
.padding(horizontal = HORIZONTAL_PADDING_DP.dp)
|
||||
) {
|
||||
YearHeader(
|
||||
year = viewModel.yearViewYear,
|
||||
onYearChange = { newYear ->
|
||||
val offset = newYear - viewModel.yearViewYear
|
||||
val targetPage = yearPagerState.currentPage + offset
|
||||
if (targetPage != yearPagerState.currentPage) {
|
||||
coroutineScope.launch { yearPagerState.animateScrollToPage(targetPage) }
|
||||
}
|
||||
}
|
||||
)
|
||||
HorizontalPager(
|
||||
state = yearPagerState,
|
||||
beyondViewportPageCount = 1,
|
||||
flingBehavior = PagerDefaults.flingBehavior(state = yearPagerState),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) { page ->
|
||||
val pageOffset = abs(yearPagerState.currentPageOffsetFraction)
|
||||
val isCurrentPage = page == yearPagerState.currentPage
|
||||
val crossFadeAlpha = if (isCurrentPage) {
|
||||
1f - pageOffset
|
||||
} else {
|
||||
pageOffset
|
||||
}
|
||||
val pageYear = viewModel.selectedDate.year + (page - START_PAGE)
|
||||
YearGridView(
|
||||
year = pageYear,
|
||||
@ -363,16 +386,11 @@ fun CalendarMonthView(
|
||||
coroutineScope.launch { pagerState.scrollToPage(targetPage) }
|
||||
}
|
||||
},
|
||||
onYearChange = { newYear ->
|
||||
val offset = newYear - pageYear
|
||||
val targetPage = yearPagerState.currentPage + offset
|
||||
if (targetPage != yearPagerState.currentPage) {
|
||||
coroutineScope.launch { yearPagerState.animateScrollToPage(targetPage) }
|
||||
}
|
||||
}
|
||||
modifier = Modifier.alpha(crossFadeAlpha)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FAB 浮动按钮
|
||||
FloatingActionButton(
|
||||
|
||||
@ -72,7 +72,12 @@ fun CalendarPager(
|
||||
modifier = modifier
|
||||
) { page ->
|
||||
val pageOffset = abs(pagerState.currentPageOffsetFraction)
|
||||
val alpha = 1f - pageOffset.coerceIn(0f, 0.3f) / 0.3f
|
||||
val isCurrentPage = page == pagerState.currentPage
|
||||
val alpha = if (isCurrentPage) {
|
||||
1f - pageOffset
|
||||
} else {
|
||||
pageOffset
|
||||
}
|
||||
val (year, month) = pageToYearMonth(page, initialYear, initialMonth)
|
||||
CalendarMonthPage(
|
||||
year = year,
|
||||
|
||||
@ -2,8 +2,6 @@ package plus.rua.project.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
@ -52,11 +50,11 @@ fun MonthHeader(
|
||||
targetState = Pair(year, month),
|
||||
transitionSpec = {
|
||||
if (targetState.second > initialState.second) {
|
||||
slideInVertically(tween(250)) { -it } + fadeIn(tween(250)) togetherWith
|
||||
slideOutVertically(tween(250)) { it } + fadeOut(tween(250))
|
||||
slideInVertically(tween(250)) { -it } togetherWith
|
||||
slideOutVertically(tween(250)) { it }
|
||||
} else {
|
||||
slideInVertically(tween(250)) { it } + fadeIn(tween(250)) togetherWith
|
||||
slideOutVertically(tween(250)) { -it } + fadeOut(tween(250))
|
||||
slideInVertically(tween(250)) { it } togetherWith
|
||||
slideOutVertically(tween(250)) { -it }
|
||||
}
|
||||
}
|
||||
) { (y, m) ->
|
||||
@ -70,11 +68,11 @@ fun MonthHeader(
|
||||
targetState = weekNumber,
|
||||
transitionSpec = {
|
||||
if (targetState > initialState) {
|
||||
slideInVertically(tween(250)) { -it } + fadeIn(tween(250)) togetherWith
|
||||
slideOutVertically(tween(250)) { it } + fadeOut(tween(250))
|
||||
slideInVertically(tween(250)) { -it } togetherWith
|
||||
slideOutVertically(tween(250)) { it }
|
||||
} else {
|
||||
slideInVertically(tween(250)) { it } + fadeIn(tween(250)) togetherWith
|
||||
slideOutVertically(tween(250)) { -it } + fadeOut(tween(250))
|
||||
slideInVertically(tween(250)) { it } togetherWith
|
||||
slideOutVertically(tween(250)) { -it }
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
package plus.rua.project.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -31,16 +36,12 @@ import kotlinx.datetime.plus
|
||||
private val WEEKDAY_LABELS = listOf("一", "二", "三", "四", "五", "六", "日")
|
||||
|
||||
/**
|
||||
* 年度网格视图,显示 4×3 精简月历网格,支持年份切换。
|
||||
*
|
||||
* 每格显示一个精简版月历(月份标题 + 星期行 + 日期数字网格),
|
||||
* 选中月份高亮,点击进入该月。
|
||||
* 年视图 4×3 月历网格。
|
||||
*
|
||||
* @param year 显示的年份
|
||||
* @param selectedMonth 当前选中月份(1-12)
|
||||
* @param today 今天的日期
|
||||
* @param onMonthClick 月份点击回调
|
||||
* @param onYearChange 年份切换回调
|
||||
* @param modifier 外部布局修饰符
|
||||
*/
|
||||
@Composable
|
||||
@ -49,49 +50,12 @@ fun YearGridView(
|
||||
selectedMonth: Int,
|
||||
today: LocalDate,
|
||||
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)
|
||||
)
|
||||
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = "${year}年",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
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
|
||||
@ -244,3 +208,67 @@ private fun generateMiniMonthDays(year: Int, month: Int): List<MiniDayData> {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 年视图标题栏,显示年份文字和左右导航箭头。
|
||||
*
|
||||
* 年份切换时文字有垂直滑动过渡动画,方向由新旧年份大小决定。
|
||||
*
|
||||
* @param year 当前年份
|
||||
* @param onYearChange 年份切换回调
|
||||
* @param modifier 外部布局修饰符
|
||||
*/
|
||||
@Composable
|
||||
fun YearHeader(
|
||||
year: Int,
|
||||
onYearChange: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
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)
|
||||
)
|
||||
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
|
||||
AnimatedContent(
|
||||
targetState = year,
|
||||
transitionSpec = {
|
||||
if (targetState > initialState) {
|
||||
slideInVertically(tween(250)) { -it } togetherWith
|
||||
slideOutVertically(tween(250)) { it }
|
||||
} else {
|
||||
slideInVertically(tween(250)) { it } togetherWith
|
||||
slideOutVertically(tween(250)) { -it }
|
||||
}
|
||||
}
|
||||
) { y ->
|
||||
Text(
|
||||
text = "${y}年",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user