Поддержка различной плотности пикселей

Устройства Android не только имеют разные размеры экранов (телефоны, планшеты, телевизоры и т. д.), но также имеют экраны с разными размерами пикселей. Одно устройство может иметь разрешение 160 пикселей на дюйм, а другое устройство — 480 пикселей в том же пространстве. Если вы не учтете эти различия в плотности пикселей, система может масштабировать ваши изображения, что приведет к их размытию или же изображения могут появиться в неправильном размере.

На этой странице показано, как можно разработать приложение для поддержки различной плотности пикселей, используя независимые от разрешения единицы измерения и предоставляя альтернативные ресурсы растровых изображений для каждой плотности пикселей.

Посмотрите следующее видео, чтобы получить обзор этих техник.

Дополнительные сведения о разработке ресурсов значков см. в рекомендациях по созданию значков Material Design .

Используйте пиксели, независимые от плотности

Избегайте использования пикселей для определения расстояний или размеров. Определение размеров с помощью пикселей является проблемой, поскольку разные экраны имеют разную плотность пикселей, поэтому одинаковое количество пикселей соответствует разным физическим размерам на разных устройствах.

Изображение, показывающее два примера дисплеев устройств с разной плотностью.
Рисунок 1. Два экрана одинакового размера могут иметь разное количество пикселей.

Чтобы сохранить видимый размер вашего пользовательского интерфейса на экранах с разной плотностью, спроектируйте свой пользовательский интерфейс, используя независимые от плотности пиксели (dp) в качестве единицы измерения. Один dp — это единица виртуального пикселя, которая примерно равна одному пикселю на экране средней плотности (160 точек на дюйм или «базовая» плотность). Android переводит это значение в соответствующее количество реальных пикселей для каждой плотности.

Рассмотрим два устройства на рисунке 1. Изображение шириной 100 пикселей на устройстве слева выглядит намного больше. Представление шириной 100 dp отображается одинакового размера на обоих экранах.

При определении размеров текста вы можете вместо этого использовать масштабируемые пиксели (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)

Примечание. Никогда не программируйте жестко это уравнение для расчета пикселей. Вместо этого используйте TypedValue.applyDimension() , который преобразует многие типы размеров (dp, sp и т. д.) в пиксели.

Представьте себе приложение, в котором жест прокрутки или перелистывания распознается после того, как палец пользователя переместился как минимум на 16 пикселей. На базовом экране палец пользователя должен переместиться 16 pixels / 160 dpi , что соответствует 1/10 дюйма (или 2,5 мм), прежде чем жест будет распознан.

На устройстве с дисплеем высокой плотности (240 dpi) палец пользователя должен переместиться 16 pixels / 240 dpi , что равняется 1/15 дюйма (или 1,7 мм). Расстояние намного короче, и поэтому приложение кажется пользователю более чувствительным.

Чтобы устранить эту проблему, выразите порог жеста в коде dp, а затем преобразуйте его в реальные пиксели. Например:

Котлин

// 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...

Ява

// 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() :

Котлин

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

Ява

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

Методы в ViewConfiguration начинающиеся с префикса getScaled гарантированно возвращают значение в пикселях, которое отображается правильно независимо от текущей плотности пикселей.

Предпочитаю векторную графику

Альтернативой созданию нескольких версий изображения с конкретной плотностью является создание только одной векторной графики. Векторная графика создает изображение с использованием XML для определения путей и цветов вместо использования растровых изображений пикселей. Таким образом, векторная графика может масштабироваться до любого размера без артефактов масштабирования, хотя обычно она лучше всего подходит для иллюстраций, таких как значки, а не фотографий.

Векторная графика часто предоставляется в виде файлов SVG (масштабируемая векторная графика), но Android не поддерживает этот формат, поэтому вам необходимо преобразовать файлы SVG в формат векторной графики Android.

Вы можете преобразовать SVG в векторный рисунок с помощью Vector Asset Studio Android Studio следующим образом:

  1. В окне проекта щелкните правой кнопкой мыши каталог res и выберите «Создать» > «Векторный ресурс» .
  2. Выберите Локальный файл (SVG, PSD) .
  3. Найдите файл, который хотите импортировать, и внесите необходимые изменения.

    Изображение, показывающее, как импортировать SVG в Android Studio.
    Рисунок 2. Импорт SVG с помощью Android Studio.

    Вы можете заметить некоторые ошибки в окне Asset Studio , указывающие на то, что векторные объекты рисования не поддерживают некоторые свойства файла. Это не мешает вам импортировать файл; неподдерживаемые свойства игнорируются.

  4. Нажмите Далее .

  5. На следующем экране подтвердите исходный набор , в котором вы хотите, чтобы файл был в вашем проекте, и нажмите «Готово» .

    Поскольку один векторный объект рисования можно использовать для любой плотности пикселей, этот файл находится в вашем каталоге рисунков по умолчанию, как показано в следующей иерархии. Вам не нужно использовать каталоги с конкретной плотностью.

    res/
      drawable/
        ic_android_launcher.xml
    

Для получения дополнительной информации о создании векторной графики прочтите документацию по векторному рисованию .

Предоставьте альтернативные растровые изображения

Чтобы обеспечить хорошее качество графики на устройствах с разной плотностью пикселей, предоставьте несколько версий каждого растрового изображения в вашем приложении — по одной для каждого сегмента плотности с соответствующим разрешением. В противном случае Android придется масштабировать растровое изображение так, чтобы оно занимало одно и то же видимое пространство на каждом экране, что приведет к появлению артефактов масштабирования, таких как размытие.

Изображение, показывающее относительные размеры растровых изображений с разной плотностью.
Рисунок 3. Относительные размеры растровых изображений в сегментах разной плотности.

В ваших приложениях доступно несколько сегментов плотности. В таблице 1 описаны различные доступные квалификаторы конфигурации и типы экранов, к которым они применяются.

Таблица 1. Квалификаторы конфигурации для различной плотности пикселей.

Спецификатор плотности Описание
ldpi Ресурсы для экранов с низкой плотностью ( ldpi ) (~120 dpi).
mdpi Ресурсы для экранов средней плотности ( mdpi ) (~ 160 dpi). Это базовая плотность.
hdpi Ресурсы для экранов высокой плотности ( hdpi ) (~240 точек на дюйм).
xhdpi Ресурсы для экранов сверхвысокой плотности ( xhdpi ) (~320 точек на дюйм).
xxhdpi Ресурсы для экранов сверхвысокой плотности ( xxhdpi ) (~ 480 точек на дюйм).
xxxhdpi Ресурсы для использования при сверхвысокой плотности ( xxxhdpi ) (~640 точек на дюйм).
nodpi Ресурсы для всех плотностей. Это ресурсы, не зависящие от плотности. Система не масштабирует ресурсы, отмеченные этим квалификатором, независимо от текущей плотности экрана.
tvdpi Ресурсы для экранов где-то между mdpi и hdpi; примерно ~213 точек на дюйм. Это не считается «основной» группой плотности. Он в основном предназначен для телевизоров, и большинству приложений он не нужен — для большинства приложений достаточно ресурсов mdpi и hdpi, и система масштабирует их соответствующим образом. Если вы считаете необходимым предоставить ресурсы tvdpi , укажите их размер с коэффициентом 1,33 * mdpi. Например, изображение размером 100x100 пикселей для экранов mdpi соответствует 133x133 пикселей для tvdpi.

Чтобы создать альтернативные растровые изображения для разных плотностей, следуйте коэффициенту масштабирования 3:4:6:8:12:16 между шестью основными плотностями. Например, если у вас есть растровое изображение размером 48x48 пикселей для экранов средней плотности, размеры будут следующими:

  • 36x36 (0,75x) для низкой плотности (ldpi)
  • 48 x 48 (базовый уровень 1,0x) для средней плотности (mdpi)
  • 72x72 (1,5x) для высокой плотности (hdpi)
  • 96x96 (2,0x) для сверхвысокой плотности (xhdpi)
  • 144x144 (3,0x) для сверхвысокой плотности (xxhdpi)
  • 192x192 (4,0x) для сверхвысокой плотности (xxxhdpi)

Поместите сгенерированные файлы изображений в соответствующий подкаталог в 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 , система выбирает подходящее растровое изображение на основе разрешения экрана. Если вы не предоставляете ресурс, специфичный для этой плотности, система находит следующее лучшее совпадение и масштабирует его по размеру экрана.

Совет: Если у вас есть доступные для рисования ресурсы, масштабирование которых вы не хотите, например, когда вы самостоятельно вносите некоторые изменения в изображение во время выполнения, поместите их в каталог с квалификатором конфигурации nodpi . Ресурсы с этим квалификатором считаются независимыми от плотности, и система не масштабирует их.

Дополнительные сведения о других квалификаторах конфигурации и о том, как Android выбирает подходящие ресурсы для текущей конфигурации экрана, см. в разделе Обзор ресурсов приложения .

Поместите значки приложений в каталоги MIP-карт.

Как и в случае с другими растровыми ресурсами, вам необходимо предоставить версии значка вашего приложения с определенной плотностью. Однако некоторые средства запуска приложений отображают значок вашего приложения на 25 % больше, чем того требует корзина плотности устройства.

Например, если сегмент плотности устройства — xxhdpi, а самый большой значок приложения, который вы предоставляете, — drawable-xxhdpi , средство запуска приложений увеличивает этот значок, что делает его менее четким.

Чтобы избежать этого, поместите все значки приложений в каталоги mipmap , а не в каталоги drawable . В отличие от каталогов 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), и изменяет размеры этих растровых изображений до размера, соответствующего текущей плотности пикселей.

    Если вы запрашиваете размеры предварительно масштабированного ресурса, система возвращает значения, представляющие размеры после масштабирования. Например, растровое изображение, созданное с размером 50x50 пикселей для экрана mdpi, масштабируется до 75x75 пикселей на экране hdpi (если нет альтернативного ресурса для hdpi), и система сообщает размер как таковой.

    В некоторых ситуациях вы можете не захотеть, чтобы Android предварительно масштабировал ресурс. Самый простой способ избежать предварительного масштабирования — поместить ресурс в каталог ресурсов с квалификатором конфигурации nodpi . Например:

    res/drawable-nodpi/icon.png

    Когда система использует растровое изображение icon.png из этой папки, она не масштабирует его в зависимости от текущей плотности устройства.

  2. Автоматическое масштабирование размеров и координат пикселей

    Вы можете отключить предварительное масштабирование размеров и изображений, установив android:anyDensity значение "false" в манифесте или программно для Bitmap , установив для inScaled значение "false" . В этом случае система автоматически масштабирует любые абсолютные координаты пикселей и значения размеров пикселей во время рисования. Это делается для того, чтобы элементы экрана, определяемые пикселями, по-прежнему отображались примерно в том же физическом размере, в котором они могут отображаться с базовой плотностью пикселей (mdpi). Система обрабатывает это масштабирование прозрачно для приложения и сообщает приложению масштабированные размеры в пикселях, а не физические размеры в пикселях.

    Например, предположим, что устройство имеет экран высокой плотности WVGA с разрешением 480x800 и примерно такого же размера, как традиционный экран HVGA, но на нем запущено приложение, в котором отключено предварительное масштабирование. В этом случае система «лжет» приложению, когда оно запрашивает размеры экрана и сообщает 320x533, приблизительный перевод mdpi для плотности пикселей.

    Затем, когда приложение выполняет операции рисования, такие как аннулирование прямоугольника с (10,10) на (100, 100), система преобразует координаты, масштабируя их на соответствующую величину, и фактически делает недействительной область (15,15) до (150, 150). Это несоответствие может привести к неожиданному поведению, если ваше приложение напрямую манипулирует масштабированным растровым изображением, но это считается разумным компромиссом для обеспечения максимально возможной производительности приложения. Если вы столкнулись с такой ситуацией, прочтите «Преобразование единиц dp в пиксели» .

    Обычно предварительное масштабирование не отключается . Лучший способ поддержки нескольких экранов — следовать основным методам, описанным на этой странице.

Если ваше приложение манипулирует растровыми изображениями или каким-либо другим образом напрямую взаимодействует с пикселями на экране, вам может потребоваться предпринять дополнительные шаги для поддержки различной плотности пикселей. Например, если вы реагируете на сенсорные жесты, подсчитывая количество пикселей, которые пересекает палец, вам необходимо использовать соответствующие значения пикселей, не зависящие от плотности, вместо реальных пикселей, но вы можете конвертировать значения dp и px .

Тест на всех плотностях пикселей

Протестируйте свое приложение на нескольких устройствах с разной плотностью пикселей, чтобы обеспечить правильное масштабирование пользовательского интерфейса. Тестирование на физическом устройстве, если это возможно; используйте эмулятор Android , если у вас нет доступа к физическим устройствам с различной плотностью пикселей.

Если вы хотите протестировать физические устройства, но не хотите покупать устройства, вы можете использовать Firebase Test Lab для доступа к устройствам в центре обработки данных Google.