본문 바로가기

Dev/안드로이드

Compose에서 RatingBar 직접 구현하기

 

 안드로이드는 기본적으로 RatingBar라는 View를 제공한다. 그래서 별점을 부여하는 UI를 구성할 때 어려울 것 없이 해당 View를 바탕으로 구현하면 된다. 하지만 그것은 View와 Xml 기반으로 UI를 작성할 때의 이야기고, Compose의 기본 컴포넌트에는 RatingBar 유형의 컴포넌트가 존재하지 않는다.

 물론 AndroidView를 통해서 기존의 View 시스템을 부분적으로 적용해서 쓰는 방법도 좋은 방법이겠지만, 직접 구현해보면 좋은 경험이 될 것 같아서 이번엔 Composable을 직접 만들어 사용했다.

 

 

 

📝 구상

 

 먼저 대략적인 디자인과 동작부터 생각했다. 이건 완성한 컴포넌트의 Preview긴 하지만, 처음 구상했던 디자인과 별반 차이는 없다. 동작이 중요했는데, 유저와의 상호작용을 통해 별점을 0.5점 단위로 변경할 수 있어야 했고, 그 상호작용은 드래그와 탭 모두 지원해야 했다.

 만약 같은 별점을 탭한다면 부여된 별점은 0으로 바뀌어야 했고, 드래그 할 때 별점 부여는 구간을 나누어서 빈 별, 반만 찬 별, 꽉 찬 별에 해당하는 점수를 부드럽게 부여할 수 있어야 했다.

이 구간은 대략 저 별이 차지하는 공간의 가로 비율을 기준으로 0.0~0.25에 드래그시 0점부여, 0.25~0.75에 드래그시 0.5점 부여, 0.75~1.0에 드래그시 1점 부여로 생각을 했다.

 

 

🔨 구현

 

 일단 RatingBar Composable은 rating과 onRatingChanged 콜백을 파라미터로 받는다. 직관적으로 알 수 있는 그대로, rating은 부여된 별점을 의미하며, onRatingChanged는 별점을 바꾸는 제스처를 인식하면 호출하게 될 콜백이다. 

마우스와 그림판으로 그린..

 

 디자인은 위와 같이 구성했다. Row를 만들어서 Box를 5개 둔다. 그리고 그 안에 비어있는 별을 표현할 Icon을 넣어주고 그리고 겹칠 수 있도록 Box를 함께 생성한다. 그리고 겹쳐둔 Box 내에는 Icon을 넣고 꽉 찬 별을 표현할 수 있도록 해준다.

 고민해야 하는 부분은 반 개 짜리 별을 어떻게 처리할 것이냐다. 심플하게 처리하자면 조건을 걸어서 부여해야 하는 점수에 따라 그에 맞는 Icon이 담긴 Box를 생성해 띄우는 방법이 있을 것이다.

 지금처럼 부여할 수 있는 별점의 단위가 고정되어 있다면 그 또한 좋은 방법이지만, 나는 Modifier의 clip()을 활용하기로 했다.

 clip()은 인자로 받는 Shape를 따라서 내용물을 클리핑 해준다. Shape는 createOutline 메소드를 구현하게 만든 인터페이스다.

 

 그리고 Shape의 createOutline() 메소드는 size, layoutDirection, density를 인자로 받아 Outline을 리턴해야 하며 Outline은 위와 같은 Sealed class다. 이번에는 사각형 모양으로 절반 잘라버리면 되니까 Rectangle을 사용했고, 그 외에 Rounded와 Generic 클래스도 사용할 수 있었다. 각각, 모서리가 둥근 사각형이랑, Path를 통해 직접 모양을 만들 수 있는 용도인 것 같았다.

 

 

 왼쪽에서부터 가로를 기준으로 지정한 비율만큼을 가로 길이로 가지는 직사각형을 만들 수 있도록 했다. fillWidthRatio 값이 0.0, 0.5, 1.0 밖에 들어오지 않을 거지만, 드래그에 따라 연속된 점수 구간으로 부여하고 싶다면 이런식으로 구현하는 것이 생산성이 좋을 것이다.

 

 

 이런 식으로 처리해서 저 클리핑 비율을 계산하게 했다. ind는 별 Box의 index로 1~5까지 부여된다. 연속된 값을 주고싶다면 저 fillWidthRatio 계산 하는 부분만 손 보면 될 것이다.

 

 이제 점수에 따라 별점 UI가 적절하게 출력되도록 디자인 하는 부분은 모두 끝났고, 제스처를 처리해주기만 하면 됐다. 이를 위해 이용한 것은 Modifier의 pointerInput()이다. Compose에서는 pointerInput을 통해 터치 입력을 처리할 수 있다. block 인자로 받는 PointerInputScope 내에서 탭, 가로 드래그, 세로 드래그 등의 제스처를 처리하는 코드를 구현할 수 있다.

 내가 구현하려 했던 것은 탭 작업과 드래그 작업인데, 탭 작업은 각 별을 나타내는 Box에 탭 제스처를 구현하고, 드래그 작업은 모든 별 Box를 담고 있는 Row에다가 드래그 제스처를 구현하는 것이 좋겠다고 생각했다. 

 

 그리고 드래그 제스처를 효과적으로 처리하기 위해 bounds를 만들어 화면 내 별 Box들의 경계를 저장해둔다. 별점이 바뀐다고 그 별 Box들의 위치와 경계까지 변하진 않으므로 remember로 생성했다.

 

 

 이 값들은 각각의 별 Box가 그려질 때 채워지게 된다. 

 

 

 드래그 제스처를 처리하는 부분이다. onHorizontalDrag 콜백을 구현하는데 change 인자를 통해서 드래그된 위치를 가져와 어느 Box의 경계에 포함돼있는지 확인한다.

 그리고 그 확인한 결과값을 토대로 해당 별이 몇 점에 해당하는 부분을 담당하는지 기준 점수를 baseRating에 저장한다. 만약 4번째 별 어딘가로 드래그 했다면 최소한 3점은 확보된 상황이니까.

 그 다음엔 드래그 한 부분이 별 내에서 어느정도까지 드래그한 것인지 계산한다. 드래그 위치에서 실제 Box 경계의 left를 빼주면 해당 Box의 왼쪽으로부터 얼마나 떨어져있는지 알 수 있고, 그 값을 Box 경계의 가로 값의 몇 퍼센트인지 계산해 0~1 사이 실수 값으로 부여해준다.

 마지막으로 그렇게 구한 값을 0.5 단위로 반올림 처리를 해준다. 0.0, 0.5, 1.0 중 가장 가까운 값이 계산되므로, 처음 의도했던 것처럼 동작하게 된다.

 그렇게 구한 값을 onRatingChanged 콜백에 전달해주면 드래그 처리는 끝이다.

 

 

 

 탭 제스처 구현은 더 심플한데, 복잡하게 비율 따질 것도 없고, 각 별 Box마다 부여되기 때문이다. onTap을 구현해주면 되는데, Offset 값을 받아 Box 내에서 왼쪽 절반이면 0.5점으로, 오른쪽 절반이면 1점을 부여하도록 했다. 대신에 터치해서 만들어지는 총 별점이 기존 별점과 같다면 별점을 0점으로 만들도록 처리했다.

 

 

 참고로 Box의 경계 크기를 가져오기 위해 사용한 저 size 값은 PointerInputScope 내라면 접근 가능해서 편리하게 사용할 수 있다.

 

 

 

📌결과

 

  onRatingChanged가 호출될 때마다 ViewModel의 메소드를 호출해 Room 테이블을 업데이트 하고, 그 변경 내용은 고스란히 Flow로 넘어와 다시 UI로 반영되도록 했고, 의도한대로 잘 동작함을 확인할 수 있다. 

 해보지 않았으면, 비슷한 걸 구현할 때 꽤 애먹었겠지만, 이제는 잘 할 수 있을 것 같다.





참조
Pointer input in Compose  |  Jetpack Compose  |  Android Developers


 

Compose의 포인터 입력  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose의 포인터 입력 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose는 사용자 상호작용에서 생

developer.android.com


How to Create a Draggable Rating Bar in Jetpack Compose | by Kappdev | Medium

 

How to Create a Draggable Rating Bar in Jetpack Compose

In this article, we’ll create a highly customizable and draggable Rating Bar in Jetpack Compose. This Rating Bar offers complete control…

medium.com


상위 Composable이 관리하는 State 주입 받아 이용하는 Modifier 내 로직이 계속 초기 값을 레퍼런싱 할 때 (tistory.com)

 

상위 Composable이 관리하는 State 주입 받아 이용하는 Modifier 내 로직이 계속 초기 값을 레퍼런싱 할

개인 프로젝트에 드래그 가능하고 탭도 가능한 RatingBar가 필요해서 커스텀 중인데, 꽤 쉽지가 않았다. 안드로이드 View 위젯들을 사용할 때는 RatingBar가 따로 있었으나, Compose에는 유사한 기본 Compo

winterry.tistory.com

(저번 포스팅인데 구현하면서 생겼던 버그에 관한 포스팅이다)