From 64bc4a9daef36cabcff515556a575f597d8ecc37 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 25 Mar 2026 18:02:06 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Refactor:=20Home=20=ED=99=94=EB=A9=B4=20Col?= =?UTF-8?q?lapsibleHeaderState=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=20=EB=B0=8F=20StickyHeader=20=EB=8F=99=EC=A0=81=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EA=B3=84=EC=82=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onGloballyPositioned를 사용하여 StickyHeader의 실제 하단 위치를 기반으로 content offset을 계산하도록 수정 - NestedScrollConnection 내 fling 핸들링 및 snap 애니메이션 로직 최적화 - 불필요한 postFling 로직 제거 및 변수명 명확화 (stickyHeaderHeightDp -> initialStickyHeaderHeightDp) --- .../presentation/screen/home/HomeScreen.kt | 8 ++- .../home/model/CollapsibleHeaderState.kt | 49 ++++++++----------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt index f3cfcdb7..197f2fcd 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt @@ -24,6 +24,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.boundsInParent +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -102,7 +104,11 @@ private fun HomeScreen( StickyHeader( modifier = Modifier .padding(top = 14.dp) - .height(collapsibleHeaderState.stickyHeaderHeightDp), + .height(collapsibleHeaderState.initialStickyHeaderHeightDp) + .onGloballyPositioned { coordinates -> + collapsibleHeaderState.stickyHeaderActualBottomPx = + coordinates.boundsInParent().bottom + }, onHelpClick = onHelpClick, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt index bd0d7e26..f894f505 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt @@ -23,20 +23,22 @@ import androidx.compose.ui.unit.dp @Stable internal class CollapsibleHeaderState( private val density: Density, - val stickyHeaderHeightDp: Dp, + val initialStickyHeaderHeightDp: Dp, val expandedHeaderHeightDp: Dp, ) { - private val stickyHeaderHeightPx: Float = with(density) { stickyHeaderHeightDp.toPx() } - private val expandedHeaderHeightPx: Float = with(density) { expandedHeaderHeightDp.toPx() } - val collapsedContentOffsetDp: Dp = with(density) { stickyHeaderHeightPx.toDp() + 18.dp } + var stickyHeaderActualBottomPx by mutableFloatStateOf(0f) + internal set + + val collapsedContentOffsetDp: Dp + get() = with(density) { stickyHeaderActualBottomPx.toDp() } var currentHeightPx by mutableFloatStateOf(expandedHeaderHeightPx) private set val expansionProgress: Float - get() = if (expandedHeaderHeightPx > 0f) (currentHeightPx / expandedHeaderHeightPx).coerceIn(0f, 1f) else 1f + get() = (currentHeightPx / expandedHeaderHeightPx).coerceIn(0f, 1f) val nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource) = @@ -46,33 +48,23 @@ internal class CollapsibleHeaderState( if (available.y > 0) consumeDelta(available.y) else Offset.Zero override suspend fun onPreFling(available: Velocity): Velocity { - if (currentHeightPx <= 0f || currentHeightPx >= expandedHeaderHeightPx) return Velocity.Zero + val isFullyCollapsed = currentHeightPx < 1f + val isFullyExpanded = currentHeightPx > expandedHeaderHeightPx - 1f - val collapse = 0f - val expand = expandedHeaderHeightPx + if (isFullyCollapsed || isFullyExpanded) return Velocity.Zero val target = when { - available.y < -50f -> collapse - available.y > 50f -> expand - else -> if (currentHeightPx - collapse < expand - currentHeightPx) collapse else expand + available.y < -50f -> 0f + available.y > 50f -> expandedHeaderHeightPx + else -> if (currentHeightPx < expandedHeaderHeightPx / 2) 0f + else expandedHeaderHeightPx } snapTo(targetHeight = target, velocity = available.y) - - return Velocity(0f, available.y) + return Velocity.Zero } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - if (available.y > 0 && currentHeightPx < expandedHeaderHeightPx) { - snapTo(targetHeight = expandedHeaderHeightPx, velocity = available.y) - return Velocity(0f, available.y) - } - - if (available.y < 0 && currentHeightPx > 0f) { - snapTo(targetHeight = 0f, velocity = available.y) - return Velocity(0f, available.y) - } - return Velocity.Zero } } @@ -94,9 +86,8 @@ internal class CollapsibleHeaderState( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow, ), - ) { value, _ -> - currentHeightPx = value - } + ) { value, _ -> currentHeightPx = value.coerceIn(0f, expandedHeaderHeightPx) } + } } @@ -104,17 +95,17 @@ internal class CollapsibleHeaderState( internal fun rememberCollapsibleHeaderState( density: Density = LocalDensity.current, windowInfo: WindowInfo = LocalWindowInfo.current, - stickyHeaderHeight: Dp = 48.dp, + initialStickyHeaderHeightDp: Dp = 48.dp, minExpandedHeaderHeight: Dp = 146.dp, ): CollapsibleHeaderState { val containerSize = windowInfo.containerSize - return remember(density, containerSize, minExpandedHeaderHeight, stickyHeaderHeight) { + return remember(density, containerSize, minExpandedHeaderHeight, initialStickyHeaderHeightDp) { val screenHeightDp = with(density) { containerSize.height.toDp() } val expandedHeaderHeightDp = (screenHeightDp * 0.18f).coerceAtLeast(minExpandedHeaderHeight) CollapsibleHeaderState( density = density, - stickyHeaderHeightDp = stickyHeaderHeight, + initialStickyHeaderHeightDp = initialStickyHeaderHeightDp, expandedHeaderHeightDp = expandedHeaderHeightDp, ) } From 50fbf108b4913c0122e44713124595243afc137c Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 25 Mar 2026 18:24:27 +0900 Subject: [PATCH 2/3] Chore: ktlintFormat --- .../presentation/screen/home/model/CollapsibleHeaderState.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt index f894f505..0cc9d2f3 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt @@ -56,8 +56,7 @@ internal class CollapsibleHeaderState( val target = when { available.y < -50f -> 0f available.y > 50f -> expandedHeaderHeightPx - else -> if (currentHeightPx < expandedHeaderHeightPx / 2) 0f - else expandedHeaderHeightPx + else -> if (currentHeightPx < expandedHeaderHeightPx / 2) 0f else expandedHeaderHeightPx } snapTo(targetHeight = target, velocity = available.y) @@ -87,7 +86,6 @@ internal class CollapsibleHeaderState( stiffness = Spring.StiffnessMediumLow, ), ) { value, _ -> currentHeightPx = value.coerceIn(0f, expandedHeaderHeightPx) } - } } From 89edf7d616c1bedbac3c991db70a0de4c9abb8c1 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 25 Mar 2026 18:41:27 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Fix:=20stickyHeaderActualBottomPx=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/screen/home/model/CollapsibleHeaderState.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt index 0cc9d2f3..f6c021a4 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt @@ -27,8 +27,9 @@ internal class CollapsibleHeaderState( val expandedHeaderHeightDp: Dp, ) { private val expandedHeaderHeightPx: Float = with(density) { expandedHeaderHeightDp.toPx() } + private val initialStickyHeaderHeightPx: Float = with(density) { initialStickyHeaderHeightDp.toPx() } - var stickyHeaderActualBottomPx by mutableFloatStateOf(0f) + var stickyHeaderActualBottomPx by mutableFloatStateOf(initialStickyHeaderHeightPx) internal set val collapsedContentOffsetDp: Dp