Android для разных размеров экрана

Полный список

— учитываем ориентацию и размер экрана в работе приложения

На странице фрагмента на официальном сайте приводятся куски кода приложения. Это приложение отображает список заголовков статей и содержимое выбранной статьи, а его вид зависит от ориентации экрана. В горизонтальной ориентации, оно отображает и заголовки и содержимое(два фрагмента в одном Activity). В вертикальной ориентации заголовки и содержимое отображаются на разных экранах (два фрагмента разделены по двум Activity).

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

Соответственно урок состоит из трех частей.

1. Приложение, отображающее слева заголовки, а справа – содержимое

2. Добавляем учет ориентации. При вертикальной будем отображать заголовки на первом экране, а содержимое на втором.

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

Долго думал, как назвать проект. Решил – MultipleScreen.

Project name: P1151_MultipleScreen
Build Target: Android 2.2
Application name: MultipleScreen
Package name: ru.startandroid.develop.p1151multiplescreen
Create Activity: MainActivity

Добавим строки в strings.xml:

Два массива – заголовки и содержимое.

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

Создаем фрагмент, который будет отображать список заголовков

Класс наследует ListFragment для удобства работы со списком.

onItemClickListener – интерфейс, который будет наследовать Activity. Подробно эту схему мы разбирали в Уроке 106. Интерфейс имеет метод itemClick, который фрагмент будет вызывать при выборе элемента списка.

В onCreate создаем адаптер с заголовками и передаем его списку.

В onAttach записываем Activity (к которому присоединен фрагмент) в listener. Разумеется, это Activity должно реализовывать интерфейс onItemClickListener.

В onListItemClick, мы через listener посылаем в Activity данные о выбранном элементе.

Т.е. этот фрагмент покажет нам заголовки и уведомит Activity о том, какой из них был выбран.

Создаем второй фрагмент, для отображения содержимого.

TextView, который будет отображать содержимое.

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

Метод getPosition достает из аргументов позицию.

onCreateView создает View, находим в нем TextView, и помещает в этот TextView содержимое, соответствующее позиции.

Для нас тут новыми являются аргументы фрагмента. Они могут быть заданы строго до того, как фрагмент будет присоединен к какому либо Activity, т.е., обычно, сразу после создания фрагмента. Они хранятся в фрагменте даже после того, как он был пересоздан в результате, например, смены ориентации экрана. Метод setArguments позволяет записать аргументы, а getArguments – считать.

Этот фрагмент при создании читает содержимое по переданной ему позиции и выводит в TextView.

Настраиваем Activity. layout-файл main.xml:

Слева будет TitlesFragment с заголовками, а в правой части будем помещать DetailsFragment в контейнер FrameLayout.

Activity наследует интерфейс onItemClickListener, чтобы получать оповещения о выбранных элементах от фрагмента со списком заголовков. Поле position будет хранить последний выбранный элемент. Это поле сохраняем (onSaveInstanceState) и читаем (savedInstanceState в onCreate) при пересоздании Activity.

В onCreate вызываем метод, который покажет последнюю выбранную запись.

Метод showDetails получает на вход позицию, ищет DetailsFragment. Если не находит или находит но, отображающий данные по другой позиции, то создает фрагмент заново, передает ему нужную позицию и размещает в контейнер.

itemClick – метод, вызываемый из фрагмента со списком заголовков. В нем мы получаем позицию выбранного элемента в списке. Пишем ее в поле position и вызываем showDetails, который отобразит нужные данные на экране.

Все сохраняем, запускаем приложение.

Выберем какой-либо пункт

все работает, как и должно.

Читайте также:  Что такое webview android studio

Теперь добавим учет ориентации экрана. В вертикальной ориентации MainActivity будет отображать только заголовки. Фрагмент с содержимым вынесем в отдельное DetailsActivity

Код из рубрики: «все слова вроде знакомые, а че сказать хотел — непонятно». Давайте разбираться.

Представим ситуацию. Мы поворачиваем планшет вертикально, у нас отобразятся только заголовки. Мы нажимаем на какой-либо заголовок и переходим на DetailsActivity, которое покажет нам содержимое (средствами DetailsFragment, разумеется). Т.е. мы имеем вертикальную ориентацию и видим содержимое. Теперь поворачиваем планшет горизонтально. Что будет? DetailsActivity отобразится во весь горизонтальный экран и покажет содержимое. Но наша концепция гласит, что в горизонтальной ориентации приложение должно показывать и содержимое и заголовки, ширина экрана ведь позволяет это сделать. А, значит, нам надо вернуться в MainActivity.

Смотрим первый фрагмент кода. Приложение определяет, что ориентация горизонтальная и в этом случае просто закрывает Activity. И т.к. это DetailsActivity у нас будет вызвано из MainActivity, то после finish мы попадаем в MainActivity и видим то, что нужно – и заголовки, и содержимое. Причем MainActivity хранит номер выбранного заголовка (независимо от ориентации) и содержимое отобразится то же самое, что было в DetailsActivity.

Смотрим второй фрагмент. Мы проверяем, что savedInstanceState == null – это означает, что Activity создается первый раз, а не пересоздается после смены ориентации экрана. Далее мы создаем фрагмент DetailsFragment, используя позицию из интента, и помещаем его в Activity.

Почему создаем фрагмент только при создании Activity и при пересоздании — нет? Потому что система сама умеет пересоздавать существующие фрагменты при поворотах экрана, сохраняя при этом аргументы фрагмента. И нам совершенно незачем в данном случае пересоздавать фрагмент самим.

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

Не забудьте прописать Activity в манифесте.

Создадим папку res/layout-land и скопируем туда основной layout — res/layout/main.xml. Т.е. при горизонтальной ориентации у нас все останется, как есть.

А res/layout/main.xml поменяем следующим образом:

Мы удалили контейнер для содержимого, оставили только заголовки. Такой экран мы получим в вертикальной ориентации.

Изменений немного. Добавляется поле withDetails, которое будет сообщать нам: показывает Activity заголовки с содержимым или без. В нашем случае это будет совпадать соответственно с горизонтальной и вертикальной ориентацией.

В onCreate мы задаем значение withDetails с помощью проверки наличия контейнера для фрагмента с содержимым. Если у нас должно отображаться содержимое, то вызываем метод showDetails и передаем ему позицию. Если содержимое не должно отображаться, то ничего не делаем, показываем только заголовки.

В методе showDetails мы смотрим withDetails. Если содержимое должно быть показано здесь же, то работает старый алгоритм, мы создаем фрагмент. Если же содержимое должно быть показано в отдельном Activity, то вызываем DetailsActivity и передаем ему позицию.

Все сохраняем, запускаем приложение.

видим только заголовки

видим содержимое в новом Activity

видим содержимое и заголовки

Осталось подстроить работу приложения под маленькие экраны. Напомню, что на мелких экранах мы при любой ориентации будем разделять заголовки и содержимое по разным Activity. Размер экрана можно определять по-разному. Я буду считать большими экраны large и xlarge. Для этих экранов логика остается текущая, для остальных немного изменим.

Ориентация экрана бывает горизонтальная (land) и вертикальная (port). Экраны мы будем различать: мелкие, large и xlarge. Итого у нас получается 6 комбинаций экранов.

И у нас уже есть два варианта файла main.xml – «заголовки с содержимым» и «только заголовки«.

Надо сопоставить комбинации и варианты.

Вариант «заголовки с содержимым» будем использовать в комбинациях 5,6 (большие экраны в горизонтальной ориентации)

Читайте также:  Если полетел андроид планшет

Вариант «только заголовки» подходит к комбинациям 1,2,3,4 (мелкие экраны в любой ориентации и большие экраны в вертикальной ориентации).

Создаем layout-папки под эти комбинации, и помещаем в них варианты экранов.

Папка res/layout-large-land. Это комбинация 5. Сюда помещаем вариант main.xml, который «заголовки с содержимым».

Папка res/layout-xlarge-land. Это комбинация 6. Сюда помещаем вариант main.xml, который «заголовки с содержимым».

Папка res/layout. Это все остальные комбинации (1,2,3,4). Сюда помещаем вариант main.xml, который «только заголовки».

Т.е. в итоге получается, что мы в папки layout-large-land и layout-xlarge-land копируем файл из layout-land, и удаляем папку layout-land. Папку layout не трогаем, там все ок.

Осталось немного изменить DetailsActivity.java:

Метод isLarge определяет, большой экран или нет. Если раньше мы в горизонтальной ориентации сразу закрывали это Activity, то теперь будем делать это только для больших экранов. А на остальных содержимое будет отображаться в горизонтальной ориентации.

Все сохраняем и запускаем приложение.

Скрины с планшета приводить не буду. Они полностью повторят предыдущие.

А на смартфоне с маленьким экраном будет так:

Жмем назад и попадаем к заголовкам

Немаленький такой материал получился. Но, вроде, все рассказал, чего хотел.

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

Если остались непонятные моменты – велкам на форум, будем решать.

На следующем уроке:

— меняем поведение Activity в Task

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Pixel-perfect верстка Android макетов

Разрабатывать интерфейс Android приложений — непростая задача. Приходится учитывать разнообразие разрешений и плотностей пикселей (DPI). Под катом практические советы о верстке макетов дизайна Android приложений в Layout, который совпадает с макетом на одном устройстве а на остальных растягивается без явных нарушений дизайна: выхода шрифтов за границы; огромных пустых мест и других артефактов.

UPD: Пост спорный, но много ценных советов в комментариях

UPD: Мне искренне жаль, я как маркетолог, не до конца разобрался с темой и не передал самую главную идею поста. Этот метод использовался на проекте, где заказчик предоставлял дизайн и его требованием было чтобы он выглядел максимально одинаково на всех устройствах. Исходя из этого и появился такой метод верстки, он спорный и подходит не всегда.

Большое спасибо комментаторам, за то что поправили меня и высказали свою точку зрения на правильную верстку. Если вам не нужно абсолютное совпадение макетов, резюме советов из комментариев:

  • Android Design in Action: Responsive Design видео на английском как должен тянутся дизайн Android приложений
  • Supporting Multiple Screens базовая информация о разных размерах экранов Android устройств, DPI и как адаптировать приложения под несколько экранов.
  • Designing for Multiple Screens три прекрасных урока от Google о поддержке разных разрешений экрана и DPI, рекомендую начать с него.
  • совет по масштабированию шрифтов от silentnuke

Используйте разные значения размера шрифта для разных разрешений экрана.
в values\dimens.xml text_size=16sp
в values-sw600dp\dimens.xml text_size=20sp
в values-sw720dp\dimens.xml text_size=24sp

Если нужно, присмотритесь к нашей идее в посте.

На iPhone layout задаются абсолютно и всего под два экрана iPhone 4 и iPhone 5. Рисуем два макета, пишем приложение и накладываем полупрозрачные скриншоты на макеты. Проблем нет, воля дизайнера ясна, проверить что она исполнена может сам разработчик, тестировщик или, даже, билд-сервер.

Под Android у нас две проблемы: нельзя нарисовать бесконечное число макетов и нельзя сверить бесконечное число устройств с конечным числом макетов. Дизайнеры проверяют вручную. Разработчики же часто понятия не имеют как правильно растягивать элементы и масштабировать шрифты. Количество итераций стремится к бесконечности.

Чтобы упорядочить хаос мы пришли к следующему алгоритму верстки. Макеты рисуются и верстаются под любой флагманский full-hd телефон. На остальных красиво адаптируются. Готовое приложение проверяет дизайнер на популярных моделях смартфонов. Метод работает для всех телефонов, для планшетов (>6.5 дюймов) требуются отдельные макеты и верстка.

Под рукой у меня только Nexus 4 возьмем его характеристики экрана для примера.

Макеты ненастоящего приложения-портфолио которые будем верстать (полноразмерные по клику).

Layout

Основную верстку делаем через вложенные LinearLayout. Размеры элементов и блоков в пикселях переносим с макета в weight и weightSum соответственно. Отступы верстаем FrameLayout или в нужных местах добавляем Gravity.

Для примера сверстаем ячейку списка приложений:

Дальше нам потребуется много DisplayMetrics-магии, напишем для него static helper.

1184 это высота Nexus 4 без кнопок, 768 — ширина. Эти значения используются, чтобы выяснить во сколько раз высота и ширина устройства, на котором запущено приложение, отличаются от эталонного.

ScrollView и List

Подход с weightSum не примемим к прокручивающимся элементам, их внутренний размер вдоль прокрутки ничем не ограничен. Для верстки ScrollView и List нам потребуется задать их размеры в коде (130 — высота элемента списка).

И дальше можно применять трюк с weightSum.

Картинки

Размер иконок приложений задается в коде:

Где 240 высота элемента списка, 20 высота отступа сверху и снизу.

Шрифты

Андроид не предоставляет единицу измерения пропорциональную размеру экрана. Размеры шрифтов рассчитываем на основании диагонали устройства:

Да, размеры шрифта придется задавать в коде (36 размер шрифта в пикселях на оригинальном макете).

Советы по работе с графикой

1. Используйте Nine-patch везде где возможно, где невозможно — перерисуйте дизайн.
2. Простые элементы рисуйте с помощью Shape
3. Избегайте масштабирования изображений в runtime

Nine-patch это графический ресурс содержащий в себе мета-информацию о том как он должен растягиваться. Подробнее в документации Android или на Хабре.

Nine-patch нужно нарезать под все dpi: ldpi mdpi tvdpi hdpi, xhdpi, xxhdpi. Масштабирование ресурсов во время работы приложения это плохо, а масштабирование Nine-Patch приводит к неожиданным артефактам. Ни в коем случае не задавайте в Nine-patch отступы, они оформляются отдельными элементами layout, чтобы растягиваться пропорционально контенту.

Shape

Если ресурс легко раскладывается на простые геометрические фигуры и градиенты лучше вместо нарезки использовать xml-shape. Для примера нарисуем фон рамку вокруг проекта в списке, которую мы выше нарезали как Nine-patch.

Картинки

Масштабирование графики силами Android трудоемкая и затратная по памяти операция. Картинки внутри Android обрабатываются как bitmap. Например, наш логотип в размере 500×500 со сплешскрина распакуется в bitmap размером 1мб (4 байта на пиксель), при масштабировании создается еще один bitmap, скажем в 500кб. Или 1,5мб из доступных 24мб на процесс. Мы не раз сталкивались с нехваткой памяти в богатых на графику проектах.

Поэтому картинки которые нельзя описать ни Nine-patch ни Shape я предлагаю поставлять в приложении как огромный ресурс в папке nodpi и при первом запуске масштабировать изображение до нужного размера и кешировать результат. Это позволит нам ускорить работу приложения (не считая первого запуска) и уменьшить потребление памяти.

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

P.S. советы придуманы и основа поста написаны нашим Android-гуру Лешей, огромное ему спасибо!

А как вы рекомендуете верстать макеты под Android? Сколько макетов рисует дизайнер? Как обращаетесь с графическими ресурсами?

Подписывайтесь на наш хабра-блог (кнопка справа вверху). Каждый четверг интересные статьи о мобильной разработке, маркетинге и бизнесе мобильной студии. Следующая статья (5 сентября) «C# async на iOS и Android»

Источник

Читайте также:  Очистка внутренней памяти андроид хонор
Оцените статью