다양한 픽셀 밀도 지원

Android 기기에는 핸드셋, 태블릿, TV 등 화면 크기가 다를 뿐만 아니라 픽셀 크기가 다른 화면도 있습니다. 한 기기에서는 인치당 160픽셀이 있고 다른 기기에서는 같은 공간에 480픽셀을 사용할 수 있습니다. 이러한 픽셀 밀도의 차이를 고려하지 않으면 시스템에서 이미지의 크기를 조정하여 흐릿한 이미지가 발생하거나 이미지가 잘못된 크기로 표시될 수 있습니다.

이 페이지에서는 해상도에 독립적인 측정 단위를 사용하고 각 픽셀 밀도에 대체 비트맵 리소스를 제공하여 다양한 픽셀 밀도를 지원하도록 앱을 디자인하는 방법을 보여줍니다.

이러한 기법의 개요를 보려면 다음 동영상을 시청하세요.

아이콘 애셋 디자인에 관한 자세한 내용은 머티리얼 디자인 아이콘 가이드라인을 참고하세요.

밀도 독립형 픽셀 사용

거리나 크기를 정의하는 데 픽셀을 사용하지 않습니다. 픽셀로 치수를 정의하는 것은 화면마다 픽셀 밀도가 다르고, 동일한 수의 픽셀이 여러 기기에서 서로 다른 실제 크기에 상응하기 때문에 문제가 됩니다.

밀도가 서로 다른 두 가지 기기 디스플레이 예를 보여주는 이미지
그림 1: 크기가 같은 두 화면의 픽셀 수는 다를 수 있습니다.

밀도가 다양한 화면에서 UI의 표시 크기를 유지하려면 밀도 독립형 픽셀 (dp)을 측정 단위로 사용하여 UI를 디자인합니다. 1dp는 중밀도 화면(160dpi 또는 '기준' 밀도)의 1픽셀과 대략 동일한 가상 픽셀 단위입니다. Android는 이 값을 서로 밀도에 맞는 적절한 실제 픽셀 수로 변환합니다.

그림 1의 두 기기를 생각해 보세요. 너비가 100픽셀인 뷰는 왼쪽의 기기에서 훨씬 더 크게 표시됩니다. 너비가 100dp로 정의된 뷰는 두 화면에서 동일한 크기로 표시됩니다.

텍스트 크기를 정의할 때는 확장 가능한 픽셀 (sp)을 단위로 사용할 수 있습니다. sp 단위는 기본적으로 dp와 같은 크기이지만 사용자가 선호하는 텍스트 크기에 따라 크기가 조절됩니다. 레이아웃 크기에는 절대 sp를 사용하지 않습니다.

예를 들어 두 뷰 사이의 간격을 지정하려면 dp를 사용합니다.

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

텍스트 크기를 지정할 때는 sp를 사용합니다.

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

dp 단위를 픽셀 단위로 변환

크기를 dp 단위로 표시한 다음 픽셀로 변환해야 하는 경우가 있습니다. dp 단위를 화면 픽셀로 변환하는 것은 다음과 같습니다.

px = dp * (dpi / 160)

참고: 이 방정식을 하드코딩하여 픽셀을 계산하지 마세요. 대신 여러 유형의 크기 (dp, sp 등)를 픽셀로 변환하는 TypedValue.applyDimension()를 사용하세요.

앱에서 사용자의 손가락이 16픽셀 이상 이동한 후 스크롤 또는 플링 동작이 인식된다고 가정해 보겠습니다. 기준 화면에서는 동작이 인식되기 전에 사용자의 손가락이 16 pixels / 160 dpi (2.5mm 또는 1/10인치와 같음)를 이동해야 합니다.

고밀도 디스플레이 (240dpi)가 있는 기기에서는 사용자의 손가락이 1.7mm (1/15인치)에 해당하는 16 pixels / 240 dpi를 이동해야 합니다. 거리가 훨씬 더 짧아지므로 앱이 사용자에게 더 민감하게 표시됩니다.

이 문제를 해결하려면 코드에서 동작 기준점을 dp 단위로 표현한 다음 실제 픽셀로 변환하세요. 예:

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

// Use gestureThreshold as a distance in pixels...

Java

// The gesture threshold expressed in dp
private final float GESTURE_THRESHOLD_DP = 16.0f;

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

// Use gestureThreshold as a distance in pixels...

DisplayMetrics.density 필드는 현재 픽셀 밀도에 따라 dp 단위를 픽셀로 변환하는 데 사용되는 배율을 지정합니다. DisplayMetrics.density는 중밀도 화면에서 1.0과 같고 고밀도 화면에서 1.5와 같습니다. 초고밀도 화면에서는 2.0과 같고 저밀도 화면에서는 0.75와 같습니다. 이 수치는 TypedValue.applyDimension()에서 현재 화면의 실제 픽셀 수를 가져오는 데 사용됩니다.

사전 크기 조정된 구성 값 사용

ViewConfiguration 클래스를 사용하여 Android 시스템에서 사용되는 일반적인 거리, 속도, 시간에 액세스할 수 있습니다. 예를 들어 프레임워크에서 스크롤 기준점으로 사용하는 픽셀 단위의 거리는 getScaledTouchSlop()로 구할 수 있습니다.

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

getScaled 접두사로 시작하는 ViewConfiguration의 메서드는 현재 픽셀 밀도와 관계없이 올바르게 표시되는 픽셀 단위 값을 반환합니다.

벡터 그래픽 선호

이미지의 여러 밀도별 버전을 만드는 대신 벡터 그래픽을 하나만 만들 수도 있습니다. 벡터 그래픽은 픽셀 비트맵을 사용하는 대신 XML을 사용하여 이미지를 만들어 경로와 색상을 정의합니다. 따라서 벡터 그래픽은 아티팩트 크기를 조정하지 않고 원하는 크기로 조정할 수 있지만 일반적으로 사진이 아닌 아이콘과 같은 삽화에 가장 적합합니다.

벡터 그래픽은 SVG (Scalable Vector Graphics) 파일로 제공되는 경우가 많지만 Android에서는 이 형식을 지원하지 않으므로 SVG 파일을 Android의 벡터 드로어블 형식으로 변환해야 합니다.

다음과 같이 Android 스튜디오의 Vector Asset Studio를 사용하여 SVG를 벡터 드로어블로 변환할 수 있습니다.

  1. Project 창에서 res 디렉터리를 마우스 오른쪽 버튼으로 클릭하고 New > Vector Asset을 선택합니다.
  2. 로컬 파일(SVG, PSD)을 선택합니다.
  3. 가져와 조정할 파일을 찾습니다.

    Android 스튜디오에서 SVG를 가져오는 방법을 보여주는 이미지
    그림 2: Android 스튜디오로 SVG 가져오기

    Asset Studio 창에서 벡터 드로어블이 파일의 일부 속성을 지원하지 않음을 나타내는 오류가 표시될 수 있습니다. 이렇게 해도 파일을 가져올 수 없으며 지원되지 않는 속성은 무시됩니다.

  4. 다음을 클릭합니다.

  5. 다음 화면에서 프로젝트에서 파일을 사용하려는 소스 세트를 확인하고 Finish를 클릭합니다.

    모든 픽셀 밀도에서 하나의 벡터 드로어블을 사용할 수 있으므로 이 파일은 다음 계층 구조와 같이 기본 드로어블 디렉터리에 있습니다. 밀도별 디렉터리를 사용할 필요가 없습니다.

    res/
      drawable/
        ic_android_launcher.xml
    

벡터 그래픽 만들기에 관한 자세한 내용은 벡터 드로어블 문서를 참고하세요.

대체 비트맵 제공

픽셀 밀도가 서로 다른 기기에서 우수한 그래픽 품질을 제공하려면 앱에서 각 비트맵의 여러 버전(밀도 버킷마다 상응하는 해상도로 하나씩)을 제공합니다. 그러지 않으면 Android에서 비트맵의 크기를 조정하여 각 화면에서 동일한 표시 공간을 차지하도록 해야 하며, 결과적으로 블러와 같은 크기 조정 아티팩트가 발생합니다.

다양한 밀도 크기에서 비트맵의 상대적 크기를 보여주는 이미지
그림 3: 다양한 밀도 버킷에 있는 비트맵의 상대적 크기

앱에서 사용할 수 있는 밀도 버킷은 여러 개가 있습니다. 표 1은 사용 가능한 여러 구성 한정자와 이러한 구성 한정자가 적용되는 화면 유형을 설명합니다.

표 1. 다양한 픽셀 밀도의 구성 한정자

밀도 한정자 설명
ldpi 저밀도 (ldpi)의 화면 (~120dpi)에 대한 리소스입니다.
mdpi 중밀도 (mdpi) 화면 (~160dpi)에 대한 리소스입니다. 이것이 기준 밀도입니다.
hdpi 고밀도 (hdpi)의 화면 (~240dpi)에 대한 리소스입니다.
xhdpi 초고밀도 (xhdpi)의 화면 (~320dpi)에 대한 리소스입니다.
xxhdpi 초초고밀도 (xxhdpi) 화면 (~480dpi)에 대한 리소스입니다.
xxxhdpi 초초초고밀도 (xxxhdpi)의 화면 (~640dpi)에 대한 리소스입니다.
nodpi 모든 밀도에 대한 리소스입니다. 이들은 밀도 독립적 리소스입니다. 시스템은 현재 화면의 밀도와 관계없이 이 한정자로 태그된 리소스의 크기를 조정하지 않습니다.
tvdpi mdpi와 hdpi 사이에 있는 화면에 대한 리소스입니다(약 213dpi). '기본' 밀도 그룹으로 간주되지 않습니다. 이는 대부분 텔레비전용이며 대부분의 앱에는 필요하지 않습니다. 대부분의 앱은 mdpi 및 hdpi 리소스를 제공하는 것으로 충분하며 시스템에서 필요에 따라 리소스를 확장합니다. tvdpi 리소스를 제공해야 한다면 1.33 * mdpi의 배수로 크기를 조절합니다. 예를 들어 mdpi 화면의 100x100픽셀 이미지는 tvdpi에서 133x133픽셀입니다.

다양한 밀도의 대체 비트맵 드로어블을 만들려면 6가지 기본 밀도 간의 3:4:6:8:12:16 크기 조정 비율을 따르세요. 예를 들어 중밀도 화면에 48x48 픽셀인 비트맵 드로어블이 있는 경우 크기는 다음과 같습니다.

  • 저밀도 (ldpi)의 경우 36x36 (0.75x)
  • 중밀도 (mdpi)의 경우 48x48 (1.0x 기준)
  • 고밀도 (hdpi)의 경우 72x72 (1.5x)
  • 초고밀도 (xhdpi)의 경우 96x96 (2.0x)
  • 초초고밀도 (xxhdpi)의 경우 144x144 (3.0x)
  • 초초초고밀도 (xxxhdpi)의 경우 192x192 (4.0x)

생성된 이미지 파일을 res/ 아래의 적절한 하위 디렉터리에 배치합니다.

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

그런 다음, @drawable/awesomeimage를 참조할 때마다 시스템은 화면의 dpi에 따라 적절한 비트맵을 선택합니다. 이 밀도에 관해 밀도별 리소스를 제공하지 않으면 시스템에서 다음으로 최적 일치를 찾아 화면에 맞게 크기를 조정합니다.

도움말: 런타임 시 직접 이미지를 조정하는 경우와 같이 시스템의 크기를 조정하지 않으려는 드로어블 리소스가 있는 경우 nodpi 구성 한정자가 있는 디렉터리에 배치합니다. 이 한정자가 있는 리소스는 밀도를 알 수 없는 것으로 간주되며 시스템에서 리소스의 크기를 조정하지 않습니다.

다른 구성 한정자 및 Android가 현재 화면 구성에 적합한 리소스를 선택하는 방법에 관한 자세한 내용은 앱 리소스 개요를 참고하세요.

mipmap 디렉터리에 앱 아이콘 지정

다른 비트맵 애셋과 마찬가지로 앱 아이콘의 밀도별 버전을 제공해야 합니다. 그러나 일부 앱 런처는 기기의 밀도 버킷에서 요구하는 것보다 최대 25% 더 크게 앱 아이콘을 표시합니다.

예를 들어 기기의 밀도 버킷이 xxhdpi이고 제공되는 가장 큰 앱 아이콘이 drawable-xxhdpi에 있다면 앱 런처가 이 아이콘을 확장하여 아이콘이 덜 선명하게 표시됩니다.

이를 방지하려면 모든 앱 아이콘을 drawable 디렉터리가 아닌 mipmap 디렉터리에 넣습니다. drawable 디렉터리와 달리 모든 mipmap 디렉터리는 APK에 유지됩니다. 밀도 특정 APK를 빌드하더라도 마찬가지입니다. 이렇게 하면 런처 앱이 홈 화면에 표시할 최적의 해상도 아이콘을 선택할 수 있습니다.

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

xxhdpi 기기의 이전 예에서는 mipmap-xxxhdpi 디렉터리에 더 높은 밀도의 런처 아이콘을 제공할 수 있습니다.

아이콘 디자인 가이드라인은 시스템 아이콘을 참고하세요.

앱 아이콘 빌드에 관한 도움말은 Image Asset Studio로 앱 아이콘 만들기를 참고하세요.

특수한 밀도 문제에 관한 조언

이 섹션에서는 Android가 다양한 픽셀 밀도에서 비트맵의 크기를 조정하는 방법과 다양한 밀도에서 비트맵이 그려지는 방식을 추가로 제어할 수 있는 방법을 설명합니다. 앱에서 그래픽을 조작하지 않거나 다양한 픽셀 밀도에서 실행할 때 문제가 발생한 경우가 아니라면 이 섹션을 무시해도 됩니다.

런타임에 그래픽을 조작할 때 여러 밀도를 지원할 수 있는 방법을 더 잘 이해하려면 시스템에서 비트맵의 적절한 크기를 보장하는 방법을 알아야 합니다. 이는 다음과 같은 방법으로 이루어집니다.

  1. 리소스 사전 크기 조정(예: 비트맵 드로어블)

    시스템은 현재 화면의 밀도에 따라 앱의 밀도별 리소스를 사용합니다. 리소스를 올바른 밀도에서 사용할 수 없는 경우 시스템은 기본 리소스를 로드하고 필요에 따라 리소스를 확대하거나 축소합니다. 시스템에서는 기본 리소스 (구성 한정자가 없는 디렉터리의 리소스)가 기준 픽셀 밀도 (mdpi)에 맞게 디자인된 것으로 가정하고 이 비트맵의 크기를 현재 픽셀 밀도에 적합한 크기로 조정합니다.

    사전 크기 조정된 리소스의 크기를 요청하면 시스템에서 크기 조정 이후의 크기를 나타내는 값을 반환합니다. 예를 들어 mdpi 화면용으로 50x50픽셀로 디자인된 비트맵은 hdpi 화면에서 75x75 픽셀로 조정되며 (hdpi의 대체 리소스가 없는 경우) 시스템은 이와 같이 크기를 보고합니다.

    Android에서 리소스를 사전 확장하지 않게 하려는 경우가 있습니다. 사전 크기 조정을 방지하는 가장 쉬운 방법은 nodpi 구성 한정자가 있는 리소스 디렉터리에 리소스를 넣는 것입니다. 예:

    res/drawable-nodpi/icon.png

    시스템에서 이 폴더의 icon.png 비트맵을 사용할 때는 현재 기기 밀도에 따라 비트맵의 크기를 조정하지 않습니다.

  2. 픽셀 크기 및 좌표 자동 크기 조정

    매니페스트에서 android:anyDensity"false"로 설정하거나 inScaled"false"로 설정하여 Bitmap의 프로그래매틱 방식으로 크기 및 이미지의 사전 크기 조정을 사용 중지할 수 있습니다. 이 경우 시스템에서는 그리기 시간에 절대 픽셀 좌표와 픽셀 크기 값을 자동으로 조정합니다. 이렇게 하면 픽셀로 정의된 화면 요소가 기준 픽셀 밀도 (mdpi)에서 표시할 수 있는 것과 거의 동일한 실제 크기로 계속 표시됩니다. 시스템에서는 이러한 크기 조정을 앱에 투명하게 처리하며, 실제 픽셀 크기가 아닌 앱에 관해 조정된 픽셀 크기를 보고합니다.

    예를 들어 480x800인 WVGA 고밀도 화면(기존 HVGA 화면과 거의 동일한 크기)이 있는 기기에서 사전 크기 조정이 사용 중지된 앱을 실행한다고 가정해 보겠습니다. 이 경우 시스템은 앱에서 화면 크기를 쿼리하고 픽셀 밀도의 대략적인 mdpi 변환인 320x533을 보고할 때 앱을 '누릅니다'.

    그런 다음 앱이 (10,10)에서 (100, 100)으로 직사각형을 무효화하는 것과 같은 그리기 작업을 실행하면 시스템은 좌표를 적절한 양만큼 조정하여 좌표를 변환하고 실제로는 영역 (15,15)을 (150, 150)으로 무효화합니다. 이러한 불일치는 앱이 크기 조정된 비트맵을 직접 조작하는 경우 예기치 않은 동작을 유발할 수 있지만, 최상의 앱 성능을 보장하기 위한 합당한 절충점으로 간주됩니다. 이러한 상황이 발생하면 dp 단위를 픽셀 단위로 변환을 참고하세요.

    일반적으로 사전 크기 조정을 사용 중지하지 않습니다. 여러 화면을 지원하는 가장 좋은 방법은 이 페이지에 설명된 기본 기법을 따르는 것입니다.

앱이 비트맵을 조작하거나 다른 방식으로 화면의 픽셀과 직접 상호작용하는 경우 다양한 픽셀 밀도를 지원하기 위해 추가 조치를 취해야 할 수도 있습니다. 예를 들어 손가락이 지나가는 픽셀 수를 카운트하여 터치 동작에 반응하는 경우 실제 픽셀 대신 적절한 밀도 독립형 픽셀 값을 사용해야 하지만 dp 값과 px 값 간에 변환할 수 있습니다.

모든 픽셀 밀도에 관해 테스트

픽셀 밀도가 서로 다른 여러 기기에서 앱을 테스트하여 UI 크기 조정이 올바르게 이루어지도록 합니다. 가능하면 실제 기기에서 테스트하세요. 모든 다양한 픽셀 밀도의 실제 기기에 액세스할 수 없는 경우 Android Emulator를 사용합니다.

실제 기기에서 테스트하고 싶지만 기기를 구매하지 않으려면 Firebase Test Lab을 사용하여 Google 데이터 센터의 기기에 액세스하면 됩니다.