refactor: 移除 fling 速度阈值,改用 spring 动画驱动折叠过渡
- ViewModel onDragEnd/onExpandDragEnd 移除 velocity 参数 - BottomCard 移除 VelocityTracker,简化回调签名 - CalendarMonthView 添加 animateFloatAsState spring 动画 - 更新单元测试:移除 fling 测试,调整阈值边界 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7f9db1dc1d
commit
0cdac663c9
@ -18,7 +18,6 @@ import kotlinx.datetime.number
|
||||
import kotlinx.datetime.plus
|
||||
import kotlinx.datetime.todayIn
|
||||
import plus.rua.project.ui.COLLAPSE_THRESHOLD
|
||||
import plus.rua.project.ui.FLING_VELOCITY_THRESHOLD_DP
|
||||
import plus.rua.project.ui.getMonthGridInfo
|
||||
import kotlin.time.Clock
|
||||
|
||||
@ -215,25 +214,17 @@ class CalendarViewModel(
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
fun onDrag(delta: Float) {
|
||||
val new = (_collapseProgress.value + delta).coerceIn(0f, 1f)
|
||||
_collapseProgress.value = new
|
||||
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开状态拖拽结束,根据进度和速度决定折叠或回弹。
|
||||
* 展开状态拖拽结束,根据进度决定折叠或回弹。
|
||||
*
|
||||
* 拖拽超过阈值时自动折叠到周视图,否则回弹到月视图。
|
||||
*
|
||||
* @param velocityDpPerSec 松手时的 fling 速度 (dp/s),正值=上滑(折叠方向),负值=下滑(展开方向)
|
||||
*/
|
||||
fun onDragEnd(velocityDpPerSec: Float = 0f) {
|
||||
fun onDragEnd() {
|
||||
val progress = _collapseProgress.value
|
||||
val shouldCollapse = when {
|
||||
velocityDpPerSec > FLING_VELOCITY_THRESHOLD_DP -> true
|
||||
velocityDpPerSec < -FLING_VELOCITY_THRESHOLD_DP -> false
|
||||
else -> progress > COLLAPSE_THRESHOLD
|
||||
}
|
||||
if (shouldCollapse) {
|
||||
if (progress > COLLAPSE_THRESHOLD) {
|
||||
_isCollapsed.value = true
|
||||
_collapseProgress.value = 1f
|
||||
} else {
|
||||
@ -248,25 +239,17 @@ class CalendarViewModel(
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
fun onExpandDrag(delta: Float) {
|
||||
val new = (_collapseProgress.value + delta).coerceIn(0f, 1f)
|
||||
_collapseProgress.value = new
|
||||
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
/**
|
||||
* 折叠状态拖拽结束,根据进度和速度决定展开或回弹。
|
||||
* 折叠状态拖拽结束,根据进度决定展开或回弹。
|
||||
*
|
||||
* 下拉超过阈值时自动展开到月视图,否则回弹到周视图。
|
||||
*
|
||||
* @param velocityDpPerSec 松手时的 fling 速度 (dp/s),正值=上滑,负值=下滑
|
||||
*/
|
||||
fun onExpandDragEnd(velocityDpPerSec: Float = 0f) {
|
||||
fun onExpandDragEnd() {
|
||||
val progress = _collapseProgress.value
|
||||
val shouldExpand = when {
|
||||
velocityDpPerSec < -FLING_VELOCITY_THRESHOLD_DP -> true
|
||||
velocityDpPerSec > FLING_VELOCITY_THRESHOLD_DP -> false
|
||||
else -> progress < COLLAPSE_THRESHOLD
|
||||
}
|
||||
if (shouldExpand) {
|
||||
if (progress < (1 - COLLAPSE_THRESHOLD)) {
|
||||
_isCollapsed.value = false
|
||||
_collapseProgress.value = 0f
|
||||
} else {
|
||||
|
||||
@ -60,13 +60,12 @@ fun BottomCard(
|
||||
today: LocalDate,
|
||||
shiftKind: ShiftKind?,
|
||||
onDrag: (Float) -> Unit,
|
||||
onDragEnd: (Float) -> Unit,
|
||||
onDragEnd: () -> Unit,
|
||||
onExpandDrag: (Float) -> Unit,
|
||||
onExpandDragEnd: (Float) -> Unit,
|
||||
onExpandDragEnd: () -> Unit,
|
||||
dragRangePx: Float,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val relativeDesc = relativeDayDescription(selectedDate, today)
|
||||
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
@ -87,36 +86,21 @@ fun BottomCard(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.pointerInput(isCollapsed) {
|
||||
val velocityTracker = androidx.compose.ui.input.pointer.util.VelocityTracker()
|
||||
if (isCollapsed) {
|
||||
// 折叠状态:下拉恢复到月视图
|
||||
detectVerticalDragGestures(
|
||||
onDragEnd = {
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
val velocityDpPerSec = with(density) { -velocity.y.toDp().value }
|
||||
onExpandDragEnd(velocityDpPerSec)
|
||||
},
|
||||
onDragCancel = {
|
||||
onExpandDragEnd(0f)
|
||||
}
|
||||
) { change, dragAmount ->
|
||||
velocityTracker.addPosition(change.uptimeMillis, change.position)
|
||||
onDragEnd = { onExpandDragEnd() },
|
||||
onDragCancel = { onExpandDragEnd() }
|
||||
) { _, dragAmount ->
|
||||
val delta = -dragAmount / dragRangePx
|
||||
onExpandDrag(delta)
|
||||
}
|
||||
} else {
|
||||
// 展开状态:上拉折叠到周视图
|
||||
detectVerticalDragGestures(
|
||||
onDragEnd = {
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
val velocityDpPerSec = with(density) { -velocity.y.toDp().value }
|
||||
onDragEnd(velocityDpPerSec)
|
||||
},
|
||||
onDragCancel = {
|
||||
onDragEnd(0f)
|
||||
}
|
||||
) { change, dragAmount ->
|
||||
velocityTracker.addPosition(change.uptimeMillis, change.position)
|
||||
onDragEnd = { onDragEnd() },
|
||||
onDragCancel = { onDragEnd() }
|
||||
) { _, dragAmount ->
|
||||
val delta = -dragAmount / dragRangePx
|
||||
onDrag(delta)
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@ import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
@ -107,6 +110,13 @@ fun CalendarMonthView(
|
||||
val collapseProgress = uiState.collapseProgress
|
||||
val showLegalHoliday = uiState.showLegalHoliday
|
||||
|
||||
// 松手后 progress 从当前值 spring 动画到目标值(0 或 1)
|
||||
val animatedCollapseProgress by animateFloatAsState(
|
||||
targetValue = collapseProgress,
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMedium),
|
||||
label = "collapseProgress"
|
||||
)
|
||||
|
||||
val density = LocalDensity.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
@ -215,7 +225,7 @@ fun CalendarMonthView(
|
||||
selectedDate = selectedDate,
|
||||
today = today,
|
||||
isCollapsed = isCollapsed,
|
||||
collapseProgress = collapseProgress,
|
||||
collapseProgress = animatedCollapseProgress,
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
rowHeightPx = rowHeightPx,
|
||||
screenWidthPx = screenWidthPx,
|
||||
@ -562,9 +572,9 @@ private fun BottomCardArea(
|
||||
today = today,
|
||||
shiftKind = shiftKind,
|
||||
onDrag = { delta -> viewModel.onDrag(delta) },
|
||||
onDragEnd = { velocity -> viewModel.onDragEnd(velocity) },
|
||||
onDragEnd = { viewModel.onDragEnd() },
|
||||
onExpandDrag = { delta -> viewModel.onExpandDrag(delta) },
|
||||
onExpandDragEnd = { velocity -> viewModel.onExpandDragEnd(velocity) },
|
||||
onExpandDragEnd = { viewModel.onExpandDragEnd() },
|
||||
dragRangePx = dragRangePx,
|
||||
modifier = modifier
|
||||
.offset(y = with(density) { (slideProgress * 200).dp })
|
||||
|
||||
@ -311,7 +311,7 @@ class CalendarViewModelStateTest {
|
||||
fun onExpandDragEnd_progressBelowThreshold_expands() {
|
||||
val vm = createViewModel()
|
||||
vm.onDrag(1f)
|
||||
vm.onExpandDrag(-0.95f)
|
||||
vm.onExpandDrag(-0.15f) // progress = 0.85 < 0.92
|
||||
vm.onExpandDragEnd()
|
||||
assertFalse(vm.isCollapsed.value)
|
||||
assertEquals(0f, vm.collapseProgress.value, 0.001f)
|
||||
@ -321,50 +321,12 @@ class CalendarViewModelStateTest {
|
||||
fun onExpandDragEnd_progressAboveThreshold_staysCollapsed() {
|
||||
val vm = createViewModel()
|
||||
vm.onDrag(1f)
|
||||
vm.onExpandDrag(-0.05f)
|
||||
vm.onExpandDrag(-0.05f) // progress = 0.95 > 0.92
|
||||
vm.onExpandDragEnd()
|
||||
assertTrue(vm.isCollapsed.value)
|
||||
assertEquals(1f, vm.collapseProgress.value, 0.001f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDragEnd_fastFlingUp_setsCollapsed() {
|
||||
val vm = createViewModel()
|
||||
vm.onDrag(0.1f)
|
||||
vm.onDragEnd(velocityDpPerSec = 900f)
|
||||
assertTrue(vm.isCollapsed.value)
|
||||
assertEquals(1f, vm.collapseProgress.value, 0.001f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDragEnd_fastFlingDown_keepsExpanded() {
|
||||
val vm = createViewModel()
|
||||
vm.onDrag(0.9f)
|
||||
vm.onDragEnd(velocityDpPerSec = -900f)
|
||||
assertFalse(vm.isCollapsed.value)
|
||||
assertEquals(0f, vm.collapseProgress.value, 0.001f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onExpandDragEnd_fastFlingDown_setsExpanded() {
|
||||
val vm = createViewModel()
|
||||
vm.onDrag(1f)
|
||||
vm.onExpandDrag(-0.1f)
|
||||
vm.onExpandDragEnd(velocityDpPerSec = -900f)
|
||||
assertFalse(vm.isCollapsed.value)
|
||||
assertEquals(0f, vm.collapseProgress.value, 0.001f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onExpandDragEnd_fastFlingUp_staysCollapsed() {
|
||||
val vm = createViewModel()
|
||||
vm.onDrag(1f)
|
||||
vm.onExpandDrag(-0.9f)
|
||||
vm.onExpandDragEnd(velocityDpPerSec = 900f)
|
||||
assertTrue(vm.isCollapsed.value)
|
||||
assertEquals(1f, vm.collapseProgress.value, 0.001f)
|
||||
}
|
||||
|
||||
// ---- getMonthDays 与 selectedDate 配合 ----
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user