Jetpack Compose Layout ์ ์ฉ๊ธฐ: ์ ์ฐํ๊ณ ์ฑ๋ฅ์ด ๊ฐ์ ๋ ํ๋ฉด์ ๊ตฌํํ๊ธฐ๊น์ง
- #Android
- #Jetpack Compose
- #Compose
- #Custom Layout
์๋ ํ์ธ์. ๋ชจ๋ฐ์ผํ ์๋๋ก์ด๋ ๊ฐ๋ฐ์ ์ค์์์ ๋๋ค. ์ด๋ฒ ๊ธ์์๋ Jetpack Compose ๋ก ๊ตฌํ๋ ๊ด์ฌ์ํฐ ์ ํํ๋ฉด์์ ์ฑ๋ฅ ๋ฐ ๋์์ธ ์ด์๋ฅผ ๋ฐ๊ฒฌํ๊ณ ์ด๋ฅผ ํด๊ฒฐํด ๋๊ฐ ๊ณผ์ ์ ๊ณต์ ํ๊ณ ์ ํฉ๋๋ค.
๋์์ธ ์๊ตฌ์ฌํญ
์ฐ์ ๊ด์ฌ์ํฐ ์ ํํ๋ฉด์ ๋์์ธ์ ์๋์ ๊ฐ์ต๋๋ค.
ํด๋น ํ๋ฉด์ ์๋จ์ ๋ค๋น๊ฒ์ด์ ์ ๋ด๋นํ๋ TopBar๋ฅผ ์ ์ธํ๋ฉด ํฌ๊ฒ ์ธ ๋ถ๋ถ์ผ๋ก ๋๋ ์ ์์ต๋๋ค.
- ํ๋ฉด์ ๋ํด ์๋ดํ๋ TextHeader (์ดํ TextHeader)
- ์ํฐ์คํธ๋ช ์ ๊ฒ์ํ๋ TextField (์ดํ SearchTextField)
- ์ํฐ์คํธ์ ์ฌ์ง๊ณผ ์ด๋ฆ์ ๊ทธ๋ฆฌ๋ ํํ๋ก ๋ณด์ฌ์ฃผ๋ ๋ฆฌ์คํธ (์ดํ FavoriteList)
์ด ๋, SearchTextField๋ stickyHeader์ ์ญํ ์ ํ์ฌ FavoriteList๊ฐ ์คํฌ๋กค๋์ด๋ ์๋จ์ ๊ณ ์ ๋์ด์ผ ํ๋ค๋ ์๊ตฌ์ฌํญ์ด ์์์ต๋๋ค.
LazyColumn๊ณผ FlowRow ํ์ฉํ ์ฒซ๋ฒ์งธ ์๋
์ฒ์์๋ ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด LazyColumn
๊ณผ FlowRow
๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
LazyColumn
์ stickyHeader
ํจ์๋ฅผ ํ์ฉํ์ฌ SearchTextField๊ฐ ๊ณ ์ ๋์ด ์์ด์ผ ํ๋ค๋ ์๊ตฌ์ฌํญ์ ์์ฝ๊ฒ ๋ง์กฑ์ํฌ ์ ์์๊ธฐ ๋๋ฌธ์
๋๋ค.
ํ์ง๋ง LazyColumn
์ ์ธ๋ก ๋ฐฉํฅ์ ๋ชฉ๋ก์ ํ์ํ๊ธฐ ๋๋ฌธ์ ๊ฐ ์ปดํฌ๋ํธ๋ค์ ์ธ๋ก๋ก ๋ฐฐ์นํ ์๋ ์์์ง๋ง, FavoriteList์ ์์ดํ
๋ค์ ๊ฒฉ์ ํํ๋ก ๋ฐฐ์นํด์ผ ํ๊ธฐ ๋๋ฌธ์ LazyColumn
์์ FlowRow
๋ฅผ ์ฌ์ฉํด์ผ ํ์ต๋๋ค.
FlowRow
๋ ์์ดํ
์ ๊ฐ๋ก๋ก ๋ฐฐ์นํ ์ ์๋ ๋ ์ด์์์ผ๋ก, ์์ดํ
์ ํฌ๊ธฐ๋ ๊ฐ์๊ฐ ๊ฐ๋ณ์ ์ผ ๋ ์ด๋ฅผ ์๋์ผ๋ก ์กฐ์ ํ์ฌ ํ๋ฉด ํฌ๊ธฐ์ ๋ง๊ฒ ๋์ ์ผ๋ก ๋ฐฐ์นํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
๋ฐ๋ผ์ ๋ค์ ์ฝ๋์ ๊ฐ์ด FlowRow
๋ฅผ ํ์ฉํ์ฌ ํ๋ฉด์ ๋๋น์ ๋ฐ๋ผ ํ ์ค(row)์ ๋ณด์ฌ์ค ์์ดํ
์ ๊ฐ์(itemInRow)๋ฅผ ๊ฒฐ์ ํ๊ณ , ์์ดํ
์ฌ์ด์ ๊ฐ๊ฒฉ(space)์ ๊ณ์ฐํ์ฌ ๋ค์ํ ๋๋ฐ์ด์ค ํ๋ฉด์ ๋์ ์ผ๋ก ์์ดํ
์ ๋ฐฐ์นํ์ฌ ๋์ํ ์ ์๋๋ก ํ์์ต๋๋ค.
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@Composable
fun FavoriteContent() {
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp
val itemInRow = if (screenWidth > 490) 4 else if (screenWidth < 360) 2 else 3
val space = (screenWidth - 48 - (itemInRow * 98)) / (itemInRow - 1)
LazyColumn() {
// 1. TextHeader
item {
TextHeader()
}
// 2. SearchTextField
stickyHeader {
SearchTextField()
}
// 3. FavoriteList
item {
FlowRow(
maxItemsInEachRow = itemInRow,
horizontalArrangement = Arrangement.spacedBy(space.dp),
) {
}
}
}
}
ํ์ง๋ง stickyHeader
ํจ์์ ๊ฒฝ์ฐ @ExperimentalFoundationApi
๋ก ํฅํ ๋ณ๊ฒฝ๋๊ฑฐ๋ ์ ๊ฑฐ๋ ๊ฐ๋ฅ์ฑ์ด ์์๊ณ , FlowRow
๋ ๋ง์ฐฌ๊ฐ์ง๋ก @ExperimentalLayoutApi
๋ก ์คํ์ ์ธ ๋จ๊ณ๋ผ ์์ง ์์ ์ ์ด์ง ์๋ค๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
ํนํ FlowRow
๋ ์ง์ฐ(Lazy) ๋ ์ด์์์ด ์๋๋ค ๋ณด๋ ์คํฌ๋กค ๊ฐ๋ฅํ ์์ดํ
๋ค๋ง์ ํ๋ฉด์ ํ์ํ๋ ๊ฒ์ด ์๋๋ผ ํ ๋ฒ์ ๋ชจ๋ ๋ชฉ๋ก์ ๋ ๋๋งํ๋ ๋นํจ์จ์ด ์์์ต๋๋ค.
๋ํ ๋๋ฐ์ด์ค์ ํฌ๊ธฐ์ ๋ฐ๋ผ ๋์ ์ผ๋ก ์์ดํ
์ ๋ฐฐ์นํ๋ค ๋ณด๋ UI๋ฅผ ๊ทธ๋ฆฌ๋ ๋ฐ ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ ธ์ต๋๋ค.
LazyVerticalGrid๋ฅผ ํ์ฉํ ๊ฐ์
๊ทธ๋์ ์์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด LazyColumn
๊ณผ FlowRow
๋์ LazyVerticalGrid
๋ฅผ ์ฌ์ฉํ๋๋ก ๊ฐ์ ํ์ต๋๋ค.
LazyVerticalGrid
๋ FlowRow
์ ๊ฐ์ด ๊ทธ๋ฆฌ๋ ํํ๋ก ์์ดํ
์ ๋ฐฐ์นํ๋ฉด์๋ Lazy ํค์๋์์ ์ ์ ์๋ฏ์ด ํ๋ฉด์ ํ์๋๋ ํญ๋ชฉ๋ง ๊ตฌ์ฑํ์ฌ ๋ ํจ์จ์ ์ผ๋ก ๋์ํ์ต๋๋ค.
๋ํ ๋๋ฐ์ด์ค์ ํฌ๊ธฐ์ ๋ฐ๋ผ ๋์ ์ผ๋ก ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํ์ฌ ์์ดํ
์ ๋ฐฐ์นํ์ง ์์๋ ๋๊ธฐ ๋๋ฌธ์ ๋ก๋ฉ์๊ฐ์ ๋จ์ถ์ํฌ ์ ์์์ต๋๋ค.
์ค์ ๋ก Macrobenchmark ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๊ฐ์์ ๋ฐ๋ผ ๋ก๋๋๊ธฐ๊น์ง ๊ฑธ๋ฆฌ๋ ์๊ฐ์ ์ธก์ ํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. 10๋ฒ ์ธก์ ํ ๊ฒฐ๊ณผ์ ํ๊ท ๊ฐ์ ํ์ํ์์ต๋๋ค.
300๊ฐ | 500๊ฐ | 1000๊ฐ | |
---|---|---|---|
FlowRow | 364ms | 450ms | 663ms |
LazyVerticalGrid | 299ms | 301ms | 300ms |
300์ฌ ๊ฐ์ ์์ดํ ์ ๊ทธ๋ฆฌ๋ ๋ฐ ํ๊ท 364ms ์์๋๋ ์๊ฐ์ด 299ms๋ก ์ฝ 18% ๋จ์ถ๋์์ต๋๋ค. ํนํ LazyVerticalGrid๋ ์์ดํ ์ ์๊ฐ ์ฆ๊ฐํด๋ ์ผ์ ํ ์๊ฐ ๋ด์ ํ๋ฉด์ ๊ทธ๋ฆด ์ ์์์ง๋ง, FlowRow๋ ์์ดํ ์ ์๊ฐ ์ฆ๊ฐํ ์๋ก ๊ทธ๋ฆฌ๋ ์๊ฐ์ด ์ฆ๊ฐํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์์ดํ ์ ๊ฐ์๊ฐ 1000๊ฐ์ผ ๋๋ ๊ฑฐ์ 2๋ฐฐ ์ด์ ์ฐจ์ด๊ฐ ๋ฌ์ต๋๋ค.
ํ์ง๋ง LazyVerticalGrid
๋ stickyHeader
๋ฉ์๋๋ฅผ ์ ๊ณตํ์ง ์๊ธฐ ๋๋ฌธ์ SearchTextField๊ฐ ์๋จ์ ๊ณ ์ ๋์ด์ผ ํ๋ ์๊ตฌ์ฌํญ์ ๋ง์กฑ์ํค๊ธฐ ์ํด ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ์์ผ ํ์ต๋๋ค.
๊ทธ๋์ ์ฐพ์ ๋ฐฉ๋ฒ์ด Box
๋ฅผ ํ์ฉํ์ฌ ์ปดํฌ๋ํธ๋ค์ ๊ฒน์ณ์ ํ์ํ๊ณ , LazyVerticalGrid
์ scrollState๋ฅผ ์ฌ์ฉํ์ฌ TextHeader์ SearchTextField์ ์์น๋ฅผ ์กฐ์ ํ๋ ๊ฒ์ด์์ต๋๋ค.
scrollState์ ์ ๋ณด๋ฅผ ์ด์ฉํ์ฌ offset์ ๊ณ์ฐํ์ฌ ์ฌ์ฉ์๊ฐ ํ๋ฉด์ ์๋๋ก ์คํฌ๋กคํ ๋๋ ํค๋๊ฐ ์คํฌ๋กค์ ๋ฐ๋ผ ๋ถ๋๋ฝ๊ฒ ์ด๋ํ๋ค๊ฐ, TextHeader์ ๋์ด๋งํผ ์คํฌ๋กค๋๋ฉด TextHeader๋ ํ๋ฉด ์์ชฝ์ผ๋ก ์์ ํ ์จ๊ฒจ์ง๊ณ SearchTextField๋ง ํ๋ฉด์ ๋จ๋๋ก ๊ตฌํํ์์ต๋๋ค.
๊ทธ๋์ SearchTextField๊ฐ stickyHeader ์ญํ ์ ํ๋ฉด์ ๋ฆฌ์คํธ๊ฐ ์คํฌ๋กค๋์ด๋ ์๋จ์ ๊ณ ์ ๋ ์ ์์์ต๋๋ค.
์ด๋ฅผ ๊ตฌํํ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
@Composable
private fun FavoriteContent() {
val headerMaxHeight = 110.dp.dpToPx().roundToInt()
val scrollState = rememberLazyGridState()
val headerOffset: Int by remember {
derivedStateOf {
if (scrollState.firstVisibleItemIndex == 0) {
-min(scrollState.firstVisibleItemScrollOffset, headerMaxHeight)
} else {
-headerMaxHeight
}
}
}
Box() {
// 3. FavoriteList
LazyVerticalGrid(
contentPadding = PaddingValues(top = 212.dp, bottom = 40.dp),
state = scrollState,
...
)
FavoriteHeaderContent(headerOffset)
}
}
@Composable
private fun FavoriteHeaderContent(headerOffset: Int) {
Column(
modifier = Modifier
.offset { IntOffset(x = 0, y = headerOffset) }
) {
// 1. TextHeader
TextHeader(modifier = Modifier.height(110.dp))
// 2. SearchTextField
SearchTextField(modifier = Modifier.height(40.dp))
}
}
ํ์ง๋ง ์ด ๋ฐฉ๋ฒ๋ ๋ชจ๋ ์์์ ๋์ด๊ฐ ์์๋ก ๊ณ ์ ๋์ด ์์ด์ ์ข์ ํด๊ฒฐ๋ฐฉ๋ฒ์ ์๋์์ต๋๋ค. ์ค์ ๋ก ํค๋์ ๋์ด๊ฐ ๊ณ ์ ๋์ด ์๋๋ฐ ํ ์คํธ์ ๊ธธ์ด๊ฐ ๊ธธ์ด๊ฐ ์ค๋ฐ๊ฟ์ ํ๊ฒ ๋๋ฉด ๋ด์ฉ์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ณผ ์ ์๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค. ๋์์ธ ์์ ์ ํ๊ธ์ ๊ธฐ์ค์ผ๋ก ํ๊ธฐ ๋๋ฌธ์ ํ๊ธ์์๋ ๋ฌธ์ ๊ฐ ์์์ง๋ง, ์ผ๋ณธ์ด๋ก ์ธ์ด๋ฅผ ๋ฐ๊พธ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ ์คํธ๊ฐ ์๋ ค๋ณด์์ต๋๋ค.
๋ฐ๋ผ์ ํค๋์ ๋์ด๋ฅผ ์ ๋์ ์ผ๋ก ์กฐ์ ํ์ฌ ํ ์คํธ ๊ธธ์ด์ ๋์ํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ฐพ์์ผ ํ์ต๋๋ค.
Layout ์ปดํฌ์ ๋ธ์ ํ์ฉํ ์ต์ข ๊ฐ์
์ฐ์ ์คํฌ๋กค ์์น๋ฅผ ์ด์ฉํ์ฌ FavoriteHeaderContent์ LazyVerticalGrid
์ ์์น๋ฅผ ์กฐ์ ํ๋ค๋ ์ฃผ์ ์์ด๋์ด๋ ๊ฐ์์ต๋๋ค.
๋ค๋ง, TextHeader์ ๋์ด๋ฅผ 110dp๋ก ๊ณ ์ ํ๋ ๋์ ์ wrapContentHeight
๋ก ๋ณ๊ฒฝํ์ฌ ํ
์คํธ์ ๊ธธ์ด์ ๋ฐ๋ผ ๋์ด๊ฐ ์ ํด์ง๋๋ก ํ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ Box
๋์ ์ Layout
์ปดํฌ์ ๋ธ์ ์ฌ์ฉํ์ฌ ์์ ์์๋ค์ ๋ฐฐ์นํ๊ณ , ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํ์ฌ ๋์ ์ผ๋ก ์กฐ์ ํ ์ ์๋๋ก ํ์์ต๋๋ค.
๋ณ๊ฒฝ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
@Composable
private fun FavoriteContent() {
val scrollState = rememberLazyGridState()
FavoriteContentLayout(
firstVisibleItemIndex = {
scrollState.firstVisibleItemIndex
},
firstVisibleItemScrollOffset = {
scrollState.firstVisibleItemScrollOffset
},
) {
// 3. FavoriteList
LazyVerticalGrid(
contentPadding = PaddingValues(top = HeaderGradientHeight, bottom = HeaderFixedHeight + 40.dp),
state = scrollState,
...
)
Spacer(modifier = Modifier.fillMaxWidth().height(HeaderGradientHeight))
FavoriteHeaderContent()
}
}
@Composable
private fun FavoriteHeaderContent(modifier: Modifier = Modifier) {
Column(
modifier = modifier
) {
// 1. TextHeader
TextHeader(modifier = Modifier.wrapContentHeight())
// 2. SearchTextField
SearchTextField()
}
}
์ฌ๊ธฐ์ FavoriteContentLayout์ Layout
์ปดํฌ์ ๋ธ์ ์ฌ์ฉํ์ฌ ๊ตฌํ๋์๋๋ฐ, Layout
์ปดํฌ์ ๋ธ์ Compose UI์ ๊ธฐ๋ณธ ์์์
๋๋ค.
๋ฐ๋ผ์ Compose์์ ์ ๊ณตํ๋ Column
, Row
, Box
๋ฑ ๊ธฐ๋ณธ์ ์ธ ๋ ์ด์์์ด ๋ชจ๋ ์ด Layout
์ปดํฌ์ ๋ธ์ ์ฌ์ฉํ์ฌ ๊ตฌํ๋์ด ์์ต๋๋ค.
Layout
์ ํ์ฉํ๋ฉด ๊ธฐ๋ณธ์ ์ธ ๋ ์ด์์ ์ด์ธ์๋ ์ํ๋ ํํ๋ก ํ๋ฉด์ ์ข ๋ ์ปค์คํ
ํ๊ฒ ์ ์ดํ๊ณ ๋ค์ํ ๋์์ธ์ ๊ตฌํํ ์ ์์ด ์ฌ์ฉํ๊ฒ ๋์์ต๋๋ค.
๊ทธ๋ผ Layout
์ ์ด๋ป๊ฒ ์ปค์คํ
๋ ์ด์์ ๊ตฌํ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๊ฒ์ผ๊น์?
์ด๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ Compose์์ UI๋ฅผ ๊ทธ๋ฆฌ๋ ๊ณผ์ ์ ์์์ผ ํฉ๋๋ค.
Compose๋ ๊ตฌ์ฑ(Composition), ๋ ์ด์์(Layout), ๊ทธ๋ฆฌ๊ธฐ(Drawing)์ ์ธ ๋จ๊ณ๋ฅผ ๊ฑฐ์ณ ์ํ๋ฅผ UI๋ก ๋ณํํฉ๋๋ค. ์ด ๋, Layout ๋จ๊ณ์์๋ ๊ตฌ์ฑ ๋จ๊ณ์์ ์์ฑํ UI ํธ๋ฆฌ๋ฅผ ์ํํ๋ฉฐ ์์์ ์ธก์ (measure)ํ๊ณ , ์์ ์ ํฌ๊ธฐ(size)๋ฅผ ๊ฒฐ์ ํ ๋ค์ ์์์ ๋ฐฐ์น(place)ํฉ๋๋ค. Layout ์ปดํฌ์ ๋ธ์ ์ฌ์ฉํ๋ฉด ์ด ๋จ๊ณ๋ฅผ ์ง์ ์ ์ดํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ ์ ์ฐํ๊ณ ์ธ๋ฐํ ๋ ์ด์์์ ๊ตฌํํ ์ ์์ต๋๋ค.
๊ทธ๋ผ ๋ค์ ์ฝ๋๋ก ๋์๊ฐ FavoriteContentLayout ๋ด๋ถ ๊ตฌํ์ ํตํด ์ด ๋จ๊ณ๋ฅผ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ฃผ๋ชฉํด์ ๋ณผ ๋ถ๋ถ์ Layout
์ ๋ง์ง๋ง ํ๋ผ๋ฏธํฐ์ธ measurePolicy ๋๋ค์
๋๋ค.
์ฌ๊ธฐ์ ๋ ์ด์์์ ์ธก์ ๊ณผ ๋ฐฐ์น๋ฅผ ์ํํ๋ ๋ก์ง์ ์ ์ํ๊ณ ์์ต๋๋ค.
@Composable
private fun FavoriteContentLayout(
firstVisibleItemScrollOffset: () -> Int,
firstVisibleItemIndex: () -> Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val fixedHeight = HeaderFixedHeight.dpToPx().roundToInt()
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
val gridPlaceable = measurables.first { it.layoutId == GridLayoutID }.measure(constraints)
val spacerPlaceable = measurables.first { it.layoutId == SpacerLayoutID }.measure(constraints)
val headerPlaceable = measurables.first { it.layoutId == HeaderLayoutID }.measure(constraints)
val scrollIndex = firstVisibleItemIndex()
val scrollOffset = firstVisibleItemScrollOffset()
val dynamicHeight = headerPlaceable.height - fixedHeight
val offset = if (scrollIndex == 0) {
-min(scrollOffset, dynamicHeight)
} else {
-dynamicHeight
}
layout(
width = constraints.maxWidth,
height = constraints.maxHeight,
) {
gridPlaceable.placeRelative(10, headerPlaceable.height + offset)
headerPlaceable.placeRelative(0, offset)
spacerPlaceable.placeRelative(0, headerPlaceable.height + offset)
}
}
}
๋๋ค์์ ๋์ด์ค๋ measurables
๋ ๋ ์ด์์์ ํฌํจ๋ ์์ ์์๋ค์ ๋ํ๋ด๋๋ฐ, layoutId๋ฅผ ํตํด ๊ฐ ์์๋ฅผ ์๋ณํ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋์ ๋ ์ด์์์ ๋๋น์ ๋์ด ๋ฑ์ ์ ํํ๋ ์ ์ฝ ์กฐ๊ฑด์ธ constraints
์ ํจ๊ป measure()
๋ฅผ ํตํด ๊ฐ ์์๋ฅผ ์ธก์ ํ๊ณ ํด๋น ํฌ๊ธฐ๋ฅผ Placeable
๊ฐ์ฒด๋ก ๋ฐํํฉ๋๋ค.
Placeable
์ ์ธก์ ์ด ๋ ์์์ผ๋ก ํฌ๊ธฐ๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, placeRelative()
๋ฅผ ํตํด ์ํ๋ ์์น์ ๋ฐฐ์นํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์๋ measure()
๋ฅผ ํ ๋ค์ headerPlaceable.height
๋ฅผ ํตํด ํค๋์ ๋์ด๋ฅผ ์ป๊ณ , ํ์ฌ ์คํฌ๋กค ์์น์ธ scrollOffset
๊ณผ ์ฒซ ๋ฒ์งธ ๋ณด์ด๋ ์์ดํ
์ ์ธ๋ฑ์ค์ธ scrollIndex
๋ฅผ ๊ธฐ๋ฐ์ผ๋ก offset
๋ณ์๋ฅผ ๊ณ์ฐํ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ layout()
ํจ์์์ offset
์ ์ด์ฉํด ๊ฐ ์์๋ค์ด ์คํฌ๋กค ์์น์ ๋ฐ๋ผ ์ ์ ํ๊ฒ ๋ฐฐ์น๋๋๋ก ํ์์ต๋๋ค.
์ด๋ ๊ฒ Layout
์ ์ฌ์ฉํจ์ผ๋ก์จ ์คํฌ๋กค๊ณผ ๊ฐ์ ์ฌ์ฉ์ ์ํธ ์์ฉ์ ๋ฐ๋ผ ์์๋ค์ ์ด๋์์ผ ๋์์ธ ์๊ตฌ์ฌํญ์ ๋ง์กฑํ ์ ์์์ต๋๋ค.
๋ง๋ฌด๋ฆฌ
์ด๋ฒ ๊ธ์์๋ Jetpack Compose๋ฅผ ์ฌ์ฉํ ๊ด์ฌ ์ํฐ์คํธ ์ ํ ํ๋ฉด ๊ตฌํ ๊ณผ์ ์์ ๋ฐ์ํ ์ฑ๋ฅ ๋ฐ ๋์์ธ ์ด์๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๊ณต์ ํ์ต๋๋ค.
์ด๊ธฐ์๋ LazyColumn
๊ณผ FlowRow
๋ฅผ ์ฌ์ฉํ์ฌ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑ์์ผฐ์ผ๋, ExperimentalApi์ ๋ถ์์ ์ฑ๊ณผ ์ฑ๋ฅ ์ ํ ๋ฌธ์ ๋ก ์ธํด LazyVerticalGrid
๋ก ์ ํํ์ต๋๋ค.
๊ทธ ๊ณผ์ ์์ stickyHeader ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด wrapContentHeight()
๋ฅผ ์ ์ฉํ๊ณ , Layout
์ปดํฌ์ ๋ธ์ ์ฌ์ฉํ์ฌ ๋์ ์ธ ๋ ์ด์์ ์กฐ์ ์ ๊ฐ๋ฅํ๊ฒ ํ์ต๋๋ค.
์ด๋ก์จ ๋ค์ํ ๋๋ฐ์ด์ค ํฌ๊ธฐ์ ์ธ์ด ์ค์ ์์๋ ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์๊ฒ ๋์๊ณ , ์ฑ๋ฅ๋ ๊ฐ์ ํ ์ ์์์ต๋๋ค.
์์ผ๋ก๋ ๊พธ์คํ Compose์ ๋ํด ํ์ตํ๋ฉด์ ๋ ๋์ UI/UX๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ๋ ธ๋ ฅํ๊ณ ์ ํฉ๋๋ค. ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค.