Compose 기초 3 : ViewPager
이번 포스팅에서는 기존 XML
에서 ViewPager
의 기능을 Compose
로 유사하게 구현하는 방법에 대하여 알아보겠습니다.
시작하기 전에 다음의 내용을 build.gradle(:app)
에 추가해 주세요
implementation "com.google.accompanist:accompanist-pager:0.20.1"
implementation "com.google.accompanist:accompanist-pager-indicators:0.20.1"
1) 기본 사용 방법
Compose
에서는 방향에 따라서 HorizontalPager
와 VerticalPager
로 사용합니다.
val colorList = listOf(
Color(0xFFFF1744),
Color(0xFF651FFF),
Color(0xFF2979FF)
)
Column(modifier = Modifier.fillMaxSize()) {
HorizontalPager(
count = colorList.size,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) { page ->
Box(
modifier = Modifier
.fillMaxSize()
.background(colorList[page]),
contentAlignment = Alignment.Center
) {
Text(
text = "Horizontal Pager: $page",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(10.dp)
.background(Color.White)
)
VerticalPager(
count = colorList.size,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) { page ->
Box(
modifier = Modifier
.fillMaxSize()
.background(colorList[page]),
contentAlignment = Alignment.Center
) {
Text(
text = "Vertical Pager: $page",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
}
}
사용 방법은 count
에 반복 횟수를 넣어주고, content
에서 표시할 UI
를 구현해 주면 됩니다.
content
에서는 해당 포지션의 값(Int
)을 받을 수 있습니다.
2) contentPadding
Column(modifier = Modifier.fillMaxSize()) {
HorizontalPager(
count = colorList.size,
contentPadding = PaddingValues(horizontal = 100.dp),
modifier = Modifier
.fillMaxWidth()
.fillMaxSize(0.5f)
) { page ->
...
}
}
contentPadding
으로 내부의 padding
값을 주면 위의 이미지처럼 Pager
의 아이템이 시작하기 전 여백이나, 스크롤 시 다음 아이템이 보이도록 설정이 가능합니다.
3) Scroll Effect
HorizontalPager(
count = colorList.size,
contentPadding = PaddingValues(horizontal = 100.dp),
modifier = Modifier
.fillMaxWidth()
.fillMaxSize(0.5f)
) { page ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.graphicsLayer {
val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue
// 크기 조절
lerp(
start = 0.85f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
).also { scale ->
scaleX = scale
scaleY = scale
}
// 투명도 조절
alpha = lerp(
start = 0.2f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
)
}
.fillMaxSize()
.background(colorList[page])
) {
...
}
}
graphicsLayer
을 이용하여 스크롤 시 아이템의 투명도, 크기의 값을 변경할 수 있습니다.
lerp
의 값을 변경해가며 구현하고자 하는 디자인의 효과를 구현해 주시면 됩니다.
주의할 점은 graphicsLayer
의 위치에 따라 UI
반영이 달라질 수 있으니 유의해 주세요
ex) fillMaxSize
와 background
를 graphicsLayer
이전에 설정하면 다른 결과가 나옵니다.
4) Indicator
val state = rememberPagerState()
HorizontalPager(
count = colorList.size,
state = state,
contentPadding = PaddingValues(horizontal = 100.dp),
modifier = Modifier
.fillMaxWidth()
.fillMaxSize(0.5f)
) { page ->
...
}
HorizontalPagerIndicator(
pagerState = state,
modifier = Modifier
.padding(top = 20.dp)
.align(Alignment.CenterHorizontally)
)
rememberPagerState
를 이용하여 변수 생성 후 Pager
의 state
에 값을 넣어줍니다.
그 후 HorizontalPagerIndicator
/ VerticalPagerIndicator
에 만든 state
를 넣어주면 Indicator
의 설정이 끝납니다.
PagerIndicator
는 모양, 크기, 선택된 Indicator
색상, 선택되지 않은 Indicator
색상, Indicator
의 간격을 설정할 수 있습니다.
5) Tab + Pager
val state = rememberPagerState()
val scope = rememberCoroutineScope()
Column(modifier = Modifier.fillMaxSize()) {
TabRow(
selectedTabIndex = state.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(state, tabPositions)
)
}
) {
tabList.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = state.currentPage == index,
onClick = {
scope.launch {
state.animateScrollToPage(index)
}
},
)
}
}
HorizontalPager(
count = colorList.size,
state = state,
contentPadding = PaddingValues(horizontal = 100.dp),
modifier = Modifier
.fillMaxWidth()
.fillMaxSize(0.5f)
) { page ->
...
}
}
Tab
과 Pager
를 연결시키는 코드는 아래의 코드입니다.
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(state, tabPositions)
)
}
연결을 시킨 뒤 Tab
의 아이템을 클릭 시에 Pager
의 포지션을 이동시켜 주어야 합니다.
scope.launch {
state.animateScrollToPage(index)
}
Compose
에서 Pager
를 이동시키는 animateScrollToPage
은 Coroutine
으로 함수이기 때문에 CoroutineScope
를 생성 후 사용하여야 합니다.
6) Auto Scroll
LaunchedEffect(state.currentPage) {
delay(1000)
val index = (state.currentPage + 1) % state.pageCount
state.animateScrollToPage(index)
}
Compose
에서 자동 스크롤은 간단하게 구현이 가능합니다.
LaunchedEffect
를 통해서 Pager
의 currentPage
의 값이 변경되면 animateScrollToPage
를 움직이게 됩니다.
LaunchedEffect
는 이미 CoroutineScope
를 가지고 있기 때문에 별도의 CoroutineScope
가 필요하지 않습니다.
delay
시간 동안 수동으로 스크롤을 하면 이전 Coroutine
은 Cancel
되고 다시 생성하므로 스크롤이 끝난 시점부터 delay
가 다시 작동하게 됩니다.