Устройства Android имеют не только разные размеры экрана (телефоны, планшеты, телевизоры и т. д.), но и экраны с разными размерами пикселей. На одном устройстве может быть 160 пикселей на дюйм, а на другом – 480 пикселей на том же пространстве. Если не учитывать эти различия в плотности пикселей, система может масштабировать изображения, что приведет к их размытию или неправильному размеру.
В этой статье рассказывается, как разработать приложение, поддерживающее разные плотности пикселей, используя независимые от разрешения единицы измерения и предоставляя альтернативные ресурсы растровых изображений для каждой плотности пикселей.
Посмотрите видео, чтобы узнать больше об этих методах.
Дополнительную информацию о разработке значков можно найти в руководстве по значкам Material Design.
Используйте пиксели, не зависящие от плотности экрана
Не используйте пиксели для определения расстояний или размеров. Определение размеров в пикселях является проблемой, поскольку разные экраны имеют разную плотность пикселей, поэтому одно и то же количество пикселей соответствует разным физическим размерам на разных устройствах.
Чтобы сохранить видимый размер интерфейса на экранах с разной плотностью, используйте в качестве единицы измерения пиксели, не зависящие от плотности (dp). Один dp – это виртуальный пиксель, который примерно равен одному пикселю на экране со средней плотностью (160 dpi, или базовая плотность). 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, а затем преобразуйте его в фактические пиксели. Пример:
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();
Методы в ViewConfiguration, начинающиеся с префикса getScaled, гарантированно возвращают значение в пикселях, которые отображаются правильно независимо от текущей плотности пикселей.
Предпочитать векторную графику
Вместо того чтобы создавать несколько версий изображения для разных плотностей пикселей, можно создать одно векторное изображение. Векторная графика создает изображение с помощью XML для определения путей и цветов вместо использования пиксельных растровых изображений. Поэтому векторную графику можно масштабировать до любого размера без артефактов масштабирования, хотя обычно она лучше всего подходит для иллюстраций, таких как значки, а не для фотографий.
Векторная графика часто предоставляется в виде файлов SVG (Scalable Vector Graphics), но Android не поддерживает этот формат, поэтому вам необходимо преобразовать файлы SVG в формат vector drawable Android.
Чтобы преобразовать SVG в класс VectorDrawable, используйте инструмент Vector Asset Studio в Android Studio. Для этого выполните следующие действия:
- В окне Project (Проект) нажмите правой кнопкой мыши на каталог res и выберите New > Vector Asset (Создать > Векторный объект).
- Выберите Локальный файл (SVG, PSD).
Найдите файл, который хотите импортировать, и внесите необходимые изменения.
Рисунок 2. Импорт SVG-файла в Android Studio. В окне Asset Studio могут появиться ошибки, указывающие на то, что классы VectorDrawable не поддерживают некоторые свойства файла. Это не помешает импортировать файл, но неподдерживаемые свойства будут проигнорированы.
Нажмите Далее.
На следующем экране подтвердите набор исходных файлов, в который нужно добавить файл в проекте, и нажмите Finish (Готово).
Поскольку один класс VectorDrawable можно использовать для всех разрешений пикселей, этот файл помещается в каталог объектов по умолчанию, как показано в следующей иерархии. Вам не нужно использовать каталоги, предназначенные для определенной плотности.
res/ drawable/ ic_android_launcher.xml
Дополнительную информацию о создании векторной графики можно найти в документации по классу VectorDrawable.
Предоставьте альтернативные растровые изображения
Чтобы обеспечить хорошее качество графики на устройствах с разной плотностью пикселей, предоставьте несколько версий каждого растрового изображения в приложении – по одной для каждого диапазона плотности с соответствующим разрешением. В противном случае Android придется масштабировать растровое изображение, чтобы оно занимало одинаковое видимое пространство на каждом экране, что приведет к появлению артефактов масштабирования, таких как размытие.
Для использования в приложениях доступно несколько диапазонов плотности. В таблице 1 описаны доступные квалификаторы конфигурации и типы экранов, к которым они применяются.
Таблица 1. Квалификаторы конфигурации для разной плотности пикселей.
| квалификатор разрешения | Описание |
|---|---|
ldpi |
Ресурсы для экранов с низкой плотностью (ldpi, около 120 dpi). |
mdpi |
Ресурсы для экранов со средней плотностью пикселей (mdpi, около 160 dpi). Это базовая плотность. |
hdpi |
Ресурсы для экранов с высокой плотностью пикселей (hdpi, около 240 dpi). |
xhdpi |
Ресурсы для экранов со сверхвысокой плотностью (xhdpi, около 320 dpi). |
xxhdpi |
Ресурсы для экранов со сверхвысокой плотностью пикселей (xxhdpi, около 480 dpi). |
xxxhdpi |
Ресурсы для экранов со сверхвысокой плотностью пикселей (xxxhdpi, около 640 dpi). |
nodpi |
Ресурсы для всех плотностей. Эти ресурсы не зависят от плотности. Система не масштабирует ресурсы, помеченные этим квалификатором, независимо от плотности текущего экрана. |
tvdpi |
Ресурсы для экранов с плотностью пикселей между mdpi и hdpi (примерно 213 dpi). Эта группа не считается основной. В основном он предназначен для телевизоров, и большинству приложений он не нужен. Для большинства приложений достаточно ресурсов mdpi и hdpi, и система масштабирует их соответствующим образом. Если вам необходимо предоставить ресурсы tvdpi,
их размер должен быть в 1,33 раза больше, чем у ресурсов mdpi. Например, изображение размером 100 x 100 пикселей для экранов mdpi будет иметь размер 133 x 133 пикселя для экранов tvdpi. |
Чтобы создать альтернативные растровые объекты для разных плотностей, используйте соотношение масштабирования 3:4:6:8:12:16 между шестью основными плотностями. Например, если у вас есть растровое изображение размером 48 x 48 пикселей для экранов со средней плотностью, размеры будут следующими:
- 36 x 36 (0,75x) для низкой плотности (ldpi);
- 48 x 48 (базовый размер 1.0x) для средней плотности (mdpi)
- 72 x 72 (1,5x) для высокой плотности (hdpi);
- 96 x 96 (2.0x) для сверхвысокой плотности (xhdpi);
- 144 x 144 (3.0x) для сверхвысокой плотности (xxhdpi);
- 192 x 192 (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 выбирает подходящие ресурсы для текущей конфигурации экрана, можно найти в обзоре ресурсов приложения.
Поместите значки приложений в каталоги mipmap
Как и в случае с другими растровыми объектами, вам нужно предоставить версии значка приложения для разных разрешений экрана. Однако некоторые панели запуска приложений отображают значок приложения на 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 выполняет масштабирование растровых изображений для экранов с различной плотностью пикселей и как вы можете управлять отрисовкой растровых изображений для экранов с различной плотностью пикселей. Если ваше приложение не работает с графикой и у вас не возникало проблем при запуске на экранах с разной плотностью пикселей, вы можете пропустить этот раздел.
Чтобы лучше понять, как можно поддерживать несколько плотностей при манипулировании графикой во время выполнения, вам необходимо знать, как система помогает обеспечить правильный масштаб для растровых изображений. Это делается следующими способами:
- Предварительное масштабирование ресурсов, например растровых изображений
В зависимости от плотности пикселей текущего экрана система использует ресурсы приложения, соответствующие этой плотности. Если таких ресурсов нет, система загружает ресурсы по умолчанию и масштабирует их по мере необходимости. Система предполагает, что ресурсы по умолчанию (из каталога без квалификаторов конфигурации) предназначены для базовой плотности пикселей (mdpi), и изменяет размер этих растровых изображений до размера, соответствующего текущей плотности пикселей.
Если вы запрашиваете размеры ресурса, который уже был масштабирован, система возвращает значения, представляющие размеры после масштабирования. Например, битовая карта размером 50 x 50 пикселей, разработанная для экрана MDPI, масштабируется до 75 x 75 пикселей на экране HDPI (если нет альтернативного ресурса для HDPI), и система сообщает о таком размере.
В некоторых ситуациях вам может не понадобиться, чтобы Android предварительно масштабировал ресурс. Проще всего избежать предварительного масштабирования, поместив ресурс в каталог ресурсов с квалификатором конфигурации
nodpi. Пример:res/drawable-nodpi/icon.png
Когда система использует битовую карту
icon.pngиз этой папки, она не масштабирует ее в зависимости от плотности пикселей на экране устройства. - Автомасштабирование размеров и координат пикселей
Вы можете отключить предварительное масштабирование размеров и изображений, задав для
android:anyDensityзначение"false"в манифесте или программно дляBitmap, задав дляinScaledзначение"false". В этом случае система автоматически масштабирует любые абсолютные пиксельные координаты и значения размеров пикселей во время отрисовки. Это делается для того, чтобы элементы экрана, заданные в пикселях, по-прежнему отображались примерно с тем же физическим размером, что и при базовой плотности пикселей (mdpi). Система выполняет масштабирование незаметно для приложения и сообщает ему размеры в масштабированных пикселях, а не в физических.Например, у устройства есть экран WVGA с высоким разрешением (480 x 800), который примерно такого же размера, как и традиционный экран HVGA. На этом устройстве запущено приложение, в котором отключено предварительное масштабирование. В этом случае система "обманывает" приложение, когда оно запрашивает размеры экрана, и сообщает 320x533, приблизительный перевод mdpi для плотности пикселей.
Затем, когда приложение выполняет операции рисования, например делает недействительным прямоугольник от (10,10) до (100, 100), система преобразует координаты, масштабируя их на соответствующую величину, и фактически делает недействительной область от (15,15) до (150, 150). Это несоответствие может привести к непредвиденному поведению, если ваше приложение напрямую манипулирует масштабированным растровым изображением, но это считается разумным компромиссом для обеспечения наилучшей производительности приложения. В этом случае прочитайте статью Как преобразовать dp в пиксели.
Как правило, отключать предварительное масштабирование не нужно. Лучший способ обеспечить поддержку нескольких экранов – следовать базовым методам, описанным на этой странице.
Если ваше приложение работает с растровыми изображениями или напрямую взаимодействует с пикселями на экране, вам может потребоваться выполнить дополнительные действия для поддержки различной плотности пикселей. Например, если вы реагируете на жесты касания, подсчитывая количество пикселей, которые пересекает палец, вам нужно использовать соответствующие значения пикселей, не зависящих от плотности, вместо фактических пикселей, но вы можете преобразовать значения dp и px.
Тестирование на экранах с разной плотностью пикселей
Протестируйте приложение на нескольких устройствах с разной плотностью пикселей, чтобы убедиться, что интерфейс масштабируется правильно. По возможности тестируйте приложение на физическом устройстве. Если у вас нет доступа к устройствам с разной плотностью пикселей, используйте эмулятор Android.
Если вы хотите провести тестирование на физических устройствах, но не хотите их покупать, воспользуйтесь Firebase Test Lab. Этот сервис позволяет получить доступ к устройствам в центре обработки данных Google.