Skip to content

Commit 64bc4a9

Browse files
committed
Refactor: Home 화면 CollapsibleHeaderState 로직 개선 및 StickyHeader 동적 위치 계산 적용
- onGloballyPositioned를 사용하여 StickyHeader의 실제 하단 위치를 기반으로 content offset을 계산하도록 수정 - NestedScrollConnection 내 fling 핸들링 및 snap 애니메이션 로직 최적화 - 불필요한 postFling 로직 제거 및 변수명 명확화 (stickyHeaderHeightDp -> initialStickyHeaderHeightDp)
1 parent 4ae9e13 commit 64bc4a9

2 files changed

Lines changed: 27 additions & 30 deletions

File tree

presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import androidx.compose.ui.Modifier
2424
import androidx.compose.ui.draw.clip
2525
import androidx.compose.ui.graphics.graphicsLayer
2626
import androidx.compose.ui.input.nestedscroll.nestedScroll
27+
import androidx.compose.ui.layout.boundsInParent
28+
import androidx.compose.ui.layout.onGloballyPositioned
2729
import androidx.compose.ui.tooling.preview.Preview
2830
import androidx.compose.ui.unit.dp
2931
import androidx.hilt.navigation.compose.hiltViewModel
@@ -102,7 +104,11 @@ private fun HomeScreen(
102104
StickyHeader(
103105
modifier = Modifier
104106
.padding(top = 14.dp)
105-
.height(collapsibleHeaderState.stickyHeaderHeightDp),
107+
.height(collapsibleHeaderState.initialStickyHeaderHeightDp)
108+
.onGloballyPositioned { coordinates ->
109+
collapsibleHeaderState.stickyHeaderActualBottomPx =
110+
coordinates.boundsInParent().bottom
111+
},
106112
onHelpClick = onHelpClick,
107113
)
108114

presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/model/CollapsibleHeaderState.kt

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,22 @@ import androidx.compose.ui.unit.dp
2323
@Stable
2424
internal class CollapsibleHeaderState(
2525
private val density: Density,
26-
val stickyHeaderHeightDp: Dp,
26+
val initialStickyHeaderHeightDp: Dp,
2727
val expandedHeaderHeightDp: Dp,
2828
) {
29-
private val stickyHeaderHeightPx: Float = with(density) { stickyHeaderHeightDp.toPx() }
30-
3129
private val expandedHeaderHeightPx: Float = with(density) { expandedHeaderHeightDp.toPx() }
3230

33-
val collapsedContentOffsetDp: Dp = with(density) { stickyHeaderHeightPx.toDp() + 18.dp }
31+
var stickyHeaderActualBottomPx by mutableFloatStateOf(0f)
32+
internal set
33+
34+
val collapsedContentOffsetDp: Dp
35+
get() = with(density) { stickyHeaderActualBottomPx.toDp() }
3436

3537
var currentHeightPx by mutableFloatStateOf(expandedHeaderHeightPx)
3638
private set
3739

3840
val expansionProgress: Float
39-
get() = if (expandedHeaderHeightPx > 0f) (currentHeightPx / expandedHeaderHeightPx).coerceIn(0f, 1f) else 1f
41+
get() = (currentHeightPx / expandedHeaderHeightPx).coerceIn(0f, 1f)
4042

4143
val nestedScrollConnection = object : NestedScrollConnection {
4244
override fun onPreScroll(available: Offset, source: NestedScrollSource) =
@@ -46,33 +48,23 @@ internal class CollapsibleHeaderState(
4648
if (available.y > 0) consumeDelta(available.y) else Offset.Zero
4749

4850
override suspend fun onPreFling(available: Velocity): Velocity {
49-
if (currentHeightPx <= 0f || currentHeightPx >= expandedHeaderHeightPx) return Velocity.Zero
51+
val isFullyCollapsed = currentHeightPx < 1f
52+
val isFullyExpanded = currentHeightPx > expandedHeaderHeightPx - 1f
5053

51-
val collapse = 0f
52-
val expand = expandedHeaderHeightPx
54+
if (isFullyCollapsed || isFullyExpanded) return Velocity.Zero
5355

5456
val target = when {
55-
available.y < -50f -> collapse
56-
available.y > 50f -> expand
57-
else -> if (currentHeightPx - collapse < expand - currentHeightPx) collapse else expand
57+
available.y < -50f -> 0f
58+
available.y > 50f -> expandedHeaderHeightPx
59+
else -> if (currentHeightPx < expandedHeaderHeightPx / 2) 0f
60+
else expandedHeaderHeightPx
5861
}
5962

6063
snapTo(targetHeight = target, velocity = available.y)
61-
62-
return Velocity(0f, available.y)
64+
return Velocity.Zero
6365
}
6466

6567
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
66-
if (available.y > 0 && currentHeightPx < expandedHeaderHeightPx) {
67-
snapTo(targetHeight = expandedHeaderHeightPx, velocity = available.y)
68-
return Velocity(0f, available.y)
69-
}
70-
71-
if (available.y < 0 && currentHeightPx > 0f) {
72-
snapTo(targetHeight = 0f, velocity = available.y)
73-
return Velocity(0f, available.y)
74-
}
75-
7668
return Velocity.Zero
7769
}
7870
}
@@ -94,27 +86,26 @@ internal class CollapsibleHeaderState(
9486
dampingRatio = Spring.DampingRatioNoBouncy,
9587
stiffness = Spring.StiffnessMediumLow,
9688
),
97-
) { value, _ ->
98-
currentHeightPx = value
99-
}
89+
) { value, _ -> currentHeightPx = value.coerceIn(0f, expandedHeaderHeightPx) }
90+
10091
}
10192
}
10293

10394
@Composable
10495
internal fun rememberCollapsibleHeaderState(
10596
density: Density = LocalDensity.current,
10697
windowInfo: WindowInfo = LocalWindowInfo.current,
107-
stickyHeaderHeight: Dp = 48.dp,
98+
initialStickyHeaderHeightDp: Dp = 48.dp,
10899
minExpandedHeaderHeight: Dp = 146.dp,
109100
): CollapsibleHeaderState {
110101
val containerSize = windowInfo.containerSize
111-
return remember(density, containerSize, minExpandedHeaderHeight, stickyHeaderHeight) {
102+
return remember(density, containerSize, minExpandedHeaderHeight, initialStickyHeaderHeightDp) {
112103
val screenHeightDp = with(density) { containerSize.height.toDp() }
113104
val expandedHeaderHeightDp = (screenHeightDp * 0.18f).coerceAtLeast(minExpandedHeaderHeight)
114105

115106
CollapsibleHeaderState(
116107
density = density,
117-
stickyHeaderHeightDp = stickyHeaderHeight,
108+
initialStickyHeaderHeightDp = initialStickyHeaderHeightDp,
118109
expandedHeaderHeightDp = expandedHeaderHeightDp,
119110
)
120111
}

0 commit comments

Comments
 (0)