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, чтобы наше приложение с фрагментами работало на старых версиях.
Создаем фрагмент, который будет отображать список заголовков
TitlesFragment.java:
Класс наследует ListFragment для удобства работы со списком.
onItemClickListener – интерфейс, который будет наследовать Activity. Подробно эту схему мы разбирали в Уроке 106. Интерфейс имеет метод itemClick, который фрагмент будет вызывать при выборе элемента списка.
В onCreate создаем адаптер с заголовками и передаем его списку.
В onAttach записываем Activity (к которому присоединен фрагмент) в listener. Разумеется, это Activity должно реализовывать интерфейс onItemClickListener.
В onListItemClick, мы через listener посылаем в Activity данные о выбранном элементе.
Т.е. этот фрагмент покажет нам заголовки и уведомит Activity о том, какой из них был выбран.
Создаем второй фрагмент, для отображения содержимого.
layout-файл details.xml:
TextView, который будет отображать содержимое.
Класс DetailsFragment.java:
Метод newInstance создает экземпляр фрагмента и записывает в его атрибуты число, которое пришло на вход методу. Это число будет содержать позицию выбранного элемента из списка заголовков.
Метод getPosition достает из аргументов позицию.
onCreateView создает View, находим в нем TextView, и помещает в этот TextView содержимое, соответствующее позиции.
Для нас тут новыми являются аргументы фрагмента. Они могут быть заданы строго до того, как фрагмент будет присоединен к какому либо Activity, т.е., обычно, сразу после создания фрагмента. Они хранятся в фрагменте даже после того, как он был пересоздан в результате, например, смены ориентации экрана. Метод setArguments позволяет записать аргументы, а getArguments – считать.
Этот фрагмент при создании читает содержимое по переданной ему позиции и выводит в TextView.
Настраиваем Activity. layout-файл main.xml:
Слева будет TitlesFragment с заголовками, а в правой части будем помещать DetailsFragment в контейнер FrameLayout.
MainActivity.java:
Activity наследует интерфейс onItemClickListener, чтобы получать оповещения о выбранных элементах от фрагмента со списком заголовков. Поле position будет хранить последний выбранный элемент. Это поле сохраняем (onSaveInstanceState) и читаем (savedInstanceState в onCreate) при пересоздании Activity.
В onCreate вызываем метод, который покажет последнюю выбранную запись.
Метод showDetails получает на вход позицию, ищет DetailsFragment. Если не находит или находит но, отображающий данные по другой позиции, то создает фрагмент заново, передает ему нужную позицию и размещает в контейнер.
itemClick – метод, вызываемый из фрагмента со списком заголовков. В нем мы получаем позицию выбранного элемента в списке. Пишем ее в поле position и вызываем showDetails, который отобразит нужные данные на экране.
Все сохраняем, запускаем приложение.
Выберем какой-либо пункт
все работает, как и должно.
Теперь добавим учет ориентации экрана. В вертикальной ориентации MainActivity будет отображать только заголовки. Фрагмент с содержимым вынесем в отдельное DetailsActivity
DetailsActivity.java:
Код из рубрики: «все слова вроде знакомые, а че сказать хотел - непонятно». Давайте разбираться.
Представим ситуацию. Мы поворачиваем планшет вертикально, у нас отобразятся только заголовки. Мы нажимаем на какой-либо заголовок и переходим на 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 поменяем следующим образом:
Мы удалили контейнер для содержимого, оставили только заголовки. Такой экран мы получим в вертикальной ориентации.
Меняем MainActivity.java:
Изменений немного. Добавляется поле 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
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Небольшая подборка различных примеров для работы с экраном. На самом деле их не так часто приходится использовать в практике, но иметь общее представление бывает необходимым. Начало было положено в 2012 году, что-то могло устареть.
Ориентация у фрагментов
Позже вы узнаете о существовании фрагментов. Может возникнуть такая ситуация, когда вы захотите выводить конкретный фрагмент в нужной ориентации. У фрагментов есть собственный жизненный цикл, и вы можете реализовать свой код в методах фрагмента:
Я с таким случаем не встречался, но оставлю как памятку.
Ошибка при загрузке apk-файла на эмулятор/устройство
Failed to upload xxxxxxxx.apk on device 'emulator-5554'
java.io.IOException: Unable to upload file: timeout
То зайдите в Eclipse в меню Window | Preferences | Android | DDMS и в текстовом поле ADB connection time-out (ms) вместо значения по умолчанию 5000 введите большее значение.
Запретить автоматическую смену ориентации при повороте устройства
Если вы хотите запретить автоматическую смену ориентацию при повороте устройства, то можно ее отключить для нужного экрана. Например, если экран с настройками должен отображаться только в портретном режиме, то добавьте одну строчку с атрибутом screenOrientation в манифест файла для соответствующей активности
На прошлом уроке мы выяснили, что Activity читает layout-файл и отображает то, что в нем сконфигурировано. Теперь выясним, откуда Activity знает, какой именно layout-файл читать.
Урок был обновлен 02.02.2015
Создадим приложение для этого урока. Для этого необходимо создать модуль в проекте Android lessons. Этот проект мы создали в прошлых уроках. Если вы еще не запомнили, как создавать модули, то можно просмотреть еще раз Уроки 3 и 4.
И давайте сразу разберем один момент. Последующие уроки были написаны давно, и написаны они были под среду разработки Eclipse. Соответственно, все уроки содержат информацию для создания проекта в Eclipse.
Но Eclipse отличается от Android Studio организацией проектов/модулей. И сейчас мы разберемся, как информацию по созданию проекта в Eclipse использовать при создании модулей в Android Studio.
Инфа для создания проекта в Eclipse выглядит так:
Project name: P0051_LayoutFiles
Build Target: Android 2.2
Application name: LayoutFiles
Package name: ru.startandroid.develop.LayoutFiles
Create Activity: MainActivity
Обычно, подобный текст есть в начале каждого урока.
А для создания модуля в Android Studio нам нужна информация в таком виде
Application/Library name: LayoutFiles
Module name: p0051layoutfiles
Package name: ru.startandroid.p0051layoutfiles
Как получить из старого новое? Рассмотрим на примере этого урока. Будем брать значения для Eclipse и подставлять их в Android Studio.
Для поля Application/Library name берете значение Application name, без каких-либо изменений. Т.е. LayoutFiles.
Для Module name можно брать значение Project name, но заглавные буквы надо сделать маленькими, а нижнее подчеркивание удалить. Т.е. из P0051_LayoutFiles получаем p0051layoutfiles.
Package name – это ru.startandroid. плюc только что полученный Module name, т.е. ru.startandroid.p0051layoutfiles
Используйте эту схему во всех последующих уроках для создания модулей.
Также, в информации для Eclipse есть поле Create Activity. Его будем использовать при создании модуля, когда указываем имя Activity, в поле Activity Name
Сюда надо подставить значение из Create Activity. Обычно это всегда MainActivity.
Запомните эту инструкцию и применяйте в каждом уроке, чтобы создавать модули.
Возвращаемся к уроку.
При разработке, каждому Activity сопоставляется одноименный java-класс (наследник класса android.app.Activity). При запуске приложения, когда система должна показать Activity и в дальнейшем работать с ним, она будет вызывать методы этого класса. И от того, что мы в этих методах накодим, зависит поведение Activity.
При создании модуля мы указывали, что надо создать Activity с именем MainActivity
Мы попросили создать Activity, и среда разработки создала нам соответствующий класс (в дальнейшем мы научимся их создавать самостоятельно).
Давайте посмотрим этот класс: откроем двойным кликом файл: java\ru\startandroid\p0051layoutfiles\MainActivity.java
Смотрим java-код. Нас интересует метод onCreate – он вызывается, когда приложение создает и отображает Activity (на остальные методы пока не обращаем внимания). Посмотрим код реализации onCreate.
это вызов метода родительского класса, выполняющий необходимые процедуры, его мы не трогаем.
Нас сейчас очень интересует следующая строка:
Метод setContentView(int) – устанавливает содержимое Activity из layout-файла. Но в качестве аргумента мы указываем не путь к layout-файлу (res/layout/activity_main.xml), а константу, которая является ID файла. Эта константа генерируется автоматически в файле R.java, который мы пока трогать не будем. В этом классе будут храниться сгенерированные ID для всех ресурсов проекта (из папки res/*), чтобы мы могли к ним обращаться. Имена этих ID-констант совпадают с именами файлов ресурсов (без расширений).
Файл res/layout/activity_main.xml был создан средой разработки вместе с Activity. Его название запрашивалось на том же экране, где и название Activity (скрин выше).
В последующих уроках этот файл называется обычно main.xml, а не activity_main.xml
Откроем двойным кликом res/layout/activity_main.xml
посмотрим, что там
Запустим приложение и посмотрим, что оно нам покажет
Все верно - Activity отобразил то, что прописано в activity_main.xml.
Попробуем отобразить содержимое другого файла. Создадим еще один layout-файл, например myscreen.xml. Для этого выделим папку res/layout в нашем модуле и нажмем на ней правую кнопку мыши. В появившемся меню выбираем New > Layout resource file. Для любителей горячих клавиш есть более удобный путь: при выделенной папке res/layout нажать ALT+Insert, и там уже Enter на пункте Layout resource file.
Вводим имя файла myscreen, остальное пока не меняем, жмем OK.
В папке layout должен появиться новый файл myscreen.xml
Этот новый layout-файл должен сразу открыться на редактирование. Добавим на экран элемент Plain TextView из списка слева и через Properties изменим его текст на: «new layout file myscreen for activity».
Обязательно сохраняем (CTRL+S).
При создании нового layout-файла myscreen, среда добавила в R.java новую константу для этого файла - R.layout.myscreen. И мы теперь в коде сможем через эту константу указать на этот новый layout-файл.
Настроим так, чтобы Activity использовало новый файл myscreen.xml, а не activity_main.xml, который был изначально. Откроем MainActivity.java и поменяем аргумент метода setContentView. Замените «R.layout.activity_main», на «R.layout.myscreen» (ID нового layout-файла). Должно получиться так:
Сохраняем код (CTRL+S) и запускаем приложение (SHIFT+F10).
Теперь нам предложат подтвердить, что мы хотим запустить приложение на включенном эмуляторе.
Чтобы он при каждом запуске это не спрашивал, включите галку Use same device for future launches и жмите OK.
Видим, что теперь оно отображает содержимое из myscreen.xml, т.к. мы явно ему это указали в методе setContentView, который выполняется при создании (onCreate) Activity
Layout-файл в виде XML
Открыв в Android Studio layout файл activity_main или myscreen, вы видите его визуальное представление. Т.е. некий предпросмотр, как это будет выглядеть на экране. Снизу вы можете видеть две вкладки – Design и Text. Откройте вкладку Text
Мы видим достаточно читабельное xml-описание всех View нашего layout-файла. Названия xml-элементов - это классы View-элементов, xml-атрибуты - это параметры View-элементов, т.е. все те параметры, что мы меняем через вкладку Properties. Также вы можете вносить изменения прямо сюда и изменения будут отображаться во вкладке Design. Например, изменим текст у TextView. Вместо «new layout file myscreen for activity», напишем текст «some new text»
Сохраняем. Открываем Design и наблюдаем изменения.
Обычно авторы учебников дают содержание layout-файлов именно в xml виде. Это удобно – вы можете просто скопировать фрагмент и использовать, и не надо вручную добавлять View-элементы, бегать по Properties и настраивать все руками. Я буду делать в своих проектах так же.
Layout-файл при смене ориентации экрана
По умолчанию мы настраиваем layout-файл под вертикальную ориентацию экрана. Но что будет если мы повернем смартфон и включится горизонтальная ориентация? Давайте смотреть.
Изменим myscreen.xml. Добавим вертикальный ряд кнопок и изменим надпись.
xml-код (вы можете скопировать его и заменить им содержимое вашего layout файла myscreen во вкладке Text):
Обратите внимание - я добавил вертикальный LinearLayout и поместил в него 4 кнопки. Подробнее обсудим это на следующем уроке.
Сохраним файл, запустим приложение.
В вертикальной ориентации все ок.
Нажмем в эмуляторе CTRL+F12, ориентация сменилась на горизонтальную и наши кнопки уже не влезают в экран.
Т.е. нам необходим еще один layout-файл, который был бы заточен под горизонтальную ориентацию и в нашем случае вывел бы кнопки горизонтально.
Но как дать знать Activity, что она в вертикальной ориентации должна использовать один layout-файл, а в горизонтальной – другой? Об этом за нас уже подумали создатели Андроид. У нас есть возможность создать layout-файл, который будет использоваться приложением, когда устройство находится в горизонтальной ориентации.
Создание такого файла почти не отличается от создания обычного layout-файла. Становимся на папку res/layout и создаем новый Layout resource file. Название файла указываем то же самое: myscreen. Осталось добавить спецификатор, который даст приложению понять, что этот layout-файл надо юзать в горизонтальной ориентации. Для этого в списке спецификаторов слева снизу находим Orientation
И жмем кнопку со стрелкой вправо. Тем самым мы включили использование спецификатора ориентации. Нам надо указать, что нас интересует горизонтальная ориентация: Landscape. Выберите это значение из выпадающего списка.
Обратите внимание, что изменилось значение поля Directory name
Настройкой спецификатора мы указали, что наш новый layout-файл будет создан в папке res/layout-land, а не res/layout, как обычно. Т.е. спецификатор –land указывает на то, что layout-файлы из этой папки будут использованы в горизонтальной ориентации устройства.
Посмотрим на структуру модуля
Видим, что у нас теперь два файла myscreen: обычный и land. Можно это же увидеть в структуре папок. Для этого сверху поменяйте вид проекта с Android на Project
И вы увидите, что в модуле теперь есть папки res/layout и res/layout-land. И обе они содержат файл myscreen.
Откроем двойным кликом файл res/layout-land/myscreen и поменяем его содержимое на такой xml-код:
Вкладка Design покажет следующее:
В этом layout файле мы расположили кнопки горизонтально, чтобы они адекватно отображались в горизонтальной ориентации.
Обратите внимание на название файла сверху. Там присутствует спецификатор land, чтобы вы всегда понимали какой из двух myscreen вы сейчас редактируете.
Activity читает layout-файл, который мы указывали в методе setContentView, т.е. myscreen.xml и отображает его содержимое. При этом оно учитывает ориентацию устройства, и в случае горизонтальной ориентации берет myscreen из папки res/layout-land (если он, конечно, там существует).
Переключим ориентацию CTRL+F12.
Activity понимает, что находится в вертикальной ориентации, и использует layout-файл myscreen из папки res/layout.
Еще немного об уроках. Далее почти во всех уроках основной layout-файл будет называться main.xml. Пусть это вас не смущает, просто помните, что ваш основной файл - это activity_main.xml.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Узнать ориентацию программно
Чтобы из кода узнать текущую ориентацию, можно создать следующую функцию:
Вызовите данную функцию из нужного места, например, при щелчке кнопки и узнайте текущую ориентацию. В примере использовались две распространённые системные константы для ориентации. Есть ещё константа ORIENTATION_SQUARE (квадратный экран). Но я таких телефонов не встречал.
Можно также вычислить ширину и высоту экрана, если высота больше ширины, то устройство в портретной ориентации, иначе - в альбомной:
Сейчас этот код считается устаревшим и для вычисления размера экрана используются другие методы (описано в примере Экран).
Сколько используется памяти
Нужно от общей памяти отнять свободную память
Более общий пример:
Кручу-верчу, запутать хочу!
Хорошо, мы можем определить текущую ориентацию, но в какую сторону повернули устройство? Ведь его можно повернуть влево, вправо или вообще вверх тормашками. Напишем другую функцию:
Раньше существовал аналогичный метод getOrientation(), который устарел. Используйте getRotation(). Впрочем, спустя несколько лет и этот код теперь считается устаревшим. Замену не искал.
На многих телефонах и эмуляторах код работает частично или не работает или работает неправильно. Точно работает на планшете Google Nexus 7 2013. Не стоит полагаться на код в рабочем приложении.
Запрет на создание новой активности
На примере программной установки ориентации можно увидеть интересный эффект, о котором нужно помнить. Предположим у нас есть кнопка, позволяющая менять ориентацию. Заодно будем менять текст на кнопке, чтобы операция соответствовала надписи.
Теперь посмотрите, что у нас получилось. Запустите проект и нажмите на кнопку. Ориентация экрана поменялась, однако текст на кнопке остался прежним, хотя по нашей задумке он должен измениться.
Нажмём на кнопку ещё раз. Надпись изменится, но ориентация не сменится. И только повторный щелчок повернёт экран в обратную сторону.
По умолчанию, при смене ориентации Android уничтожает и пересоздаёт активность из кода, что подразумевает повторный вызов метода onCreate(). Поэтому при повороте активность устанавливала текст, определенный в onCreate(). В большинстве случаев это не мешает программе. Но если приложение воспроизводит видео, то при смене ориентации вызов onCreate() может привести к повторному началу воспроизведения (если так написан пример).
Чтобы активность не пересоздавалась, добавьте в манифест строчку для нужной активности:
Во многих примерах, которые я видел, используется пара keyboardHidden|orientation, но, похоже, в Android 4 этого недостаточно и нужно добавить ещё один атрибут screenSize
При изменении ориентации система вызовет метод onConfigurationChanged(Configuration) и мы можем отловить поворот экрана:
В документации говорится, что данный способ следует избегать.
Как использовать собственный значок для программы
Когда вы создаете учебные примеры, то у всех программ используется стандартный значок с изображением андроида. Как же использовать свой значок? Ответ прост. В папке проекта /res вы можете видеть подпапки drawable-ldpi, drawable-mdpi, drawable-hdpi и др., в которых и содержатся готовые значки в формате PNG под разные размеры экранов.
Вы можете просто открыть png-файл в графическом редакторе и нарисовать что-то свое и сохранить изменения.
Вы можете сохранить значки под своим именем, например, cat.jpg. В этом случае вам надо открыть файл манифеста, найти там строчку:
И отредактировать её, например, android:icon="@drawable/cat" (без расширения). Как вариант, вы можете сохранить один значок в папке drawable в нужном размере, если пишете программу под определенные типы телефонов.
Размеры значков в папках (часть):
- Low density – ldpi = 36×36 px
- Medium density – mdpi = 48×48 px
- High density – hdpi = 72×72 px
- Extra-high density screen - xhdpi (320 dpi) = 96 × 96 px
Проверка на первый запуск приложения
Для проверки первого запуска приложения можно использовать настройки SharedPreferences. Смотрите пример.
Убить приложение
Аналог html-кода
Получить номер версии программы
Размеры картинок для фона экрана
Если вы используете изображение в качестве фона, то нет смысла выводить очень большую картинку на устройстве с маленьким экраном. Можно подготовить разные размеры.
res/drawable-ldpi - 240x320
res/drawable-mdpi - 320x480
res/drawable-hdpi - 480x800
res/drawable-xhdpi - 640x960
res/drawable-xxhdpi - 960x1440
res/drawable-tvdpi - 1.33 * mdpi
Когда создавались первые портативные устройства - КПК и смартфоны, то за основу бралась настольная операционная система и допиливалась под мобильное устройство. Лишние функции удалялись, а некоторые функции добавлялись. Но при этом как-то совсем упустили из виду, что в отличие от громоздких мониторов и экранов ноутбуков, карманные устройства можно вращать в руках. Первые устройства не умели менять ориентацию экрана. Некоторые программисты самостоятельно стали создавать программы, которые умели переключаться в разные режимы. Затем эту возможность стали включать в настройки аппарата. Позже аппараты научились самостоятельно определять ориентацию экрана.
Всего существует два режима - портретный и альбомный. На большинстве телефонов используется по умолчанию портретный режим (как на паспорте). Альбомный режим знаком нам по обычным мониторам.
Рассмотрим следующий случай. Предположим, у нас в приложении имеется одно текстовое поле и шесть кнопок. Вроде всё нормально.
Но стоит нам повернуть устройство на 90 градусов, как сразу обнаруживаются проблемы. Пятая кнопка видна частично, а шестая вообще оказалась за пределами видимости. Непорядок!
Чтобы избежать такой проблемы, необходимо как-то по другому скомпоновать кнопки. Например, расположить их не подряд друг за другом, а разбить на пары. Воспользуемся контейнером TableLayout. С его помощью мы можем разбить кнопки на две колонки и поместить их в три ряда.
Для этой операции нам понадобится сделать несколько важных шагов. Сначала нужно создать новую подпапку в папке res. Выделяем папку res, вызываем из него контекстное меню и последовательно выбираем команды New | Android resource directory. В диалоговом окне из выпадающего списка Resource type: выбираем layout. В списке Available qualifiers: находим элемент Orientation и переносим его в правую часть Chosen qualifiers: с помощью кнопки с двумя стрелками. По умолчанию у вас появится имя папки layout-port в первой строке Directory Name:. Но нам нужен альбомный вариант, поэтому в выпадающем списке Screen orientation выбираем Landscape. Теперь название папки будет layout-land.
Можно обойтись без помощи мастера, создав папку сразу через меню New | Directory. Этот способ годится для опытных разработчиков, которые знают, как следует назвать папку. Важно запомнить, что имя даётся не произвольно, а именно в таком виде layout-land. По суффиксу -land система понимает, что речь идёт о новом режиме. Теперь нам осталось создать в созданной папке новый XML-файл activity_main.xml. Вызываем контекстное меню у папки layout-land и выбираем команды New | Layout Resource File. В диалоговом окне присваиваем имя activity_main.xml, которое должно совпадать с именем существующего файла. Во втором поле вводим LinearLayout, по мере ввода появится подсказка, облегчающая выбор.
Откроем созданный файл и модифицируем его следующим образом.
Запускаем приложение и проверяем. Отлично, теперь видны все кнопки. Поздравляю, вы гений!
Когда вы создаёте альтернативную разметку, то не забывайте включать все компоненты, к которым будете обращаться программно, иначе получите ошибку. Допустим, вы забыли добавить шестую кнопку. В портретном режиме программа будет работать, а когда пользователь перевернёт экран, то активность будет инициализировать все компоненты для работы, а кнопки-то и нет. Крах приложения и минусы в отзывах.
Исчезающий текст
Как уже говорилось, при смене ориентации активность пересоздаётся. При этом можно наблюдать интересный эффект с пропадающим текстом. Чтобы увидеть эффект, создадим два текстовых поля. Одному из них присвоим идентификатор, а другое поле оставим без него.
Запустите приложение, введите любой текст в обоих полях и смените ориентацию. Вы увидите, что у поля с идентификатором текст при повороте сохранится, а у поля без идентификатора текст исчезнет. Учитывайте данное обстоятельство.
К вышесказанному могу добавить, что при смене ориентации у поля с идентификатором вызывается метод onTextChanged():
В чем разница между fill_parent и match_parent
match_parent = fill_parent. Первое свойство нужно использовать в новых проектах, второе свойство считается устаревшим и пока оставлено в целях совместимости.
Кодирование в режиме Base64
Осталось применить её где-нибудь:
Как убрать заголовок у Activity?
В некоторых случаях хочется спрятать заголовок (Title) у программы. Есть несколько способов. Например, применить специальную тему (прописать в манифесте файла):
Также попробуйте android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" (убирает не только заголовок, но и панель уведомлений). Названия тем могут быть и другими, смотрите документацию. Несколько примеров
Если у вас используется своя тема, то используйте в ней параметр:
Также существует программный способ (перед вызовом setContentView):
Как заблокировать экран в приложении?
Также нужно установить разрешение android.permission.DISABLE_KEYGUARD.
Если стоит обратная задача - запретить блокировку экрана при долгом бездействии, то используйте метод setKeepScreenOn() или используйте XML-атрибут android:keepScreenOn="true".
Установить ориентацию программно и через манифест
Если вы большой оригинал и хотите запустить приложение в стиле "вид сбоку", то можете сделать это программно. Разместите код в методе onCreate():
Учтите, что в этом случае котам не очень удобно будет пользоваться вашим приложением.
Вы можете запретить приложению менять ориентацию, если добавите нужный код в onCreate().
Но указанный способ не совсем желателен. Лучше установить нужную ориентацию через манифест, прописав в элементе параметр android:screenOrientation:
Кстати, существует ещё один вариант, когда устройство полагается на показания сенсора и некоторые другие:
В Android 4.3 (API 18) появились новые значения (оставлю пока без перевода):
- userLandscape - Behaves the same as "sensorLandscape", except if the user disables auto-rotate then it locks in the normal landscape orientation and will not flip.
- userPortrait - Behaves the same as "sensorPortrait", except if the user disables auto-rotate then it locks in the normal portrait orientation and will not flip.
- fullUser - Behaves the same as "fullSensor" and allows rotation in all four directions, except if the user disables auto-rotate then it locks in the user's preferred orientation.
- locked - to lock your app's orientation into the screen's current orientation.
После появления Android 5.0 зашёл на страницу документации и пришёл в ужас. Там появились новые значения.
Настраиваем яркость экрана в своём приложении
Существует возможность переопределить яркость экрана в пределах своего приложения. Я не смог придумать, где можно найти практическое применение, но вдруг вам пригодится. Для управления яркостью экрана воспользуемся элементом SeekBar.
За яркость экрана отвечает свойство LayoutParams.screenBrightness:
Интересно, что когда выводил ползунок в значение 0, то эмулятор зависал с экраном блокировки. Вам следует учесть эту ситуацию и добавить условие:
Опять столкнулся с проблемой. Пример работал на старых устройствах, а на некоторых устройства не работает. Но за эти годы мне ни разу не пришлось использовать этот способ, поэтому даже не стал искать причину. И кстати, ошибка со значением 0 уже не возникает (возможно из-за того, что сам пример не работает как раньше).
Запоминаем значения переменных
С поворотом экрана возникает одна очень неприятная проблема. Вдумайтесь в значение слов, что при повороте экрана активность создаётся заново. Чтобы было понятно, нужно вернуться к проекту, в котором мы считали ворон. Если вы его удалили, то придётся пройти урок заново и восстановить его.
Щёлкните несколько раз по кнопке. Допустим на экране красуется надпись "Я насчитал 5 ворон". Поворачиваем экран - куда улетели все вороны? На экране появилась надпись, что . Впрочем, я не буду говорить вам, сами посмотрите.
А что собственно произошло? Я же вас предупреждал, что активность при повороте создаётся заново. А значит переменная mCount снова принимает значение 0, т.е сбрасывается в начальное значение.
Что же делать? Для этих целей у активности существует специальный метод onSaveInstanceState(), который вызывается системой перед методами onPause(), onStop() и onDestroy(). Метод позволяет сохранить значения простых типов в объекте Bundle. Класс Bundle - это простой способ хранения данных ключ/значение.
Создадим ключ с именем KEY_COUNT. В Android Studio c версии 1.5 появились живые шаблоны, позволяющие быстро создать ключ. Введите до метода onCreate() строчными буквами слово key, во время набора появится подсказка. Нажимаем Enter и получаем заготовку. После символа подчёркивания вводим название ключа. В результате получим ключ следующего вида.
Далее создаём метод onSaveInstanceState() после метода onCreate(). Во время набора имени метода подсказка покажет, что имеется два метода. Выбирайте метод с одним параметров (обычно он идёт вторым). Записываем в ключа значение счётчика.
А в методе onCreate() делаем небольшую проверку.
У метода в параметре содержится объект Bundle. Только здесь он назван savedInstanceState вместо outState, но пусть вас это не вводит заблуждение. Имена вы можете придумывать сами. Главное, что объект содержит сохранённое значение переменной при повороте. При первом запуске приложения объект не существует (null), а потом мы его создали своим кодом. Для этого и нужна проверка. Обратите внимание, что здесь мы не прибавляем единицу к счётчику, как у кнопки. Если скопировать код у кнопки, то получится, что счётчик будет увеличиваться самостоятельно при поворотах без нажатия на кнопку. Прикольно, конечно, но может ввести в заблуждение пользователя. Хотя, если вы пишите приложение "Я твой дом труба шатал", то такой способ может пригодиться для подсчёта, сколько раз вы вертели телефон, чтобы разрушить чей-то дом.
Обращаю ваше внимание, что данный способ используется для сохранения промежуточных результатов во время действия программы. В следующих уроках вы узнаете, как можно сохранять результат между запусками приложения.
Проверка на существование
Если вы используете две разные разметки, то возможна ситуация, когда в альбомной ориентации используется кнопка, которой нет в портретной ориентации. Это можете привести к ошибке в коде, поэтому нужно проверить существование кнопки:
На практике такое встречается редко, но помните на всякий случай.
Определение поддерживаемых экранных размеров в манифесте
Не всегда предоставляется возможным написать приложение для всех возможных типов экранов. Вы можете использовать тег в манифесте, чтобы указать, на устройствах с какими экранами может работать ваша программа.
В данном примере приводится поддержка нормальных и больших экранов. Маленьким экраном можно назвать любой дисплей с разрешением меньше, чем HVGA. Под большим экраном подразумевается такой, который значительно больше, чем у смартфона (например, у планшетов). Экран нормальных размеров имеет большинство смартфонов.
Атрибут anyDensity говорит о том, каким образом ваше приложение будет масштабироваться при отображении на устройствах с разной плотностью пикселов. Если вы учитываете это свойство экрана в своем интерфейсе, установите этому атрибуту значение true. При значении false Android будет использовать режим совместимости, пытаясь корректно масштабировать пользовательский интерфейс приложения. Как правило, это снижает качество изображения и приводит к артефактам при масштабировании. Для приложений, собранных с помощью SDK с API level 4 и выше, этот атрибут по умолчанию имеет значение true.
Настройки - Экран
Чтобы показать окно Экран из системного приложения Настройки:
Запретить делать скриншот экрана приложения
Существует специальный флаг FLAG_SECURE, запрещающий пользователю сделать скриншот вашего экрана. Это нужно в целях безопасности для отдельных видов приложений, например, банковских.
Узнать размеры экрана
Для вычисления размеров экрана можно воспользоваться двумя способами. Второй способ более правильный.
Имитация тяжёлых задач
В Android нельзя выполнять сложные задачи в основном потоке. Для создания быстрой заглушки, которая будет имитировать тяжёлую задачу, можно написать следующий код:
Раскодирование HTML-символов
Если вам нужно раскодировать HTML-символы (угловые скобки <>, знак амперсанда &, кавычки ""), то воспользуйтесь методом TextUtils.htmlEncode(). Добавим на форму две текстовые метки. И небольшой код для примера:
Примечание: Если вы пользуетесь Notepad++, то там есть такая же возможность: TextFX | TextFX Convert | Encode HTML.
Сделать скриншот экрана своего приложения
Один из примеров снятия скриншота своего экрана - через методы рисования получить графический отпечаток корневой разметки, а затем сохранить его как картинку, которую можно вывести в ImageView. Выберите любой ваш экран для опытов и добавьте на неё кнопку для снятия скриншотов и ImageView для вывода сохранённого изображения. В моём случае корневым элементом была компоновка TableLayout.
Результат до и после снятия скриншота. Сам скриншот помещается в ImageView, поэтому растягивается до его размеров. Попробуйте доработать пример.
Изменённый пример, когда скриншот сохраняется на SD-карту:
Не забываем про разрешения.
Ошибка Debug Certificate expired
Вам следует удалить старый сертификат, и Eclipse создаст новый ещё на один год. Путь к сертификату можно посмотреть в настройках: Preferences | Android | Build | Default debug keystore. Обычно это папка C:\Users\ИмяПользователя\.android.
Доступ к ресурсам через URI
Обычно мы обращаемся к ресурсам через идентификатор. Существует альтернативный способ через Uri. Например, такой подход может пригодиться для загрузки изображения в WebView при помощи метода loadUrl(). Формат доступа будет следующим: android.resource://[package-name]/res-id. Например:
Убрать предупреждающие значки при использовании строк в атрибутах
Если в некоторых атрибутах компонентов использовать строки, то появляются предупреждающие значки с всплывающей надписью [I18N] Hardcoded string "Котики рулят", should use @string resource. Надпись призывает использовать строковые ресурсы, а не писать текст прямо в атрибутах. На самом деле это всего лишь предупреждение, а не ошибка. Программа будет прекрасно запускаться, но новичков такие надписи пугают. Можете убрать данное предупреждение, если добавите дополнительный атрибут tools:ignore.
Где находятся исходники системного калькулятора?
Получить текущее значение яркости экрана
В настройках экрана можно установить желаемую яркость экрана при помощи ползунка, но при этом мы не знаем, сколько это в попугаях. Я открою вам секрет при помощи простого кода:
Разрешённый объём памяти для приложения
Система выделяет каждому приложению определённый лимит памяти. На разных устройствах размер выделяемой памяти отличается. Чтобы узнать размер выделяемой памяти для вашего приложения, используйте код:
Особенно это касается приложений, которые активно используют графику. Следите за размерами изображений, вызывайте метод recycle() для освобождения памяти при работе с классом BitmapFactory.
Начиная с Android 3.0 можно попросить у системы выделять чуть больше памяти, прописав в манифесте просьбу:
Плотность экрана, масштабирование шрифта и др.
Существует класс DisplayMetrics, также имеющий в своём составе свойства для экрана. Пример также пришлось переписывать после выхода Android 11 (API 30), который теперь тоже устаревший:
Вот ещё несколько способов определения размеров:
Такой же код, но с использованием дополнительной константы SCREENLAYOUT_SIZE_MASK:
На Kotlin в виде отдельной функции.
Заглянув в документацию, обнаружил, что можно обойтись без собственных констант. Они уже есть в Android. Оформил в виде отдельного метода.
Кодирование строк по схеме UTF-8
Вероятно, вы не раз замечали, что в адресной строке некоторые символы заменяются на последовательность других символов. Самый простой пример - символ пробела заменяется на %20. Немного изменим предыдущий пример:
В нашем примере были преобразованы символы пробела и русские символы.
Установить яркость экрана
Если можно получить значение текущей яркости экрана, значит можно и установить яркость. Для начала нужно установить разрешение на изменение настроек в манифесте:
Для настройки яркости нужно использовать параметр System.SCREEN_BRIGHTNESS. Добавим на форму кнопку, метку и ползунок. Код для установки яркости:
Проверил старый пример времён Android 2.2 на эмуляторе с Android 10. Правила ужесточились. Теперь разрешение на изменение системных настроек выдаются только системным программам. Пока ещё есть лазейка, которой и воспользуемся. Новый пример написан на Kotlin. Добавим в манифест немного модифицированное разрешение.
Далее программа должна проверить возможность изменять системные настройки через метод canWrite(). Если такая возможность есть, то запрашиваем разрешение. Появится специальное окно, в котором пользователь должен подтвердить своё решение через переключатель. После этого нужно заново запустить программу, чтобы ползунок стал доступен. Теперь можете менять настройки.
Размеры экрана и его ориентация (Старый и новый способ)
Чтобы узнать размеры экрана и его ориентацию из запущенного приложения, можно воспользоваться встроенными классами Android.
Ориентацию при помощи нового метода не узнаешь. Помните, что это размеры экрана устройства, а не экрана вашего приложения. Кроме того, в документации как-то туманно описывается точность вычислений этих размеров. Никому верить нельзя.
Жизненный цикл при повороте
При повороте активность проходит через цепочку различных состояний. Порядок следующий.
В папке res/xml размещаем файл network_security_config.xml:
В манифесте в блоке application добавляем новый атрибут.
Читайте также: