티스토리 뷰

안드로이드/코드

Compose 기초 3 : ViewPager

알렌보이스 2022. 8. 30. 19:39
728x90

이번 포스팅에서는 기존 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에서는 방향에 따라서 HorizontalPagerVerticalPager로 사용합니다.

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
            )
        }
    }
}

Screen_Recording_20220830-173813_ComposeDesign_1.gif

사용 방법은 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 ->
        ...
    }
}

Screen_Recording_20220830-173813_ComposeDesign_1.gif

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])
    ) {
        ...
    }
}

Screen_Recording_20220830-181508_ComposeDesign_1.gif

graphicsLayer을 이용하여 스크롤 시 아이템의 투명도, 크기의 값을 변경할 수 있습니다.

lerp의 값을 변경해가며 구현하고자 하는 디자인의 효과를 구현해 주시면 됩니다.

주의할 점은 graphicsLayer의 위치에 따라 UI 반영이 달라질 수 있으니 유의해 주세요

ex) fillMaxSizebackgroundgraphicsLayer 이전에 설정하면 다른 결과가 나옵니다.

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)
)

Screen_Recording_20220830-182457_ComposeDesign_1.gif

rememberPagerState를 이용하여 변수 생성 후 Pagerstate에 값을 넣어줍니다.

그 후 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 ->
                ...
    }
}

Screen_Recording_20220830-183825_ComposeDesign_1.gif

TabPager를 연결시키는 코드는 아래의 코드입니다.

indicator = { tabPositions ->
    TabRowDefaults.Indicator(
        Modifier.pagerTabIndicatorOffset(state, tabPositions)
    )
}

연결을 시킨 뒤 Tab의 아이템을 클릭 시에 Pager의 포지션을 이동시켜 주어야 합니다.

scope.launch {
    state.animateScrollToPage(index)
}

Compose에서 Pager를 이동시키는 animateScrollToPageCoroutine으로 함수이기 때문에 CoroutineScope를 생성 후 사용하여야 합니다.

6) Auto Scroll

LaunchedEffect(state.currentPage) {
    delay(1000)
    val index = (state.currentPage + 1) % state.pageCount
    state.animateScrollToPage(index)
}

Compose에서 자동 스크롤은 간단하게 구현이 가능합니다.

LaunchedEffect를 통해서 PagercurrentPage의 값이 변경되면 animateScrollToPage를 움직이게 됩니다.

LaunchedEffect는 이미 CoroutineScope를 가지고 있기 때문에 별도의 CoroutineScope가 필요하지 않습니다.

delay 시간 동안 수동으로 스크롤을 하면 이전 CoroutineCancel되고 다시 생성하므로 스크롤이 끝난 시점부터 delay가 다시 작동하게 됩니다.

728x90
댓글