feat: US-007~010 实现年月视图共享元素转场动画
- 引入 SharedTransitionLayout + AnimatedContent 管理月/年视图切换 - 使用 sharedBounds + rememberSharedContentState 标记共享元素 - 转场动画 400ms,FastOutSlowInEasing - 添加 compose-animation 依赖支持 SharedTransition API Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c19916c2ec
commit
cf9315cfe3
@ -24,6 +24,7 @@ androidx-lifecycle-viewmodelCompose = { module = "androidx.lifecycle:lifecycle-v
|
||||
androidx-lifecycle-runtimeCompose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
||||
compose-runtime = { module = "androidx.compose.runtime:runtime" }
|
||||
compose-animation = { module = "androidx.compose.animation:animation" }
|
||||
compose-foundation = { module = "androidx.compose.foundation:foundation" }
|
||||
compose-material3 = { module = "androidx.compose.material3:material3" }
|
||||
compose-ui = { module = "androidx.compose.ui:ui" }
|
||||
|
||||
@ -45,6 +45,7 @@ dependencies {
|
||||
|
||||
implementation(libs.compose.runtime)
|
||||
implementation(libs.compose.foundation)
|
||||
implementation(libs.compose.animation)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
package plus.rua.project.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
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.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@ -50,7 +55,6 @@ import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
@ -70,13 +74,14 @@ import kotlin.math.abs
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* 日历主界面,包含月/周视图切换、折叠动画和年视图缩放转场。
|
||||
* 日历主界面,包含月/周视图切换、折叠动画和年视图共享元素转场。
|
||||
*
|
||||
* 折叠时日历从月视图收缩为周视图(1行),BottomCard 同步上移填充空间。
|
||||
* 通过左下角 FAB 菜单切换月/年视图,以当前月为锚点缩放转场。
|
||||
* 通过左下角 FAB 菜单切换月/年视图,使用 SharedTransitionLayout 实现共享元素转场。
|
||||
*
|
||||
* @param modifier 外部布局修饰符
|
||||
*/
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun CalendarMonthView(
|
||||
modifier: Modifier = Modifier,
|
||||
@ -139,10 +144,8 @@ fun CalendarMonthView(
|
||||
}
|
||||
}
|
||||
|
||||
// 年视图锚点缩放:当前月在 4×3 网格中的归一化位置
|
||||
val anchorPivotX = ((currentMonth - 1) % 3 + 0.5f) / 3f
|
||||
val anchorPivotY = ((currentMonth - 1) / 3 + 0.5f) / 4f
|
||||
|
||||
SharedTransitionLayout {
|
||||
val sharedScope = this
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
@ -152,21 +155,30 @@ fun CalendarMonthView(
|
||||
screenWidthPx = size.width
|
||||
}
|
||||
) {
|
||||
// 月视图层:仅在非年视图时渲染,年视图激活时立即移除。
|
||||
if (!viewModel.isYearView) {
|
||||
AnimatedContent(
|
||||
targetState = viewModel.isYearView,
|
||||
label = "month_year_transition",
|
||||
transitionSpec = {
|
||||
fadeIn(tween(300, easing = FastOutSlowInEasing)) togetherWith
|
||||
fadeOut(tween(300, easing = FastOutSlowInEasing))
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { isYearView ->
|
||||
with(sharedScope) {
|
||||
if (!isYearView) {
|
||||
composeTraceBeginSection("MonthView:Compose")
|
||||
val layoutReady = rowHeightPx > 0
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
val monthProgress = 1f - viewModel.yearViewProgress
|
||||
val scale = lerp(0.3f, 1f, monthProgress)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
alpha = if (layoutReady) monthProgress.coerceIn(0f, 1f) else 0f
|
||||
transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY)
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key = "month_content"),
|
||||
animatedVisibilityScope = this@AnimatedContent,
|
||||
boundsTransform = { _, _ ->
|
||||
tween(400, easing = FastOutSlowInEasing)
|
||||
}
|
||||
)
|
||||
.alpha(if (layoutReady) 1f else 0f)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@ -205,22 +217,18 @@ fun CalendarMonthView(
|
||||
}
|
||||
}
|
||||
composeTraceEndSection()
|
||||
}
|
||||
|
||||
// 年视图层:标题固定,HorizontalPager 只包裹网格。
|
||||
if (viewModel.isYearView) {
|
||||
val yearProgress = viewModel.yearViewProgress
|
||||
} else {
|
||||
composeTraceBeginSection("YearView:Compose")
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
val scale = lerp(3.3f, 1f, yearProgress)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
alpha = yearProgress.coerceIn(0f, 1f)
|
||||
transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY)
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key = "month_content"),
|
||||
animatedVisibilityScope = this@AnimatedContent,
|
||||
boundsTransform = { _, _ ->
|
||||
tween(400, easing = FastOutSlowInEasing)
|
||||
}
|
||||
)
|
||||
.padding(horizontal = HORIZONTAL_PADDING_DP.dp)
|
||||
) {
|
||||
YearHeader(
|
||||
@ -271,6 +279,8 @@ fun CalendarMonthView(
|
||||
}
|
||||
composeTraceEndSection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FAB 浮动按钮
|
||||
FloatingActionButton(
|
||||
@ -358,6 +368,7 @@ fun CalendarMonthView(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MenuIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user