[Android/Kotlin] Compose Layout(1)
함수 뜯어보고 탐구하기
Compose Pager
화면 내에서 페이지를 좌우 또는 상하로 스크롤하여 콘텐츠를 탐색할 수 있도록 돕는 Components입니다.
종류는 다음과 같습니다.
- HorizontalPager
- VerticalPager
이 컴포넌트들은 콘텐츠를 지연 로딩하여 필요할 때만 페이지를 생성함으로써 성능을 최적화 합니다.
( 사용자가 페이지를 스크롤하면 컴포저블은 더 이상 필요하지 않은 페이지를 삭제합니다. 기본적으로 페이저는 화면에 표시되는 페이지만 로드합니다. 화면 외부에 더 많은 페이지를 로드하려면 beyondBoundsPageCount를 0 보다 큰 값을 설정해야 합니다.)
HorizontalPager는 화면의 전체 너비를 차지하고, VerticalPager는 전체 높이를 차지하며 페이저는 한 번에 한 페이지만 플링합니다.
val pagerState = rememberPagerState(pageCount = {
10
})
HorizontalPager(state = pagerState) { page ->
// Our page content
Text(
text = "Page: $page",
modifier = Modifier.fillMaxWidth()
)
}
val pagerState = rememberPagerState(pageCount = {
10
})
VerticalPager(state = pagerState) { page ->
// Our page content
Text(
text = "Page: $page",
modifier = Modifier.fillMaxWidth()
)
}
페이저에서 특정 페이지로 스크롤하려면 아래와 같은 rememberPagerState()를 사용하여 PagerState 객체를 만들고
이를 페이저에 state 매개변수로 전달해야 합니다. 이 상태에서 CoroutineScope 내에서 PagerState.scrollToPage() 함수를 선언해줘야 합니다.
Scroll시 Animation을 추가하려면 animateScrollToPage로 설정하여 적용시킵니다.
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
// Call scroll to on pagerState
pagerState.scrollToPage(5)
// Animation
pagerState.animateScrollToPage(5)
}
})
페이지 상태 변경에 대한 알림 받기를 할려면
PagerState에 페이지에 관한 정보가 포함된 세가지 속성을 활용 해야 합니다.
- currentPage: 스냅 위치에 가장 가까운 페이지입니다. 기본적으로 스냅 위치는 레이아웃의 시작 부분에 있습니다.
- settledPage: 애니메이션이나 스크롤이 실행되지 않는 페이지 번호입니다. 이는 currentPage 속성과는 다릅니다. currentPage는 페이지가 스냅 위치에 충분히 가까우면 즉시 업데이트되지만 settledPage는 모든 애니메이션이 실행을 완료할 때까지 동일하게 유지됩니다.
- targetPage: 스크롤 동작의 제안된 중지 위치입니다.
snapshotFlow 함수를 사용하여 이러한 변수의 변경사항을 관찰하고 이에 반응할 수 있습니다. 예를 들어 각 페이지 변경 시 애널리틱스 이벤트를 전송하려면 다음을 수행합니다.
LaunchedEffect(pagerState) {
// Collect from the a snapshotFlow reading the currentPage
snapshotFlow { pagerState.currentPage }.collect { page ->
// Do something with each page change, for example:
// viewModel.sendPageSelectedEvent(page)
Log.d("Page change", "Page changed to $page")
}
}
페이지 표시기 추가하기 위해선 pagerState.currentPage를 사용하여 활용합니다.
repeat(pagerState.pageCount) { iteration ->
if (pagerState.currentPage == iteration){
// Something
}
}
페이지가 현재 선택된 페이지에서 얼마나 멀리 떨어져 있는지 확인하려면 PagerState.currentPageOffsetFraction를 사용해야 한다. 항목의 불투명도를 조정할려면, Modifier.graphicsLayer를 사용하여 alpha를 변경합니다.
Modifier
.size(200.dp)
.graphicsLayer {
// Calculate the absolute offset for the current page from the
// scroll position. We use the absolute value which allows us to mirror
// any effects for both directions
val pageOffset = (
(pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction
).absoluteValue
// We animate the alpha, between 50% and 100%
alpha = lerp(
start = 0.5f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
)
}
pageSize의 변수를 활용하여 fixed, fill(default) 또는 맞춤 크기를 갖도록 설정할 수 있습니다.
HorizontalPager(
state = pagerState,
pageSize = PageSize.Fixed(100.dp)
)
기본적으로 플링 동작으로 한 번에 스크롤할 수 있는 최대 페이지 수를 1페이지로 설정합니다. 이를 변경하려면, flingBehavior에서 pagerSnapDistance를 설정해야합니다.
val fling = PagerDefaults.flingBehavior(
state = pagerState,
pagerSnapDistance = PagerSnapDistance.atMost(10)
)
Column(modifier = Modifier.fillMaxSize()) {
HorizontalPager(
state = pagerState,
pageSize = PageSize.Fixed(200.dp),
beyondViewportPageCount = 10,
flingBehavior = fling
) {
PagerSampleItem(page = it)
}
}
Flow Layout
FlowRow 및 FlowColumn는 Row 및 Column와 유사한 컴포저블이지만 컨테이너의 공간이 부족하면 항목이 다음 줄로 이동한다는 점에서 다릅니다. 그러면 여러 행 또는 열이 생성됩니다.
maxItemsInEachRow 또는 maxItemsInEachColumn를 설정하여 줄에 있는 항목 수를 제어할 수도 있습니다. FlowRow 및 FlowColumn를 사용하여 반응형 레이아웃을 빌드하는 경우가 많습니다.
FlowRow(modifier = Modifier.padding(8.dp)) {
ChipItem("Price: High to Low")
ChipItem("Avg rating: 4+")
ChipItem("Free breakfast")
ChipItem("Free cancellation")
ChipItem("£50 pn")
}
ContextualFlowRow 및 ContextualFlowColumn는 흐름 행 또는 열의 콘텐츠를 지연 로드할 수 있는 FlowRow 및 FlowColumn의 특수 버전입니다. 또한 항목이 첫 번째 행에 있는지 여부와 같은 항목 위치(색인, 행 번호, 사용 가능한 크기)에 관한 정보도 제공합니다. 이는 대규모 데이터 세트와 항목에 관한 문맥 정보가 필요한 경우에 유용합니다.
maxLines 매개변수는 표시되는 행 수를 제한하며 overflow 매개변수는 항목 오버플로에 도달했을 때 표시할 항목을 지정하므로 커스텀 expandIndicator 또는 collapseIndicator를 지정할 수 있습니다.
* maxLines는 FlowRow 및 FlowColumn에서 사용할 수 있지만, ContextualFlowRow 및 ContextualFlowColumn는 하위 컴포지션을 사용하여 레이아웃 정보를 결정하므로 모든 단계에서 shownItemCount와 같은 레이아웃 정보에 액세스할 수 있습니다. 그리기 단계에 있는 경우에만 FlowRow에서 shownItemCount에 액세스할 수 있습니다. 이 정보가 필요한지 여부와 언제 필요한지 고려하여 두 가지 중에서 선택하세요.
val totalCount = 40
var maxLines by remember {
mutableStateOf(2)
}
val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
val remainingItems = totalCount - scope.shownItemCount
ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
if (remainingItems == 0) {
maxLines = 2
} else {
maxLines += 5
}
})
}
ContextualFlowRow(
modifier = Modifier
.safeDrawingPadding()
.fillMaxWidth(1f)
.padding(16.dp)
.wrapContentHeight(align = Alignment.Top)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
maxLines = maxLines,
overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
minRowsToShowCollapse = 4,
expandIndicator = moreOrCollapseIndicator,
collapseIndicator = moreOrCollapseIndicator
),
itemCount = totalCount
) { index ->
ChipItem("Item $index")
}
그리고 항상 크기가 일정하게 설정하기 위해서는 fillMaxColumnWidth() 및 fillMaxRowHeight()의 설정이 필수적입니다.