HorizontalPager With Compose

H.Zoon·2024년 7월 17일
1
post-thumbnail

사이드 프로젝트를 진행하면서 세로로 슬라이드되는 뷰를 만들어야 했다.

뷰페이저, 리사이클러뷰? 많은 후보군이 떠올랐는데, 이번에는 처음 사용해보는 컴포즈를 이용해서 구현해보기로 했다.

바로 결과부터 보면

Resized Image

일반적으로 하나의 아이템이 화면 가득 나오고 페이지 인디케이터로 다음 페이지가 있음을 알려주지만, 사실 UX상으로 사이드 카드에 대한 힌트를 주는것이 더 직관적이라고 하니. 양 옆 카드 이미지 힌트와 에니메이션을 추가해 타겟팅 되지 않은 페이지는 크기와 투명도를 변화화게 하여 좀 더 이쁘게(?) 구현 해보았다.

그럼 이제 코드로

카드 요소 구성하기

어떤 방법으로 뷰를 구성하던 해당 요소에 대한 데이터 정의는 필요하다.
이번에 구현한 방법도 카드 요소를 담은 데이터 클래스를 먼저 정의하였다.

data class CardInfo(
    val title: String,
    val iconResId: Int,
    val description: String,
    val navID: String,
)

위 예제에서는 다음과 같이 구성하였다.

  1. 카드 제목
  2. 카드 아이콘
  3. 세부 설명
  4. 컴포즈 네비게이션을 위한 navID

컴포즈 네비게이션은 다음 포스트에 자세히 설명하고 이번에는 카드 섹션만 자세하게..

2. HorizontalPager 설정

HorizontalPager는 수평 스크롤이 가능한 뷰페이저를 제공한다.
이 컴포넌트는 다양한 화면에 걸쳐 여러 페이지를 표시할 때 유용하게 사용할 수 있다.

HorizontalPager(
    count = cardList.size, // 페이지 수는 cardList의 크기만큼 설정
    state = pagerState, // 페이지의 상태를 관리하는 객체.
    modifier = Modifier.height(300.dp), // HorizontalPager의 높이를 300dp로 설정.
    contentPadding = PaddingValues(horizontal = screenWidth * 0.2f) // 페이지 양쪽에 20%만큼의 여백을 추가합니다.
) { page ->
   
// 카드 기능 구현하기
HorizontalPager(
    count = cardList.size, // 페이지 수는 cardList의 크기만큼 설정
    state = pagerState, // 페이지의 상태를 관리하는 객체.
    modifier = Modifier.height(300.dp) // HorizontalPager의 높이를 300dp로 설정.
) { page ->
    Card(
        modifier = Modifier
            .width(200.dp) // 카드의 너비를 200dp로 설정.
            .clickable {
                // 클릭 요소 선언
            }
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize(), // Column의 크기를 가득 차게 설정.
            verticalArrangement = Arrangement.SpaceEvenly,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = cardList[page].title, // cardList에서 페이지에 해당하는 카드의 제목을 가져와 설정.
                style = MaterialTheme.typography.titleLarge
            )

            Image(
                painter = painterResource(id = cardList[page].iconResId), // 카드의 아이콘 이미지를 설정합니다.
                contentDescription = null, // 이미지의 내용 설명을 null로 설정합니다.
                modifier = Modifier
                    .size(64.dp) // 이미지의 크기를 64dp로 설정합니다.
                    .padding(8.dp), // 이미지의 여백을 8dp로 설정합니다.
                contentScale = ContentScale.Fit // 이미지의 비율을 유지하며 맞춥니다.
            )

            Text(
                text = cardList[page].description, // cardList에서 페이지에 해당하는 카드의 설명을 가져와 설정합니다.
                fontSize = 14.sp // 설명 텍스트의 크기를 14sp로 설정합니다.
            )
        }
    }
}

@Composable
    fun PagerIndicator(pagerState: PagerState) {
        HorizontalPagerIndicator(
            pagerState = pagerState,
            modifier = Modifier
                .padding(8.dp),
            activeColor = Color.Gray,
            inactiveColor = Color.Black,
            indicatorWidth = 10.dp,
            spacing = 8.dp
        )
    }

이렇게 하면 다음과 같이 기본적인 HorizontalPager가 나타나게 된다.

Resized Image

이제 위에서 본 예제처럼 고도화를 해보자

3.graphicsLayer 추가하기

컴포즈의 graphicsLayer를 이용하여 컴포즈 UI 요소에 다양한 그래픽 효과를 적용할 수 있다.
Modifier를 사용하면 회전, 크기 조정, 투명도 변경, 오프셋 조정 등 다양한 그래픽 변환을 쉽게 적용할 수 있다.

각 요소들로는
1. 회전 (Rotation): UI 요소를 특정 각도로 회전.
2. 크기 조정 (Scale): UI 요소의 크기를 조정합니다. x축과 y축의 크기를 각각 조정.
3. 투명도 (Alpha): UI 요소의 투명도를 조정.
4. 오프셋 (Translation): UI 요소를 x축과 y축 방향으로 이동.
5. 쉐도우 (Shadow): UI 요소에 그림자를 추가할 수 있음.
6. 클리핑 (Clipping): UI 요소를 특정 형태로 클리핑.

Card(
       //나머지 요소는 위와 동일
            .graphicsLayer {
                // 현재 페이지 오프셋을 계산하여 절대값을 가져옴
                val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue
                // 시작 및 종료 스케일 값을 설정하고, 페이지 오프셋에 따라 보간
                val scale = lerp(
                    start = ScaleFactor(0.9f, 0.9f),
                    stop = ScaleFactor(1f, 1f),
                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
                )
                scaleX = scale.scaleX  // X축 스케일 설정
                scaleY = scale.scaleY  // Y축 스케일 설정
                // 페이지 오프셋에 따라 알파(투명도) 값을 설정
                alpha = 0.5f + (1f - pageOffset.coerceIn(0f, 1f)) * 0.5f
            }
            .width(screenWidth * 0.8f)  // 카드뷰의 너비 설정
            .padding(16.dp),  // 내부 패딩 설정
        elevation = CardDefaults.cardElevation(
            defaultElevation = 10.dp  // 카드뷰의 그림자 높이를 설정
        )
    )

추가적으로 카드 요소가 정상적으로 표시될수 있도록 다음과 같이 HorizontalPager의 contentPadding을 수정해주면..

HorizontalPager(
count = cardList.size,
state = pagerState,
modifier = Modifier.height(300.dp),

// 수평 패딩을 설정하여 카드 간의 간격을 조절
contentPadding = PaddingValues(horizontal = screenWidth * 0.2f)
)

이제 최종 결과물을 확인해보면 위에서 본 아름다운 카드뷰를 볼 수 있을것이다.

0개의 댓글