페스타고 인기 축제 목록의 중첩된 페이지를 어떻게 개발했는지 공유하려고 합니다.
디자인은 다음과 같습니다.
이는 크게 두 가지로 나누어 생각해볼 수 있습니다.
앞으로 이 부분을 ForegroundPager, BackgroundPager 라고 칭하겠습니다.
1. ForegroundPager : 화면 앞 부분에서 페이지를 넘길 수 있고 이전, 이후 아이템을 미리보기 가능
2. BackgroundPager : 앞 페이지가 선택되면 변경되는 배경 부분
Foreground 부분은 ViewPager, Background 부분은 ImageView 를 사용하면 되겠다 생각했습니다.
ForegroundPager
페이지 형태의 화면을 구성해야 할 때 다음과 같은 보기가 있습니다.
Android View : ViewPager ,
Android Jetpack Compose : HorizontalPager, VerticalPager
현재 릴리즈된 페스타고는 ViewPager 로 구현되어있습니다.
ForegroundPager 를 구현하기 위해선 다음과 같은 고려사항이 있습니다.
1. 페이지 사이 일정한 간격 유지
2. 선택되지 않은 페이지 크기 줄이기
3. 무한스크롤. 시작 페이지라도 왼쪽이 마지막 페이지가 있어야함. 마지막 페이지라도 오른쪽에 시작페이지가 있어야함.
1. 페이지 사이 일정 간격 유지
ViewPager 는 페이지간 간격 조정을 지원하지 않습니다.
각 페이지의 x 좌표를 이동시켜 페이지를 겹치는 형식으로 구현해야합니다.
x 좌표를 얼마나 이동시켜야 원하는 간격으로 페이지가 보일까요?
겹쳐야 할 길이는 다음과 같습니다.
핸드폰 화면의 너비는 다 다르기 때문에 폰 너비, 이미지 크기, 이미지 간격으로 겹쳐야할 길이를 구할 수 있습니다.
그러나 우리는 나중에 이미지 크기를 줄여야합니다 결국 줄어든 크기 만큼 더 빼주어야 합니다.
앞에서 계산한 길이에 -1 * Position 만큼 곱하고 X 좌표를 이동시키면 현재 선택된 페이지 position 이 0 이므로 각 페이지들이 모이게 됩니다.
코드를 일부 요약해서 보여드리겠습니다.
스크롤 위치가 변경될 때마다 각 페이지에 대해 호출되는 setPageTransfromer 를 사용했습니다.
translateOffsetBetweenPages 함수를 정의해 앞에서 말한 방법으로 이동 시킬 값 offset 을 구해 x 좌표를 이동시켰습니다.
2. 선택되지 않은 페이지 줄이기
reduceUnselectedPagesScale 로 이미지 크기도 줄였습니다. 그 내부에서 View 의 reduceScaleBy 라는 확장 함수를 실행하는데요.
1 ~ rate 사이에서 scale 을 조작합니다.
예를 들어 rate 가 0.8 이라면 position 이 0 일때 1 로 원래 사이즈로 보입니다. position 이 -1.7 이라면 계선결과 0.7 이고 이는 0.8 보다 작기 때문에 scaleFactor 는 0.8 이 됩니다. Float 의 확장함수 coerceAtLeast 를 활용했습니다.
3. 무한스크롤
데이터가 한정된 개수에서 원형 큐처럼 반복해서 스크롤 가능하도록 하려면 어떻게 해야할까요?
저는 ViewPager 에 RecycierView.Adapter 를 붙여서 사용했는데요.
이 RecyclerViewAdapter 는 ListAdapter 와 달리 리스트를 직접 소유하지 않습니다.
따라서 itemCounts 를 알려주어야하죠. 이를 이용해서 실제 리스트 아이템 개수보다 많은 값을 전달할 수 있습니다. 약간 꼼수인거죠.
아이템 개수에 Int 의 최대값 (21억 쯤..) 으로 넘겨주고 [position 을 items.size 로 나눈 나머지] 번째 index 를 찾아 bind 해주었습니다.
그리고 처음 시작을 포지션 중간쯤으로 강제 이동시킵니다.. 그럼 10억쯤에서 시작하게됩니다. 무한은 아니지만 사실상 무한이 되는거죠.
4. 결과물
디자인 요구사항에 맞게 잘 구현된 것을 볼 수 있습니다!
BackgroundPager
다음은 배경을 만들어주어야겠죠!
원래 계획은 ImageView 를 사용하려고 했습니다. 하지만 아이템이 변경되는 과정에서 번쩍임이 발생하더군요. Glide preload 를 사용해 미리 이미지 캐싱도 해봤지만 그리는 과정은 필요했기 때문에 ImageView 만 사용해서는 번쩍임을 방지할 수 없었습니다.
아이템 개수에 생관없이 다음에 선택될 페이지들까지 미리 그려놓고 찾아서 보여주는 방법은 없을까요..?
그래서 결국 또 ViewPager 를 쓰게 되었습니다.
ViewPager 는 위와 같이 setOffscreenPageLimit 이라는 함수를 제공합니다.
현재 페이지 기준으로 양쪽 화면을 몇 개까지 유지할 지 결정할 수 있습니다.
모든 페이지를 다 들고있을 필요는 없으니깐 setOffscreenPageLimit 로 양쪽 2개까지만 유지되도록 만들었습니다.
ForegroundPager 가 선택되면 해당 아이템이 뒤에 보여져야했기 때문에 PageChangeCallback 을 위와 같이 재정의해주었습니다.
혼자 개발하는 것이 아니기 때문에 Foreground, Background 를 조합한 새로운 객체를 정의해 쉽게 조작할 수 있도록 하였습니다.
결과물
여기까지 개발하고 보니 너무 복잡하다는 생각이 들었습니다.
how 가 아닌 what 에 집중한 선언형 UI Jetpack Compose 를 사용하면 더 쉽게 개발할 수 있지 않을까요?
그래서 branch 를 하나 파고 Compose 로 리팩터링해봤습니다.
Refactoring to Compose (VerticalPager, HorizontalPager)
https://developer.android.com/develop/ui/compose/layouts/pager?hl=ko
아직 실험용이라고 합니다.
PagerState 를 만들고 Composable HorizontalPager 나 VerticalPager 에 전달해 사용하면 된다고 합니다.
pageSpacing. pageSize, beyondBoundsPageCount 를 전달해 쉽게 조작할 수 있었습니다.
앞서 View 로 개발했던 것 처럼 ForegroundPager, BackgroundPager 를 따로 정의하고 Box 로 묶어 겹쳐지게 만들어 주었습니다.
LaunchedEffect 를 사용해 처음에 페이지 중간으로 이동시키고 Foreground 페이지 변경 시 Background 도 변경되도록 작성했습니다.
해당 화면만 컴포즈로 변경했기 때문에 다른 화면은 모두 View 로 만들어져있습니다. View 에 Compose 를 올리려면 ComposeView 를 사용해주었습니다.
View (ViewPager) 후기
View 로 개발할 때는 훨씬 많은 Cost 를 요구했습니다. 하지만 코드에 대한 이해도는 더 높아서 예상한 그대로 결과를 얻을 수 있었습니다. 또한, View 를 조작하는데 있어 이해도가 더 높아졌네요.
Compose (HorizontalPager) 후기
급하게 구현하다 보니 완전히 똑같이 변경하지는 못했습니다. Compose 로 하는게 시간은 훨씬 적게 걸렸습니다.
하지만 기본적인 컴포즈에 대한 이해도가 아직 낮았습니다. LaunchedEffect 등을 완전히 이해하고 사용하지 못했죠.
그리고 최신 버전 문제 이슈를 마주할 수 있었습니다. 이해도가 높아지고 라이브러리가 안정되면 효율적으로 사용할 수 있어 보입니다.
View 와 컴포즈를 같이 써보는 경험도 좋았습니다.
글이 마음에 드셨다면 페스타고로 대학 축제를 즐겨보시는건 어떤가요..! 감사합니다.
'Android' 카테고리의 다른 글
페스타고 상세 페이지 이동 애니메이션 변경 및 UX 개선 (0) | 2024.05.12 |
---|---|
[Android] Encryption & Decryption in Android (0) | 2024.01.10 |
[안드로이드] DataStore 이해하기 (0) | 2024.01.08 |
RecyclerView 목록 스크롤에 CoordinatorLayout 적용하기 (0) | 2023.11.17 |
[안드로이드] Repository Pattern (저장소 패턴) (0) | 2023.10.26 |