1. Drawable
안드로이드의 Drawable API는 화면에 그래픽을 그리기 위한 추상화된 계층을 제공한다.
Drawable은 BitmapDrawable, VectorDrawable , NinePatchDrawable , ShapeDrawable 등의 다양한 구현체들이 존재한다.
지난 포스팅에서 Bitmap Drawable의 densityDpi별 대응을 위해 밀도별 스케일 펙터를 적용한 Bitmap을 사용한다고 했는데, 왜 사진이나 복잡한 이미지는 Vector Drawable로 사용하지 않는 지 의문이 들었다.
2. VectorDrawable
주로 애플리케이션에서 사용하는 로고, 아이콘에 사용되는 Drawable 구현체이다.
앞선 포스팅에서는 밀도 별 버전을 만들어서 해상도 별로 대응을 한다고 했는데, VectorDrawable은 여러 밀도별 버전을 만들지 않고 Vector 그래픽을 하나만 만들어 사용하게 된다.
Vector 그래픽은 xml 파일을 사용해서 이미지를 만들고, 픽셀 Bitmap을 사용하는 대신 경로와 색상을 지정할 수 있다.

하지만 안드로이드에서는 SVG 형식(Scalable Vector graphic)을 지원하지 않기 때문에, 안드로이드 개발자들은
피그마에서 Icon들을 SVG 파일로 가져온 뒤에 안드로이드 스튜디오의 Asset Studio를 통해 Vector Drawable 파일로 저장해서 사용할 수 있다.
✨안드로이드 시스템이 Vector Drawable을 처리하는 방법
Vector Drawable을 사용하면 어떻게 단일 xml 파일을 사용해서 다양한 해상도의 화면을 처리할 수 있을까?
그건 Vector Drawable이 이미지를 픽셀 단위의 Bitmap이 아니라, 수학적 Vector 데이터로 정의하기 때문이다.

xml 파일 내에서 path, group, clip-path 등으로 이미지를 그릴 경로를 정의하기 때문에, 이런 정보들은 해상도 의존적이지 않고 동적으로 스케일링이 가능해 다양한 화면 밀도를 가지는 디바이스들에 대응이 가능하다.(이미지 퀄리티 보장)
path에서 객체의 윤곽선 도형 정보를, group에는 변환을 위한 세부적인 정보를 가진다.
아래 xml은 어떤 아이콘의 SVG 파일을 변환한 Vector Drawable 파일이다.
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:pathData="M12,8L6,14L12,20"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M22,14.75C22.414,14.75 22.75,14.414 22.75,14C22.75,13.586 22.414,13.25 22,13.25V14.75ZM7,14.75H22V13.25H7V14.75Z"
android:fillColor="#000000"/>
</vector>
이를 렌더링하는 과정에서 안드로이드 시스템은 Canvas API를 사용해서 VectorDrawable.draw() 메소드를 호출한다.
이 때,
- 밀도 스케일 펙터에 기반해서 실제 그려질 크기로 변환한다.
- 변환된 크기와 좌표계의 크기(viewport width/height)를 비교해서 벡터 스케일 펙터를 구한다.
- 벡터 드로어블의 내부 좌표를 벡터 스케일 펙터에 기반해서 확장해서 렌더링한다.
위 과정을 거치게 된다.
만약 크기가 28dp인 벡터 드로어블이
(1) mdpi 밀도의 기기에서 56dp로 그려져야 한다면, 벡터 스케일 펙터는 28:28로 1이다.
(2) xhdpi 밀도의 기기에서 56dp로 그려져야 한다면, 벡터 스케일 펙터는 56:28로 2이다.
이후 벡터 스케일 펙터를 기반으로 벡터 드로어블 내부 좌표를 확장하여 실제 렌더링할 크기에 맞게 스케일링할 수 있게 된다.
위 Vector Drawable을 렌더링하면 아래와 같은 뒤로가기 버튼이 나온다.

그럼 Bitmap 이미지도 Vector Drawable로 변환하면 다양한 해상도에 대응하기 위해 별도의 이미지 파일들을 추가할 필요가 없지 않을까? 왜 아이콘처럼 단순한 그래픽 요소에는 Vector Drawable을 사용하지만 고해상도 이미지들은 해상도별로 대응해줘야 할까?
깨알 꿀팁 V

안드로이드 공식 문서에서는 일반적인 UI의 레이아웃, 구성요소, 간격에는 8dp 단위를,
아이콘 같은 작은 요소들에는 4dp 단위를 추천한다.
28dp, 30dp는 예시일 뿐, 실 개발 단계에서는 위 지침을 따르는 게 일관된 사용자 경험을 제공하는 데에 도움이 될 것이다.
3. Bitmap Drawable을 사용하는 이유

Bitmap은 픽셀을 2차원 배열 형태로 저장하는 데이터 구조이다.
BMP와 PNG가 대표적인 래스터 파일 포맷으로, 이미지 데이터를 픽셀 단위로 저장한다.(이 때 Bitmap 구조로 저장해서 래스터 파일을 비트맵이라고 부른다)
비트맵은 파일 자체에 포함된 픽셀 수가 정해져 있어서 이미지의 크기를 조정하게 되면 새로운 픽셀이 추가되는게 아니라 기존 픽셀이 늘어나게 되어 이미지 품질이 저하된다.
그래서 사진이나 복잡한 이미지는 Bitmap 형식을 사용해서 이미지 품질을 유지하고, 로고나 아이콘은 벡터 드로어블을 사용하는 것이다.
❓ dp를 사용하면 모든 화면에서 동일하게 보여질까?
지난 포스팅을 작성하면서 다시 한 번 고민해 본 개념이다. 항상 개발을 하면서도 dp를 사용하면 모든 화면에서 동일한 크기로 "당연히" 보여진다고 생각했고, 부끄럽게도 그 이유는 자세히 파헤쳐보지 않았다.
안드로이드에서 디바이스 간의 해상도나 크기 차이를 보정하려고 dp라는 밀도 독립형 픽셀 단위를 사용했다고 하니 의심하지 않았던 것 같다. 하지만 과연 dp를 사용한다고 모든 화면(디스플레이)에서 완벽하게 동일한 크기로 보여질까?
이에 대한 설명은 서로 다른 ppi를 가진 안드로이드 디바이스 2개를 비교하면서 시작하고자 한다.
위와 같은 두 안드로이드 디바이스가 있을 때, 가로-세로가 30dp인 View를 보여줘야 한다고 해보자.
밀도 스케일 펙터를 구해보면,
- 갤럭시 S25 : 419/160 -> 2.62
- 갤럭시 노트10 : 402/160 -> 2.51
위 값이 나오게 된다. 실제 픽셀 값으로 계산해보면,
- 30 * 2.61 = 78.6px
- 30 * 2.51 = 75.3px
그럼 실제 디바이스에서는 어떤 크기로 보일까?
- 419 ppi 디바이스에서 78.6px -> 0.1875894988066826 inch
- 402 ppi 디바이스에서 75.3px -> 0.1873134328358209 inch
대략 0.0002 inch가 차이나고, 이걸 cm로 변환하면
- 2.54 * 0.0002 = 0.000508 cm
가 나온다.
두 디바이스에서 보여지는 30dp 크기의 View는 실제로는 0.0005cm의 크기 차이가 있으니 수치상으로는 동일하지 않다.
하지만 만약 30dp가 아닌 600dp 크기의 View를 보여줘야 한다고 해도, 두 디바이스에서의 크기 오차는 0.01cm(0.1mm)이다. 사실상 사용자 입장에서는 완벽하게 동일한 크기로 보여진다고 생각해도 될 정도의 오차인 것이다.
결론은, 동일한 크기로 보여지지만 동일한 크기는 아니다. 라고 표현할 수 있을 것 같다.
'개발 > Android' 카테고리의 다른 글
[안드로이드] Hilt 어노테이션 프로세싱 - Dagger를 휘두르는 방법 (0) | 2025.03.25 |
---|---|
[안드로이드] 난독화와 R8 컴파일러 (0) | 2025.03.17 |
[안드로이드] 쉘 스크립트로 이미지를 dpi 폴더에 분류하기(feat.해상도 대응) (0) | 2025.03.04 |
[안드로이드] ART의 GC는 어떻게 동작할까(feat.Dalvik) (0) | 2025.02.17 |
[안드로이드] 화면 회전과 ViewModel (0) | 2025.02.13 |
1. Drawable
안드로이드의 Drawable API는 화면에 그래픽을 그리기 위한 추상화된 계층을 제공한다.
Drawable은 BitmapDrawable, VectorDrawable , NinePatchDrawable , ShapeDrawable 등의 다양한 구현체들이 존재한다.
지난 포스팅에서 Bitmap Drawable의 densityDpi별 대응을 위해 밀도별 스케일 펙터를 적용한 Bitmap을 사용한다고 했는데, 왜 사진이나 복잡한 이미지는 Vector Drawable로 사용하지 않는 지 의문이 들었다.
2. VectorDrawable
주로 애플리케이션에서 사용하는 로고, 아이콘에 사용되는 Drawable 구현체이다.
앞선 포스팅에서는 밀도 별 버전을 만들어서 해상도 별로 대응을 한다고 했는데, VectorDrawable은 여러 밀도별 버전을 만들지 않고 Vector 그래픽을 하나만 만들어 사용하게 된다.
Vector 그래픽은 xml 파일을 사용해서 이미지를 만들고, 픽셀 Bitmap을 사용하는 대신 경로와 색상을 지정할 수 있다.

하지만 안드로이드에서는 SVG 형식(Scalable Vector graphic)을 지원하지 않기 때문에, 안드로이드 개발자들은
피그마에서 Icon들을 SVG 파일로 가져온 뒤에 안드로이드 스튜디오의 Asset Studio를 통해 Vector Drawable 파일로 저장해서 사용할 수 있다.
✨안드로이드 시스템이 Vector Drawable을 처리하는 방법
Vector Drawable을 사용하면 어떻게 단일 xml 파일을 사용해서 다양한 해상도의 화면을 처리할 수 있을까?
그건 Vector Drawable이 이미지를 픽셀 단위의 Bitmap이 아니라, 수학적 Vector 데이터로 정의하기 때문이다.

xml 파일 내에서 path, group, clip-path 등으로 이미지를 그릴 경로를 정의하기 때문에, 이런 정보들은 해상도 의존적이지 않고 동적으로 스케일링이 가능해 다양한 화면 밀도를 가지는 디바이스들에 대응이 가능하다.(이미지 퀄리티 보장)
path에서 객체의 윤곽선 도형 정보를, group에는 변환을 위한 세부적인 정보를 가진다.
아래 xml은 어떤 아이콘의 SVG 파일을 변환한 Vector Drawable 파일이다.
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:pathData="M12,8L6,14L12,20"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M22,14.75C22.414,14.75 22.75,14.414 22.75,14C22.75,13.586 22.414,13.25 22,13.25V14.75ZM7,14.75H22V13.25H7V14.75Z"
android:fillColor="#000000"/>
</vector>
이를 렌더링하는 과정에서 안드로이드 시스템은 Canvas API를 사용해서 VectorDrawable.draw() 메소드를 호출한다.
이 때,
- 밀도 스케일 펙터에 기반해서 실제 그려질 크기로 변환한다.
- 변환된 크기와 좌표계의 크기(viewport width/height)를 비교해서 벡터 스케일 펙터를 구한다.
- 벡터 드로어블의 내부 좌표를 벡터 스케일 펙터에 기반해서 확장해서 렌더링한다.
위 과정을 거치게 된다.
만약 크기가 28dp인 벡터 드로어블이
(1) mdpi 밀도의 기기에서 56dp로 그려져야 한다면, 벡터 스케일 펙터는 28:28로 1이다.
(2) xhdpi 밀도의 기기에서 56dp로 그려져야 한다면, 벡터 스케일 펙터는 56:28로 2이다.
이후 벡터 스케일 펙터를 기반으로 벡터 드로어블 내부 좌표를 확장하여 실제 렌더링할 크기에 맞게 스케일링할 수 있게 된다.
위 Vector Drawable을 렌더링하면 아래와 같은 뒤로가기 버튼이 나온다.

그럼 Bitmap 이미지도 Vector Drawable로 변환하면 다양한 해상도에 대응하기 위해 별도의 이미지 파일들을 추가할 필요가 없지 않을까? 왜 아이콘처럼 단순한 그래픽 요소에는 Vector Drawable을 사용하지만 고해상도 이미지들은 해상도별로 대응해줘야 할까?
깨알 꿀팁 V

안드로이드 공식 문서에서는 일반적인 UI의 레이아웃, 구성요소, 간격에는 8dp 단위를,
아이콘 같은 작은 요소들에는 4dp 단위를 추천한다.
28dp, 30dp는 예시일 뿐, 실 개발 단계에서는 위 지침을 따르는 게 일관된 사용자 경험을 제공하는 데에 도움이 될 것이다.
3. Bitmap Drawable을 사용하는 이유

Bitmap은 픽셀을 2차원 배열 형태로 저장하는 데이터 구조이다.
BMP와 PNG가 대표적인 래스터 파일 포맷으로, 이미지 데이터를 픽셀 단위로 저장한다.(이 때 Bitmap 구조로 저장해서 래스터 파일을 비트맵이라고 부른다)
비트맵은 파일 자체에 포함된 픽셀 수가 정해져 있어서 이미지의 크기를 조정하게 되면 새로운 픽셀이 추가되는게 아니라 기존 픽셀이 늘어나게 되어 이미지 품질이 저하된다.
그래서 사진이나 복잡한 이미지는 Bitmap 형식을 사용해서 이미지 품질을 유지하고, 로고나 아이콘은 벡터 드로어블을 사용하는 것이다.
❓ dp를 사용하면 모든 화면에서 동일하게 보여질까?
지난 포스팅을 작성하면서 다시 한 번 고민해 본 개념이다. 항상 개발을 하면서도 dp를 사용하면 모든 화면에서 동일한 크기로 "당연히" 보여진다고 생각했고, 부끄럽게도 그 이유는 자세히 파헤쳐보지 않았다.
안드로이드에서 디바이스 간의 해상도나 크기 차이를 보정하려고 dp라는 밀도 독립형 픽셀 단위를 사용했다고 하니 의심하지 않았던 것 같다. 하지만 과연 dp를 사용한다고 모든 화면(디스플레이)에서 완벽하게 동일한 크기로 보여질까?
이에 대한 설명은 서로 다른 ppi를 가진 안드로이드 디바이스 2개를 비교하면서 시작하고자 한다.
위와 같은 두 안드로이드 디바이스가 있을 때, 가로-세로가 30dp인 View를 보여줘야 한다고 해보자.
밀도 스케일 펙터를 구해보면,
- 갤럭시 S25 : 419/160 -> 2.62
- 갤럭시 노트10 : 402/160 -> 2.51
위 값이 나오게 된다. 실제 픽셀 값으로 계산해보면,
- 30 * 2.61 = 78.6px
- 30 * 2.51 = 75.3px
그럼 실제 디바이스에서는 어떤 크기로 보일까?
- 419 ppi 디바이스에서 78.6px -> 0.1875894988066826 inch
- 402 ppi 디바이스에서 75.3px -> 0.1873134328358209 inch
대략 0.0002 inch가 차이나고, 이걸 cm로 변환하면
- 2.54 * 0.0002 = 0.000508 cm
가 나온다.
두 디바이스에서 보여지는 30dp 크기의 View는 실제로는 0.0005cm의 크기 차이가 있으니 수치상으로는 동일하지 않다.
하지만 만약 30dp가 아닌 600dp 크기의 View를 보여줘야 한다고 해도, 두 디바이스에서의 크기 오차는 0.01cm(0.1mm)이다. 사실상 사용자 입장에서는 완벽하게 동일한 크기로 보여진다고 생각해도 될 정도의 오차인 것이다.
결론은, 동일한 크기로 보여지지만 동일한 크기는 아니다. 라고 표현할 수 있을 것 같다.
'개발 > Android' 카테고리의 다른 글
[안드로이드] Hilt 어노테이션 프로세싱 - Dagger를 휘두르는 방법 (0) | 2025.03.25 |
---|---|
[안드로이드] 난독화와 R8 컴파일러 (0) | 2025.03.17 |
[안드로이드] 쉘 스크립트로 이미지를 dpi 폴더에 분류하기(feat.해상도 대응) (0) | 2025.03.04 |
[안드로이드] ART의 GC는 어떻게 동작할까(feat.Dalvik) (0) | 2025.02.17 |
[안드로이드] 화면 회전과 ViewModel (0) | 2025.02.13 |