Tab과 ViewPager은 언제 사용되는가?
Swipe해서 탭이 바뀌는 UI는 많은 앱에 쓰이며, 여러 페이지를 탭으로 구성해 사용 복잡도를 줄이기 위해 사용한다.
그림1과 같은 동작으로 Tab을 직접 클릭해서 Page를 이동할 수도 있으며, Swipe 동작을 통해 페이지를 이동할 수도 있다. 오늘은 이러한 UI를 만드는 방법에 대해 알아보고자 한다.
위 페이지를 만들기 위해서는 다음 단계들을 거쳐야 한다.
- Tab + ViewPager 만들기 위한 라이브러리 추가하기
- Tab 목록 만들기
- PagerState 선언하기
- CoroutineScope 선언하기
- TabRow 만들기
- HorizontalPager 만들기
Compose로 Tab과 ViewPager 만들기
1. Tab + ViewPager 만들기 위한 라이브러리 추가하기
accompanist에서 Tab과 Viewpager을 만드는 라이브러리를 제공하는데 아래 두가지 라이브러리를 앱 수준의 build.gradle에 추가해야 한다.
implementation "com.google.accompanist:accompanist-pager:0.20.1"
implementation "com.google.accompanist:accompanist-pager-indicators:0.20.1"
2. Tab 목록 만들기
먼저 Tab 목록을 만들어야 한다. 탭에 표시할 제목들을 다음과 같이 작성한다.
val pages = listOf("페이지1", "페이지2", "페이지3")
3. PagerState 선언하기
다음은 TabRow과 Pager 객체에서 공유할 데이터인 PagerState 객체를 만들어야 한다.
val pagerState = rememberPagerState()
PagerState 객체는 TabRow와 Pager객체 모두에서 사용하는 객체로 현재 몇 페이지에 있는지를 저장한다.
4. CoroutineScope 선언하기
Composable에서 페이지를 이동하는 동작은 CoroutineScope에서 수행되어야 한다. 따라서 다음과 같이 Composable 내부에서 사용할 CoroutineScope을 선언해야 한다.
val coroutineScope = rememberCoroutineScope()
5. TabRow 만들기
다음은 TabRow를 만들어야 한다. TabRow는 그림2와 같이 Tab들을 저장하는 Row 객체이다.
TabRow에 2에서 선언한 pages 변수의 title들을 Tab 객체를 넣어야 하며, pagerState를 연결시켜야 한다.
TabRow(
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
)
}
) {
pages.forEachIndexed { index, title ->
Tab(
text = { Text(text = title) },
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.scrollToPage(index)
}
}
)
}
}
또한 탭이 클릭되었을 때 4에서 선언한 coroutineScope를 사용해 pagerState.scrollToPage 함수를 수행해야 한다.
6. HorizontalPager 만들기
HorizontalPager에도 2, 3에서 만든 pages와 pagerState를 다음과 같이 연결시킨다.
HorizontalPager(
count = pages.size,
state = pagerState,
) { page ->
Text(
modifier = Modifier.wrapContentSize(),
text = page.toString(),
textAlign = TextAlign.Center,
fontSize = 30.sp
)
}
완성 화면
위와 같이 만들면 다음과 같은 페이지가 완성된다.
전체 코드
전체 코드는 다음과 같다.
val pages = listOf("페이지1", "페이지2", "페이지3")
@AndroidEntryPoint
class TabPagerActivity : ComponentActivity() {
@OptIn(ExperimentalPagerApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NewTheme {
Surface(
modifier = Modifier
.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
TabRow(
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
)
}
) {
pages.forEachIndexed { index, title ->
Tab(
text = { Text(text = title) },
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.scrollToPage(index)
}
}
)
}
}
HorizontalPager(
count = pages.size,
state = pagerState,
) { page ->
Text(
modifier = Modifier.wrapContentSize(),
text = page.toString(),
textAlign = TextAlign.Center,
fontSize = 30.sp
)
}
}
}
}
}
}
}