콘텐츠를 왼쪽과 오른쪽 또는 위와 아래로 스크롤하려면 각각 HorizontalPager
및 VerticalPager
컴포저블을 사용하면 됩니다. 이러한 컴포저블은 뷰 시스템의 ViewPager
와 유사한 기능을 갖습니다. 기본적으로 HorizontalPager
는 화면의 전체 너비를 차지하고 VerticalPager
는 전체 높이를 차지하며 페이저는 한 번에 하나의 페이지만 플링합니다. 이러한 기본값은 모두 구성할 수 있습니다.
HorizontalPager
왼쪽과 오른쪽으로 가로 스크롤하는 페이저를 만들려면 HorizontalPager
를 사용하세요.
// Display 10 items val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier.fillMaxWidth() ) }
VerticalPager
위아래로 스크롤하는 페이저를 만들려면 VerticalPager
를 사용합니다.
// Display 10 items val pagerState = rememberPagerState(pageCount = { 10 }) VerticalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier.fillMaxWidth() ) }
지연 생성
HorizontalPager
및 VerticalPager
의 페이지는 필요할 때 지연 컴포지션되고 레이아웃됩니다. 사용자가 페이지를 스크롤하면 컴포저블은 더 이상 필요하지 않은 페이지를 삭제합니다.
화면 밖에 페이지 더 로드
기본적으로 페이저는 화면에 표시되는 페이지만 로드합니다. 화면 밖에 더 많은 페이지를 로드하려면 beyondBoundsPageCount
를 0보다 큰 값으로 설정합니다.
페이저에서 항목으로 스크롤
페이저에서 특정 페이지로 스크롤하려면 rememberPagerState()
를 사용하여 PagerState
객체를 만들고 이를 페이저에 state
매개변수로 전달합니다. 이 상태에서 CoroutineScope
내에서 PagerState#scrollToPage()
를 호출할 수 있습니다.
val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier .fillMaxWidth() .height(100.dp) ) } // scroll to page val coroutineScope = rememberCoroutineScope() Button(onClick = { coroutineScope.launch { // Call scroll to on pagerState pagerState.scrollToPage(5) } }, modifier = Modifier.align(Alignment.BottomCenter)) { Text("Jump to Page 5") }
페이지로 애니메이션을 적용하려면 PagerState#animateScrollToPage()
함수를 사용하세요.
val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier .fillMaxWidth() .height(100.dp) ) } // scroll to page val coroutineScope = rememberCoroutineScope() Button(onClick = { coroutineScope.launch { // Call scroll to on pagerState pagerState.animateScrollToPage(5) } }, modifier = Modifier.align(Alignment.BottomCenter)) { Text("Jump to Page 5") }
페이지 상태 변경에 대한 알림 받기
PagerState
에는 페이지에 관한 정보가 포함된 세 가지 속성(currentPage
, settledPage
, targetPage
)이 있습니다.
currentPage
: 스냅 위치에 가장 가까운 페이지입니다. 기본적으로 스냅 위치는 레이아웃 시작 부분에 있습니다.settledPage
: 애니메이션이나 스크롤이 실행되지 않는 페이지 번호입니다. 이는currentPage
속성과는 달리 페이지가 스냅 위치에 충분히 가까우면currentPage
가 즉시 업데이트되지만settledPage
는 모든 애니메이션이 실행 완료될 때까지 동일하게 유지된다는 점에서 다릅니다.targetPage
: 스크롤 동작의 제안된 중지 위치입니다.
snapshotFlow
함수를 사용하여 이러한 변수의 변경사항을 관찰하고 이에 반응할 수 있습니다. 예를 들어 각 페이지 변경 시 애널리틱스 이벤트를 전송하려면 다음을 실행하면 됩니다.
val pagerState = rememberPagerState(pageCount = { 10 }) 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") } } VerticalPager( state = pagerState, ) { page -> Text(text = "Page: $page") }
페이지 표시기 추가
페이지에 표시기를 추가하려면 PagerState
객체를 사용하여 페이지 중에서 선택된 페이지에 관한 정보를 가져오고 맞춤 표시기를 그립니다.
예를 들어 간단한 원 표시기가 필요한 경우 pagerState.currentPage
를 사용하여 원 수를 반복하고 페이지가 선택되었는지에 따라 원 색상을 변경할 수 있습니다.
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize() ) { page -> // Our page content Text( text = "Page: $page", ) } Row( Modifier .wrapContentHeight() .fillMaxWidth() .align(Alignment.BottomCenter) .padding(bottom = 8.dp), horizontalArrangement = Arrangement.Center ) { repeat(pagerState.pageCount) { iteration -> val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray Box( modifier = Modifier .padding(2.dp) .clip(CircleShape) .background(color) .size(16.dp) ) } }
콘텐츠에 항목 스크롤 효과 적용
일반적인 사용 사례는 스크롤 위치를 사용하여 페이저 항목에 효과를 적용하는 것입니다. 페이지가 현재 선택된 페이지에서 얼마나 떨어져 있는지 확인하려면 PagerState.currentPageOffsetFraction
를 사용하면 됩니다.
그런 다음 선택한 페이지와의 거리를 기준으로 콘텐츠에 변환 효과를 적용할 수 있습니다.
예를 들어 항목이 중심에서 얼마나 떨어져 있는지에 따라 항목의 불투명도를 조정하려면 페이저 내의 항목에서 Modifier.graphicsLayer
를 사용하여 alpha
를 변경합니다.
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager(state = pagerState) { page -> Card( 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) ) } ) { // Card content } }
맞춤 페이지 크기
기본적으로 HorizontalPager
및 VerticalPager
는 각각 전체 너비 또는 전체 높이를 차지합니다. pageSize
변수를 Fixed
, Fill
(기본값) 또는 맞춤 크기 계산을 갖도록 설정할 수 있습니다.
예를 들어 고정 너비 페이지를 100.dp
로 설정하려면 다음을 실행합니다.
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, pageSize = PageSize.Fixed(100.dp) ) { page -> // page content }
표시 영역 크기를 기준으로 페이지 크기를 조정하려면 맞춤 페이지 크기 계산을 사용하세요. 맞춤 PageSize
객체를 만들고 항목 간의 간격을 고려하여 availableSpace
를 3으로 나눕니다.
private val threePagesPerViewport = object : PageSize { override fun Density.calculateMainAxisPageSize( availableSpace: Int, pageSpacing: Int ): Int { return (availableSpace - 2 * pageSpacing) / 3 } }
콘텐츠 패딩
HorizontalPager
와 VerticalPager
는 모두 콘텐츠 패딩 변경을 지원하므로 페이지의 최대 크기와 정렬에 영향을 줄 수 있습니다.
예를 들어 start
패딩을 설정하면 페이지가 맨 끝에 정렬됩니다.
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(start = 64.dp), ) { page -> // page content }
start
및 end
패딩을 모두 동일한 값으로 설정하면 항목이 가로로 가운데에 정렬됩니다.
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(horizontal = 32.dp), ) { page -> // page content }
end
패딩을 설정하면 페이지가 시작 부분에 정렬됩니다.
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(end = 64.dp), ) { page -> // page content }
top
및 bottom
값을 설정하여 VerticalPager
에 유사한 효과를 줄 수 있습니다. 32.dp
값은 여기서 예시로만 사용됩니다. 각 패딩 크기를 원하는 값으로 설정할 수 있습니다.
스크롤 동작 맞춤설정
기본 HorizontalPager
및 VerticalPager
컴포저블은 스크롤 동작이 페이저에서 작동하는 방식을 지정합니다. 하지만 pagerSnapDistance
또는 flingBehavior
와 같은 기본값은 맞춤설정하고 변경할 수 있습니다.
맞추기 거리
기본적으로 HorizontalPager
및 VerticalPager
는 플링 동작으로 한 번에 스크롤할 수 있는 최대 페이지 수를 1페이지로 설정합니다. 이를 변경하려면 flingBehavior
에서 pagerSnapDistance
를 설정하세요.
val pagerState = rememberPagerState(pageCount = { 10 }) 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) } }
자동 전진 페이저 만들기
이 섹션에서는 Compose에서 페이지 표시기가 있는 자동 전진 페이저를 만드는 방법을 설명합니다. 항목 모음이 자동으로 가로로 스크롤되지만 사용자는 항목 간에 수동으로 스와이프할 수도 있습니다. 사용자가 페이저와 상호작용하면 자동 진행이 중지됩니다.
기본 예
다음 스니펫을 함께 사용하면 각 페이지가 다른 색상으로 렌더링되는 시각적 표시기가 있는 기본 자동 전진 페이저 구현을 만들 수 있습니다.
@Composable fun AutoAdvancePager(pageItems: List<Color>, modifier: Modifier = Modifier) { Box(modifier = Modifier.fillMaxSize()) { val pagerState = rememberPagerState(pageCount = { pageItems.size }) val pagerIsDragged by pagerState.interactionSource.collectIsDraggedAsState() val pageInteractionSource = remember { MutableInteractionSource() } val pageIsPressed by pageInteractionSource.collectIsPressedAsState() // Stop auto-advancing when pager is dragged or one of the pages is pressed val autoAdvance = !pagerIsDragged && !pageIsPressed if (autoAdvance) { LaunchedEffect(pagerState, pageInteractionSource) { while (true) { delay(2000) val nextPage = (pagerState.currentPage + 1) % pageItems.size pagerState.animateScrollToPage(nextPage) } } } HorizontalPager( state = pagerState ) { page -> Text( text = "Page: $page", textAlign = TextAlign.Center, modifier = modifier .fillMaxSize() .background(pageItems[page]) .clickable( interactionSource = pageInteractionSource, indication = LocalIndication.current ) { // Handle page click } .wrapContentSize(align = Alignment.Center) ) } PagerIndicator(pageItems.size, pagerState.currentPage) } }
코드 관련 핵심 사항
AutoAdvancePager
함수는 자동 전진이 포함된 가로 페이징 뷰를 만듭니다. 각 페이지의 배경 색상으로 사용되는Color
객체 목록을 입력으로 사용합니다.pagerState
는 페이저의 상태를 보유하는rememberPagerState
를 사용하여 생성됩니다.pagerIsDragged
및pageIsPressed
는 사용자 상호작용을 추적합니다.LaunchedEffect
는 사용자가 페이저를 드래그하거나 페이지 중 하나를 누르지 않는 한 2초마다 페이저를 자동으로 전진시킵니다.HorizontalPager
는 페이지 목록을 표시하며, 각 페이지에는 페이지 번호를 표시하는Text
컴포저블이 있습니다. 이 수정자는 페이지를 채우고pageItems
에서 배경 색상을 설정하며 페이지를 클릭 가능하게 만듭니다.
@Composable fun PagerIndicator(pageCount: Int, currentPageIndex: Int, modifier: Modifier = Modifier) { Box(modifier = Modifier.fillMaxSize()) { Row( modifier = Modifier .wrapContentHeight() .fillMaxWidth() .align(Alignment.BottomCenter) .padding(bottom = 8.dp), horizontalArrangement = Arrangement.Center ) { repeat(pageCount) { iteration -> val color = if (currentPageIndex == iteration) Color.DarkGray else Color.LightGray Box( modifier = modifier .padding(2.dp) .clip(CircleShape) .background(color) .size(16.dp) ) } } } }
코드 관련 핵심 사항
Box
컴포저블이 루트 요소로 사용됩니다.Box
내에서Row
컴포저블은 페이지 표시기를 가로로 정렬합니다.
- 맞춤 페이지 표시기는 원의 행으로 표시되며, 여기서
circle
에 잘린 각Box
는 페이지를 나타냅니다. - 현재 페이지의 원은
DarkGray
로 색상이 지정되고 다른 원은LightGray
로 지정됩니다.currentPageIndex
매개변수는 진한 회색으로 렌더링되는 원을 결정합니다.
결과
이 동영상에서는 이전 스니펫의 기본 자동 전진 페이저를 보여줍니다.