Replace Column layout with Box+manual y-offset for collapse animation
Switch CalendarMonthPage from Column to Box with calculated y positions per row, enabling precise height computation that aligns with CalendarMonthView's BottomCard positioning. Simplify cardTopPx calculation by deriving grid height from the same formula instead of tracking expandedCalendarHeightPx separately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2fb36168a3
commit
151b15d09e
@ -1,6 +1,6 @@
|
|||||||
package plus.rua.project.ui
|
package plus.rua.project.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@ -42,37 +42,68 @@ fun CalendarMonthPage(
|
|||||||
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
|
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasSelectedWeek = selectedWeekIndex >= 0
|
||||||
|
|
||||||
var rowHeightPx by remember { mutableIntStateOf(0) }
|
var rowHeightPx by remember { mutableIntStateOf(0) }
|
||||||
val rowMeasured = rowHeightPx > 0
|
val rowMeasured = rowHeightPx > 0
|
||||||
|
val H = rowHeightPx.toFloat()
|
||||||
|
|
||||||
// 选中行上移距离 = 上方行数 × 行高 × progress
|
// 总高度 = 6行 × 行高(展开时),或选中行高度(折叠时)
|
||||||
val selectedOffsetPx = if (rowMeasured) {
|
val totalHeightDp = if (rowMeasured) {
|
||||||
-(selectedWeekIndex.toFloat() * rowHeightPx.toFloat() * collapseProgress)
|
if (hasSelectedWeek) {
|
||||||
|
// 选中行高度 + 上方行压缩高度 + 下方行压缩高度
|
||||||
|
val aboveH = selectedWeekIndex * H * (1f - collapseProgress)
|
||||||
|
val belowH = (weeks.size - 1 - selectedWeekIndex) * H * (1f - collapseProgress)
|
||||||
|
val selH = H
|
||||||
|
with(density) { (aboveH + selH + belowH).toDp() }
|
||||||
|
} else {
|
||||||
|
with(density) { (weeks.size * H).toDp() }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
0f
|
null
|
||||||
}
|
}
|
||||||
val selectedOffsetDp = with(density) { selectedOffsetPx.toDp() }
|
|
||||||
|
|
||||||
Column(modifier = modifier.clipToBounds()) {
|
Box(modifier = modifier.clipToBounds().then(
|
||||||
|
if (totalHeightDp != null) Modifier.height(totalHeightDp)
|
||||||
|
else Modifier
|
||||||
|
).onSizeChanged { size ->
|
||||||
|
if (collapseProgress > 0f) {
|
||||||
|
println("[Page] totalH=${size.height}px p=$collapseProgress selWeek=$selectedWeekIndex rowH=$rowHeightPx")
|
||||||
|
}
|
||||||
|
}) {
|
||||||
weeks.forEachIndexed { weekIndex, week ->
|
weeks.forEachIndexed { weekIndex, week ->
|
||||||
val isSelected = weekIndex == selectedWeekIndex
|
val isSelected = hasSelectedWeek && weekIndex == selectedWeekIndex
|
||||||
val isAboveSelected = weekIndex < selectedWeekIndex
|
val isAbove = hasSelectedWeek && weekIndex < selectedWeekIndex
|
||||||
val isBelowSelected = weekIndex > selectedWeekIndex
|
val isBelow = hasSelectedWeek && weekIndex > selectedWeekIndex
|
||||||
|
|
||||||
// 非选中行高度跟手压缩
|
|
||||||
val rowScale = when {
|
val rowScale = when {
|
||||||
isAboveSelected || isBelowSelected -> 1f - collapseProgress
|
isAbove || isBelow -> 1f - collapseProgress
|
||||||
else -> 1f
|
else -> 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
val rowHeightDp = if (rowMeasured && rowScale > 0.01f) {
|
val rowHeightDp = if (rowMeasured && rowScale > 0.01f) {
|
||||||
with(density) { (rowHeightPx.toFloat() * rowScale).toDp() }
|
with(density) { (H * rowScale).toDp() }
|
||||||
} else if (!rowMeasured) {
|
} else if (!rowMeasured) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
0.dp
|
0.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手动计算每行的视觉 y 位置
|
||||||
|
val yOffsetDp = if (rowMeasured && hasSelectedWeek) {
|
||||||
|
val yPx = when {
|
||||||
|
isAbove -> weekIndex * H * (1f - collapseProgress)
|
||||||
|
isSelected -> selectedWeekIndex * H * (1f - collapseProgress)
|
||||||
|
isBelow -> selectedWeekIndex * H * (1f - collapseProgress) + H + (weekIndex - selectedWeekIndex - 1) * H * (1f - collapseProgress)
|
||||||
|
else -> weekIndex * H
|
||||||
|
}
|
||||||
|
with(density) { yPx.toDp() }
|
||||||
|
} else if (rowMeasured) {
|
||||||
|
with(density) { (weekIndex * H).toDp() }
|
||||||
|
} else {
|
||||||
|
0.dp
|
||||||
|
}
|
||||||
|
|
||||||
val shouldShow = rowHeightDp == null || rowHeightDp > 0.dp
|
val shouldShow = rowHeightDp == null || rowHeightDp > 0.dp
|
||||||
|
|
||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
@ -84,10 +115,7 @@ fun CalendarMonthPage(
|
|||||||
if (rowHeightDp != null) Modifier.height(rowHeightDp)
|
if (rowHeightDp != null) Modifier.height(rowHeightDp)
|
||||||
else Modifier
|
else Modifier
|
||||||
)
|
)
|
||||||
.then(
|
.offset(y = yOffsetDp)
|
||||||
if (isSelected && rowMeasured) Modifier.offset(y = selectedOffsetDp)
|
|
||||||
else Modifier
|
|
||||||
)
|
|
||||||
.onSizeChanged { size ->
|
.onSizeChanged { size ->
|
||||||
if (size.height > 0 && !rowMeasured) {
|
if (size.height > 0 && !rowMeasured) {
|
||||||
rowHeightPx = size.height
|
rowHeightPx = size.height
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column
|
|||||||
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.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -19,7 +18,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.todayIn
|
import kotlinx.datetime.todayIn
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
@ -44,26 +42,34 @@ fun CalendarMonthView(
|
|||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
var calendarHeightPx by remember { mutableIntStateOf(0) }
|
var calendarHeightPx by remember { mutableIntStateOf(0) }
|
||||||
var screenHeightPx by remember { mutableIntStateOf(0) }
|
|
||||||
var expandedCalendarHeightPx by remember { mutableIntStateOf(0) }
|
|
||||||
var monthHeaderHeightPx by remember { mutableIntStateOf(0) }
|
var monthHeaderHeightPx by remember { mutableIntStateOf(0) }
|
||||||
var weekdayHeaderHeightPx by remember { mutableIntStateOf(0) }
|
var weekdayHeaderHeightPx by remember { mutableIntStateOf(0) }
|
||||||
|
var screenHeightPx by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
// 日历网格高度 = 总高度 - MonthHeader - WeekdayHeader
|
val p = viewModel.collapseProgress
|
||||||
val expandedGridHeightPx = expandedCalendarHeightPx - monthHeaderHeightPx - weekdayHeaderHeightPx
|
val headerHeightPx = monthHeaderHeightPx + weekdayHeaderHeightPx
|
||||||
// 折叠偏移量 = 进度 × 网格5行高度(保留1行可见)
|
|
||||||
val collapseOffsetPx = if (viewModel.isCollapsed) {
|
// 展开时网格高度 = 首次测量的日历总高度 - headers
|
||||||
0
|
val expandedGridHeightPx = calendarHeightPx - headerHeightPx
|
||||||
} else {
|
val weeksCount = 6
|
||||||
-(viewModel.collapseProgress * expandedGridHeightPx * 5f / 6f).toInt()
|
|
||||||
}
|
// 折叠时网格高度公式(与 CalendarMonthPage 一致):
|
||||||
val cardTopPx = if (viewModel.isCollapsed) {
|
// gridH = rowH × (1 + (weeks-1) × (1-p))
|
||||||
calendarHeightPx
|
// 其中 rowH = expandedGridHeightPx / weeksCount
|
||||||
} else {
|
val gridHeightPx = if (expandedGridHeightPx > 0 && p > 0f) {
|
||||||
expandedCalendarHeightPx + collapseOffsetPx
|
val rowH = expandedGridHeightPx.toFloat() / weeksCount
|
||||||
}
|
(rowH * (1 + (weeksCount - 1) * (1f - p))).toInt()
|
||||||
|
} else if (expandedGridHeightPx > 0) {
|
||||||
|
expandedGridHeightPx
|
||||||
|
} else 0
|
||||||
|
|
||||||
|
val cardTopPx = headerHeightPx + gridHeightPx
|
||||||
val cardHeightPx = screenHeightPx - cardTopPx
|
val cardHeightPx = screenHeightPx - cardTopPx
|
||||||
|
|
||||||
|
if (p > 0f) {
|
||||||
|
println("[View] p=$p monthH=$monthHeaderHeightPx weekdayH=$weekdayHeaderHeightPx expandedGridH=$expandedGridHeightPx gridH=$gridHeightPx cardTop=$cardTopPx cardH=$cardHeightPx screenH=$screenHeightPx calH=$calendarHeightPx isCollapsed=${viewModel.isCollapsed}")
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@ -73,12 +79,11 @@ fun CalendarMonthView(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(horizontal = 16.dp).onSizeChanged { size ->
|
Column(modifier = Modifier.padding(horizontal = 16.dp).onSizeChanged { size ->
|
||||||
calendarHeightPx = size.height
|
// 仅在展开时记录日历总高度(折叠时 HorizontalPager 不缩小)
|
||||||
// 仅在首次展开时记录完整日历高度,折叠后不再覆盖
|
if (p < 0.01f) {
|
||||||
if (!viewModel.isCollapsed && viewModel.collapseProgress < 0.01f) {
|
calendarHeightPx = size.height
|
||||||
expandedCalendarHeightPx = size.height
|
}
|
||||||
}
|
}) {
|
||||||
}) {
|
|
||||||
MonthHeader(
|
MonthHeader(
|
||||||
year = currentYear,
|
year = currentYear,
|
||||||
month = currentMonth,
|
month = currentMonth,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user