Mineme 프로젝트를 구현하며
Figam에서 Story의 Calendar 섹션을 살펴보면, 디자이너가 특별히 일그러진 원 형태로 디자인한 Calendar Item을 발견하였다.
이 디자인 선택은 예전 회의 때 의도적으로 결정된 사항이다.
개인적으로 굉장히 간단히 이 부분을 구현했다고 생각하는데 이 꿀팁을 공유하고자 글을 작성한다.
in Figma
in Project
초기에 커스텀 디자인을 어떻게 구현해야 할지에 대한 고민이 컸다.
특히나, XML을 사용하는 과정에서도 Custom View를 생성해본 경험이 없었기 때문에, Compose 환경에서 이를 어떻게 구현하고 적용해야 할지에 대한 고민이 더욱 깊었다.
공식 문서에서 Customize an image 부분을 참고하며 공부하였다
Jetpack Compose에서 이미지나 다른 UI 요소를 특정 도형에 맞게 자르기 위해서는 clip
이라는 내장 수정자를 사용한다.
예를 들어 이미지를 원형으로 잘라내고 싶다면 Modifier.clip(CircleShape)
을 사용하면 된다.
만약 특별한 도형을 원한다면, Shape
를 확장하여 Custom Shap를 생성할 수 있다. 이때 Path
를 활용해 도형을 정의할 수 있다고 한다.
먼저 Jetpack Compose에서 기본으로 제공하는 도형 중 하나인 CircleShape
를 찾아 좀 분석해봐야겠다고 생각했다.
CircleShape
는 실제로 RoundedCornerShape
클래스를 통해 생성됩니다. 그리고 RoundedCornerShape
는 궁극적으로 Shape
인터페이스를 상속받아 구현되어 있다.
그럼 공식문서에 써져있는대로 Shape를 확장하여 Custom Shape를 만들 수 있다는 것을 확인했고 이제 어떻게 그릴지 RoundedCornerShape를 좀 더 분석해보자
먼저 Shape 인터페이스는 createOutline 함수를 가지는데
이 createOutline은 도형의 외곽선(Outline)을 생성하는 함수이다.
여기서 중요한 것은 이 함수의 반환값인 Outline 객체이다.
Outline
주석에 따르면 그래픽 영역의 경계를 정의하기 위한 간단한 도형을 정의한다고 한다.
그냥 말 그대로 도형의 외곽선을 나타내는 것이다.
Outline은 sealed class 로 이루어져있다. 따라서 서브 클래스는 해당 파일 내에서만 정의 할 수 있다.
그전에 Outline은 bounds : Rect 라는 추상 속성을 가지는데
이 추상 속성은 Outline
클래스나 그 하위 클래스에서 아웃라인의 경계를 나타내는 직사각형 영역을 반환하는 속성 Outline
클래스를 상속받는 모든 하위 클래스는 이 bounds
속성에 대한 구체적인 구현을 제공해야 한다
즉 좀 헷갈릴 수 있는데
Outline의 bounds
속성은 해당 UI 요소가 차지하는 외부 경계 영역을 나타낸다. 원이나 커스텀 형태의 UI 요소라 할지라도, 이 bounds
는 그 요소를 완벽하게 감싸는 최소한의 직사각형 영역을 반환한다.
예를 들어, 원의 경우 이 직사각형은 원을 완전히 포함하는 가장 작은 크기의 정사각형이 된다.
커스텀 형태 UI의 경우 그 경로를 완벽하게 감싸는 가장 작은 크기의 직사각형 상자가 bounds
가 되는 것이다.
개발자 옵션의 "레이아웃 경계 보기"를 활성화하면 이를 더욱 명확하게 확인할 수 있다
원이나 다른 형태의 UI 요소라도 그 영역은 항상 직사각형으로 표현된다. 결론적으로, bounds
는 해당 Outline이 차지하는 최소 영역을 의미하는 것이다
이제 Outline Selade Class에 정의된 서브 클래스에는 Rectangle, Rounded, Generic 3가지가 있다.
Outline.Rectangle
: 사각형 외곽선Outline.Rounded
: 둥근 모서리가 있는 사각형 외곽선Outline.Generic
: Path
를 사용하여 정의된 일반적인 외곽선현재 내가 필요한 것은 울퉁불퉁 모양 즉, Custom OutLine이므로 Generic에 대해서 알아보겠다.
Generic
은 주어진 경로(Path
)를 기반으로 한 영역을 나타낸다.
해당 클래스는 Path
타입의 인자를 받아 인스턴스 변수로 저장한다.
그리고 해당 변수를 Path
에서 제공하는 getBounds()
메서드를 사용하여 해당 경로의 경계 직사각형을 반환한다.
Path
는 일련의 점, 선, 곡선으로 이루어진 그래픽 형태를 정의하는 방법인데 이를 통해 간단한 선부터 복잡한 도형까지 다양한 그래픽을 표현 가능하다.
이 Path의 기본 속성으로는 여러가지가 있는데 주요 속성으로는
Path
의 시작 위치를 설정Path
의 시작점과 끝점을 연결하여 닫힌 도형을 형성합니다.즉 최종적으로 정리하자면
Image
의 형태를 커스텀하기 위해 clip
수정자를 사용Shape
클래스를 정의Shape
의 createOutline
함수 내에서 Outline.Generic
을 사용Outline.Generic
구현 시, Path
를 사용하여 원하는 디자인을 그림Shape
를 clip
수정자에 적용하여 Image
의 형태를 커스텀하게 잘라냄근데 내가 이 Path의 주요 속성을 공부해서 하나하나 그려가며 확인하기에는 너무 러닝 커브가 크다고 판단했다.
혹시나 하고 Figma에서 이미지를 벡터로 추출해서 Android Studio에서 이미지를 확인해보니
이해하기 어려운 Path정보가 이미 있는 것을 확인했다
이때부터 ChatGPT를 활용한 방법이다
이 Path정보를 가지는 XML 벡터 그래픽 데이터 정보를 ChatGpt 제공하며 Custom Shape를 그려달라고 부탁했다.
내가 GPT에게 입력한 프롬프트 아래와 같다.
내가 제공한 XML 벡터 그래픽 데이터를 기반으로 @Composable
Custom Shape
를 만들어줘
데이터는 아래와 같다.
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="45dp"
android:viewportWidth="48"
android:viewportHeight="45">
<path
android:pathData="M36.403,42.528C26.23,46.909 4.996,47.558 0.996,25.196C-0.004,12.021 8.033,-4.319 32.447,2.028C51.245,6.915 51.118,36.191 36.403,42.528Z"
android:fillColor="#D9D9D9"/>
</vector>
내 코드에 맞게 이름 및 스타일을 조금 수정한 뒤
해당 Shape를 clip에 넘겨주었다.
실제 기기에서 확인해보니 원했던 대로 잘 나오는 것을 볼 수 있다.
Image를 Customize해서 표시하는 방법에 대해서 알아보았습니다.
저는 이 Path의 속성을 공부하며 직접 그리는 방법 대신 이미 백터 데이터가 있는 상황에서 ChatGPT를 통해 간단히 구현 하는 방법을 택했습니다.
Figma에서 이미지의 백터 데이터를 추출하여 ChatGpt에게 제공하며 질문을 하시면 됩니다.
이러한 방법은 효율적이지만 ChagGPT의 응답을 테스트해보는 단계를 꼭 포함하는 것이 중요하다