뒤로
양수진
양수진 ·

Android에서 iOS(같은) DatePicker 만들어버리기

수수에서 경조금을 언제 받았는지 기록하세요!

안녕하세요, 수수에서 Android 개발을 하고 있는 양수진입니다.

저희 경조사비 관리 서비스 ‘수수’에서는 금액, 이름을 비롯한 경조사비에 대한 기록을 할 수 있습니다. 언제 경조사비를 주고 받았는지도 주요 관심사 중 하나겠지요? 이번에는 경조사비를 주고 받은 날짜를 입력하는 기능을 개발하던 중 겪었던 이야기입니다.

날짜를 어떻게 입력 받을까?

날짜 입력 컴포넌트를 정하면서 여러 의견이 오고 갔습니다. 개발 시간 단축을 위해 Material3 컴포넌트를 사용하는 선택지도 있었지만… 수수의 디자인과 잘 어우러진다는 인상을 받지 못했습니다. 그리고 커스텀 컴포넌트를 만든다고 결정하니 캘린더, 입력형 등 다양한 방식 중 어떤 방식을 선택할지도 고민이 되었습니다.

달력형.png

그때, 불현듯 디자이너님의 한 마디가 스쳐지나갔습니다.

iOS DatePicker 같은 느낌으로…

ios.png

뼛속까지 안드로이드 유저인 저도 iOS의 디자인과 편의성을 인정하는 바입니다. 😙

그렇게 iOS에서 제공하는 DatePicker처럼 슬라이드를 통해 날짜를 입력하는 형태를 채택하였고, 디자인도 빠르게 정해졌습니다. 개발 시간을 줄이기 위해 오픈소스 라이브러리도 찾아보았지만, 저희의 디자인을 충족할 수 있도록 커스텀하기 어려운 부분이 많아 직접 구현하기로 했습니다.

new design2.png

iOS(같은) DatePicker를 만드려면?

  1. 선택한 날짜가 중앙에 하이라이트 되어야 함

  2. 슬라이드가 멈출 때에 드르륵... 턱! 하고 걸려야 함

  3. 선택할 수 있는 항목의 끝에 도달하면 시작점으로 순환되는 구조

    • 12월 다음에는 1월이 다시 등장해야 한다.

선택 항목 중앙 하이라이트

콘텐츠의 전역 포지션이 변경되었을 때 호출되는 onGloballyPositioned 콜백을 통해 현재 항목의 좌표를 비교하여 특정 item이 선택되었다고 판별하였습니다. 사용자가 선택한 item을 판별하여 UI를 그릴 때 하이라이트 효과를 넣어서 그려주도록 했습니다.

Modifier.onGloballyPositioned { coordinates ->
		// 부모 컴포저블 내에서의 y 좌표
    val y = coordinates.positionInParent().y + itemHalfHeight
    
    // 부모 컴포저블 높이와 y좌표 구하기
    val parentHeight = coordinates.parentCoordinates?.size?.height ?: 0
    val parentHalfHeight = parentHeight / 2f
    
    // 아이템이 선택 영역 내에 있다면 선택
    val isSelected = y in (parentHalfHeight - itemHalfHeight * 1.5f)..(parentHalfHeight + itemHalfHeight * 1.5f)

    if (isSelected && lastSelectedIndex != i) {
        onItemSelected(i % itemsState.size, item)
        lastSelectedIndex = i
    }
}

선택.jpg

슬라이드가 멈출때 드르륵..탁! 하고 걸리기

LazyColumn 에 FlingBehavior 를 설정하여 적용할 수 있습니다

  • 드르륵..탁! 드르륵…탁!

 LazyColumn(
        ...
        flingBehavior = flingBehavior,
    ) 

Screen_Recording_20240616_2152401-ezgif.com-video-to-gif-converter.gif

끝에 도달하면 순환하는 구조

제가 가진 지식 안에선 Compose에서 순환하는 형태의 컴포넌트는 존재하지 않았습니다.

묘수를 Stack Overflow에서 찾을 수 있었는데요, LazyColumn 에 무수히 많은 item을 넣어서 무한정 순환하는 것처럼 보이게 하는 방법입니다. Int.MAX_VALUE (약 21억) 개 만큼 item이 존재한다고 가정하고, 중간 즈음에서 스크롤을 시작하는 것입니다. 이론 상 사용자가 10억 개를 스크롤 하지 않는 이상 끝에 도달하지 않습니다. 아래 링크의 답변 코드를 기반으로 저희가 필요한 기능을 커스텀해서 사용했습니다.

출처: https://stackoverflow.com/questions/69734451/is-there-a-way-to-create-scroll-wheel-in-jetpack-compose

Screen_Recording_20240616_2153081-ezgif.com-video-to-gif-converter.gif

  • LazyColumn 은 화면에 보이는 항목만 처리하고, 화면에 보이지 않는 항목은 메모리를 해제하는 등 많은 데이터를 보여줄 때 효율적으로 자원을 사용하는 컴포넌트이기 때문에 사용 가능한 방법입니다. Profiler를 통해 메모리 사용량을 관찰했을 때에도 어느정도의 메모리 사용량은 증가하지만, 성능에 영향을 줄 정도로 증가하지 않았습니다.

그렇게 완성된 형태

Screen_Recording_20240617_210324-ezgif.com-video-to-gif-converter.gif
  • 초기 날짜 설정

  • 선택 가능 날짜 범위 제한

  • 스크롤, 터치 선택 지원

등의 부가기능을 추가하여 컴포넌트를 완성했습니다!


이번 포스팅에서는 디자인 요구사항에 맞는 컴포넌트를 개발하는 과정에서의 고민과 해결 과정을 공유해드렸습니다. 다음 포스팅은 백엔드 팀원분들이 서버 비용을 절약하기 위해 허리를 졸라맸던 이야기가 업로드될 예정이니, 관심 있으신 분들은 꼭 지켜봐주세요! 🌽🌽

➡️ 이전 메이커로그 보러가기

💌경조사비, 너로 정했다!💌

경조사비, 너로 정했다! | Disquiet*

수수의 What을 찾아서💰

수수의 What을 찾아서 | Disquiet*

사이드 프로젝트에서 MVI 써본 썰

사이드 프로젝트에서 MVI 써본 썰 | Disquiet*

➡️ 수수(SUSU)가 궁금할 땐

구글 플레이스토어

수수(susu) - 경조사비 기록 장부 - Apps on Google Play

앱스토어 (링크 예정)

💁🏻‍♀️ : 7월 iOS 출시 예정이에요!

인스타그램 (@team.oksusu)

Instagram (@team.oksusu)

수수 SUSU

경조사비 관리 기록 장부 서비스

11

댓글

로그인 후 댓글을 남길 수 있습니다.

극락코딩
극락코딩

우왕!

서혜원
서혜원

역시 알잘딱깔센! 😎👍🏻

유서경 Quartz
유서경 Quartz

오오 라이브러리를 사용하지 않고 한 땀 한 땀 UI 컴포넌트를 짤 때 은근히 즐겁더라고요 ㅋㅋㅋ 드르륵 탁 요구사항 재밌네요

Doeon Kwon 권도언
Doeon Kwon 권도언

수진님 메이커로그가 이번 주 뉴스레터에 소개되었어요 :) https://dis.qa/LnJyg9