- Ориентация
- Вступление
- Узнать ориентацию программно
- Кручу-верчу, запутать хочу!
- Установить ориентацию программно и через манифест
- Запрет на создание новой активности
- Исчезающий текст
- Проверка на существование
- Запоминаем значения переменных
- Ориентация у фрагментов
- Жизненный цикл при повороте
- Screen Orientations in Android with Examples
- AndroidManifest.xml
- Handling Orientation Changes on Android
- Introduction
- Background
- What Not To Do
- Saving State
- Restoring State
- Adding Fragments
- Handling AsyncTasks
- So Where Does That Leave Us?
Ориентация
Вступление
Когда создавались первые портативные устройства — КПК и смартфоны, то за основу бралась настольная операционная система и допиливалась под мобильное устройство. Лишние функции удалялись, а некоторые функции добавлялись. Но при этом как-то совсем упустили из виду, что в отличие от громоздких мониторов и экранов ноутбуков, карманные устройства можно вращать в руках. Первые устройства не умели менять ориентацию экрана. Некоторые программисты самостоятельно стали создавать программы, которые умели переключаться в разные режимы. Затем эту возможность стали включать в настройки аппарата. Позже аппараты научились самостоятельно определять ориентацию экрана.
Всего существует два режима — портретный и альбомный. На большинстве телефонов используется по умолчанию портретный режим (как на паспорте). Альбомный режим знаком нам по обычным мониторам.
Рассмотрим следующий случай. Предположим, у нас в приложении имеется одно текстовое поле и шесть кнопок. Вроде всё нормально.
Но стоит нам повернуть устройство на 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, по мере ввода появится подсказка, облегчающая выбор.
Откроем созданный файл и модифицируем его следующим образом.
Запускаем приложение и проверяем. Отлично, теперь видны все кнопки. Поздравляю, вы гений!
Когда вы создаёте альтернативную разметку, то не забывайте включать все компоненты, к которым будете обращаться программно, иначе получите ошибку. Допустим, вы забыли добавить шестую кнопку. В портретном режиме программа будет работать, а когда пользователь перевернёт экран, то активность будет инициализировать все компоненты для работы, а кнопки-то и нет. Крах приложения и минусы в отзывах.
Узнать ориентацию программно
Чтобы из кода узнать текущую ориентацию, можно создать следующую функцию:
Вызовите данную функцию из нужного места, например, при щелчке кнопки и узнайте текущую ориентацию. В примере использовались две распространённые системные константы для ориентации. Есть ещё константа ORIENTATION_SQUARE (квадратный экран). Но я таких телефонов не встречал.
Можно также вычислить ширину и высоту экрана, если высота больше ширины, то устройство в портретной ориентации, иначе — в альбомной:
Сейчас этот код считается устаревшим и для вычисления размера экрана используются другие методы (описано в примере Экран).
Кручу-верчу, запутать хочу!
Хорошо, мы можем определить текущую ориентацию, но в какую сторону повернули устройство? Ведь его можно повернуть влево, вправо или вообще вверх тормашками. Напишем другую функцию:
Установить ориентацию программно и через манифест
Если вы большой оригинал и хотите запустить приложение в стиле «вид сбоку», то можете сделать это программно. Разместите код в методе 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 зашёл на страницу документации и пришёл в ужас. Там появились новые значения.
Запрет на создание новой активности
На примере программной установки ориентации можно увидеть интересный эффект, о котором нужно помнить. Предположим у нас есть кнопка, позволяющая менять ориентацию. Заодно будем менять текст на кнопке, чтобы операция соответствовала надписи.
Теперь посмотрите, что у нас получилось. Запустите проект и нажмите на кнопку. Ориентация экрана поменялась, однако текст на кнопке остался прежним, хотя по нашей задумке он должен измениться.
Нажмём на кнопку ещё раз. Надпись изменится, но ориентация не сменится. И только повторный щелчок повернёт экран в обратную сторону.
По умолчанию, при смене ориентации Android уничтожает и пересоздаёт активность из кода, что подразумевает повторный вызов метода onCreate(). Поэтому при повороте активность устанавливала текст, определенный в onCreate(). В большинстве случаев это не мешает программе. Но если приложение воспроизводит видео, то при смене ориентации вызов onCreate() может привести к повторному началу воспроизведения (если так написан пример).
Чтобы активность не пересоздавалась, добавьте в манифест строчку для нужной активности:
При изменении ориентации система вызовет метод onConfigurationChanged(Configuration) и мы можем отловить поворот экрана:
В документации говорится, что данный способ следует избегать.
Исчезающий текст
Как уже говорилось, при смене ориентации активность пересоздаётся. При этом можно наблюдать интересный эффект с пропадающим текстом. Чтобы увидеть эффект, создадим два текстовых поля. Одному из них присвоим идентификатор, а другое поле оставим без него.
Запустите приложение, введите любой текст в обоих полях и смените ориентацию. Вы увидите, что у поля с идентификатором текст при повороте сохранится, а у поля без идентификатора текст исчезнет. Учитывайте данное обстоятельство.
К вышесказанному могу добавить, что при смене ориентации у поля с идентификатором вызывается метод onTextChanged():
Проверка на существование
Если вы используете две разные разметки, то возможна ситуация, когда в альбомной ориентации используется кнопка, которой нет в портретной ориентации. Это можете привести к ошибке в коде, поэтому нужно проверить существование кнопки:
На практике такое встречается редко, но помните на всякий случай.
Запоминаем значения переменных
С поворотом экрана возникает одна очень неприятная проблема. Вдумайтесь в значение слов, что при повороте экрана активность создаётся заново. Чтобы было понятно, нужно вернуться к проекту, в котором мы считали ворон. Если вы его удалили, то придётся пройти урок заново и восстановить его.
Щёлкните несколько раз по кнопке. Допустим на экране красуется надпись «Я насчитал 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), а потом мы его создали своим кодом. Для этого и нужна проверка. Обратите внимание, что здесь мы не прибавляем единицу к счётчику, как у кнопки. Если скопировать код у кнопки, то получится, что счётчик будет увеличиваться самостоятельно при поворотах без нажатия на кнопку. Прикольно, конечно, но может ввести в заблуждение пользователя. Хотя, если вы пишите приложение «Я твой дом труба шатал», то такой способ может пригодиться для подсчёта, сколько раз вы вертели телефон, чтобы разрушить чей-то дом.
Обращаю ваше внимание, что данный способ используется для сохранения промежуточных результатов во время действия программы. В следующих уроках вы узнаете, как можно сохранять результат между запусками приложения.
Ориентация у фрагментов
Позже вы узнаете о существовании фрагментов. Может возникнуть такая ситуация, когда вы захотите выводить конкретный фрагмент в нужной ориентации. У фрагментов есть собственный жизненный цикл, и вы можете реализовать свой код в методах фрагмента:
Я с таким случаем не встречался, но оставлю как памятку.
Жизненный цикл при повороте
При повороте активность проходит через цепочку различных состояний. Порядок следующий.
Источник
Screen Orientations in Android with Examples
Screen Orientation, also known as screen rotation, is the attribute of activity element in android. When screen orientation change from one state to other, it is also known as configuration change.
States of Screen orientation
There are various possible screen orientation states for any android application, such as:
Attention reader! Don’t stop learning now. Get hold of all the important Java Foundation and Collections concepts with the Fundamentals of Java and Java Collections Course at a student-friendly price and become industry ready. To complete your preparation from learning a language to DS Algo and many more, please refer Complete Interview Preparation Course.
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- ActivityInfo.SCREEN_ORIENTATION_USER
- ActivityInfo.SCREEN_ORIENTATION_SENSOR
- ActivityInfo.SCREEN_ORIENTATION_BEHIND
- ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
- ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
- ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
The initial orientation of the Screen has to be defined in the AndroidManifest.xml file.
AndroidManifest.xml
How to change Screen orientation?
Here is an example of an Android application that changes screen orientation for Landscape and Portrait mode.
We will create two activities of different screen orientation.
- The first activity will be as “portrait” orientation and
- Second activity as “landscape” orientation state.
Источник
Handling Orientation Changes on Android
Introduction
Handling orientation changes on Android is one of the most frustrating things to deal with as an Android engineer. I hope to make that challenge just a little bit easier and to help you better understand exactly what’s happening when you rotate your Android device. I’m going to cover what not to do when handling orientation changes, and what you should do in some common scenarios including dealing with Fragments, AsyncTasks and ListViews.
Background
When you rotate your device and the screen changes orientation, Android usually destroys your application’s existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration. When it destroys your Activities and Fragments it will end up creating new instances of them which will wipe out all of your member variables. To work around this, Android gives you the opportunity to save your app’s state before destroying your Activities and Fragments, and the opportunity to restore your state when recreating them. Proper handling of orientation changes centers around saving this state and also avoiding memory leaks.
While it may seem a bit tedious to implement, handling orientation changes properly provides you with several benefits: you will be able to easily use alternate layouts in portrait and landscape orientations, and you will be able to handle many exceptional states such as low memory situations and interruptions from incoming phone calls without any extra code.
What Not To Do
One of the most common “solutions” to dealing with orientation changes is to not deal with them. You can do this by setting the android:configChanges flag on your Activity in AndroidManifest.xml as shown below:
This flag signals to the Android platform that you are going to manually handle orientation, screenSize and keyboard appearance/disappearance changes for this Activity. So instead of destroying and recreating your Activity, Android will just rotate the screen and call one of the lifecycle methods: onConfigurationChanged. If you have a Fragment attached to this Activity, it will also receive a call to its onConfigurationChanged method. This means that the same instances of your Activities and Fragments will be in use and your member variables will remain untouched. If you do want something to be different when the orientation changes, such as using a new layout, you would have to implement onConfigurationChanged and manually discard the old layout/View, inflate the new layout and display it — which is a lot more work and can make your code difficult to work with in the future.
Calling setRetainInstance(true) on a Fragment is similar to setting the android:configChanges flag on an Activity. It signals to Android that you want to continue using the same instance of the current Fragment, so all of your member variables will remain untouched. If you rotate your device when you have an Activity that is NOT using the configChanges flag and a Fragment that IS being retained, the following lifecycle methods will be called on the Fragment:
Notice that Android does not call onCreate and onDestroy because we retained the Fragment; nor does it call the constructor, because the same Fragment instance will be used after the orientation change. Android will call all of the other callbacks because the Fragment’s parent Activity IS being destroyed and recreated, so the Fragment does have to go through the process of being detached then reattached. Alternatively, if your Activity HAS the configChanges flag set and your Fragment IS retained, all that will happen is the screen will rotate and both your Activity and Fragment will receive calls to their respective onConfigurationChanged methods. Calling setRetainInstance(true) on a Fragment is generally a bad idea for the same reasons as using the configChanges flag on an Activity is a bad idea: you won’t be able to reload resources that may need to be refreshed. There is one situation in which retaining a Fragment is a good idea and we’ll explore that in more detail in the Handling AsyncTasks section.
Finally, another “solution” to the orientation change problem is to set the android:screenOrientation flag on your activity:
This altogether prevents orientation changes from happening while the user is in the Activity with the flag set. So if you rotate your device the screen won’t rotate with it. While there are certainly situations in which this is the desired behavior, you should, if possible, allow your app to be used in both landscape and portrait orientations because it greatly increases your app’s usability.
Saving State
The most important aspect of handling orientation changes is saving state. In most cases this involves implementing the onSaveInstanceState method (this could be in your Activity, Fragment or both) and placing the values you need to save in the Bundle argument that gets passed to the method.
Most of the time you don’t have to worry about saving the state of your Views because Android automatically calls the View.onSaveInstanceState method on each of the views in your view hierarchy as long as you call through to the super method in onSaveInstanceState. This also means that if you use any custom Views, they should contain an implementation of onSaveInstanceState. Do note, that in order for a View’s state to be saved it MUST have an android:id attribute because this is essentially used as the key for that particular View’s state.
One thing to watch out for is a ListView. The super method of onSaveInstanceState will take care of saving certain things such as scroll position, but it is up to you to save the contents of the adapter in your onSaveInstanceState method. If you are using an ArrayList of a model object to populate your ListView, then one option is to ensure your model object implements Serializable and in onSaveInstanceState, place it in the Bundle through putSerializable. One thing to keep in mind: even though it is good practice to “code to interface” and declare your ArrayList as a List (List items = new ArrayList<>()), in this particular case, you will have to declare it as an ArrayList (ArrayList items = new ArrayList<>()) because the compiler needs to know that your List implements Serializable (which List itself does not, but ArrayList does) in order to pass it to Bundle.putSerializable(String, Serializable).
onSaveInstanceState gets called before onStop but it is not guaranteed to be called before or after onPause. Android will also only call it when your application needs to save temporary state which includes when orientation changes occur and when your Activity is killed for its memory resources. It will not be called in certain situations such as finishing an Activity normally or putting an Activity into the background.
Restoring State
The way you restore state can vary between different Activities and between Activities and Fragments. In a typical Activity, you would check the savedInstanceState argument that gets passed to your onCreate method. If savedInstanceState != null, you would retrieve your state from that Bundle.
Another option in an Activity is to implement onRestoreInstanceState which also gets passed a savedInstanceState Bundle. The only reason to take this approach is if you want to wait for all of your onCreate initialization to be done before restoring state or if you want to allow subclasses to specifically handle restoring state. If you choose to implement onRestoreInstanceState, you must call through to the super method if you want your View states restored. Again, if you use any custom Views, they should implement their own onRestoreInstanceState. An Activity’s onRestoreInstanceState is called after onStart and before onResume.
In a Fragment you can restore state in several different callbacks (these are listed in the order they get called): onCreate, onCreateView, onActivityCreated, or onViewStateRestored. Where you choose to check your savedInstanceState Bundle depends entirely on what you need to have happened before restoring your state. If you need to make sure your parent Activity’s view hierarchy has been created, you should restore state in onActivityCreated. If you need to make sure your own Fragment’s view hierarchy has been created and had its state restored, you should restore state in onViewStateRestored.
ListViews again pose an extra requirement for restoring state. If you want your scroll position to be saved and restored properly, you must retrieve your saved ArrayList of model objects, create an adapter and assign it to the ListView before it gets its state restored (i.e. you have to do it in onCreateView or onActivityCreated).
See below for examples of how and where you can restore state in a Fragment:
In the example above, we restored state in two different places. That was just for the purposes of the example. In reality, it is much better to only restore state in one place.
Adding Fragments
One of the common pitfalls of handling orientation changes with Fragments is accidentally re-instantiating and re-adding them every time the Activity is recreated. If you allow Android to handle orientation changes, it will take care of re-instantiating your fragments, re-adding them to the activity, and recreating the fragment backstack, when it recreates the parent Activity. Therefore, you should only instantiate or add a fragment to an Activity if the savedInstanceState bundle passed to your Activity callbacks is null.
If you need to obtain a reference to the Fragment you can do so through the FragmentManager using a tag. This means that when you add the Fragment to the Activity you have to provide a tag argument. When you need a reference to your Fragment you can call FragmentManager.findFragmentByTag(String) and pass in the tag you used when adding the Fragment. The FragmentManager will take care of everything related to re-instantiating and re-adding the Fragment and it will make sure to return the right instance of the Fragment from findFragmentByTag. See below for an example of how to implement this:
Handling AsyncTasks
Starting an AsyncTask can pose problems if your Activities and Fragments are randomly getting recreated, so you will have to pay special attention to how you handle them. There are a few dangerous things that could happen with improperly handled AsyncTasks: memory leaks and crashes.
Memory leaks can occur if your AsyncTask holds on to a reference to an Activity or a Fragment. When Android destroys your Activity or Fragment because of an orientation change (or any other configuration change), it will not destroy any AsyncTasks that you started. So if an AsyncTask has a reference to a now-destroyed Activity or Fragment, the garbage collector won’t be able to collect that Activity or Fragment even though it should never be used again. You are particularly susceptible to memory leaks if you have an AsyncTask declared as a non-static inner class of an Activity or Fragment because that AsyncTask will implicitly hold a reference to its parent class (in this case the Activity or Fragment) even though it appears as though it doesn’t have this reference.
The other problem you can run into is an actual crash. If, for example, you are displaying a ProgressDialog in the onPreExecute method of your AsyncTask and dismissing it (calling ProgressDialog.dismiss()) in the onPostExecute or onCancelled method a few things will happen.
If you rotate the device before the task is finished you’ll immediately see a WindowLeaked exception printed out to the logs. This indicates that Android couldn’t release the resources for that window (your ProgressDialog) because you are still referencing it in your AsyncTask. This exception won’t actually cause a crash. However, when the task eventually does complete, and you call ProgressDialog.dismiss(), you will get an IllegalArgumentException: View…not attached to window manager. This is indicating that you are trying to dismiss a dialog that’s not actually attached to anything and it will cause a crash.
One way to avoid this is to cancel AsyncTasks in your Activity or Fragment’s onDestroy method. You can also save the state of your task (if it’s running or not) in the onSaveInstanceState method. When your Activity or Fragment is recreated, you can use that to determine if you need to restart the task.
Using a Retained Fragment
Now, it is not always an option (or at least a good option) to cancel and restart your AsyncTasks whenever an orientation change occurs. For example if you have a task that is downloading a file and it’s almost done when suddenly the user rotates the device, it would be an extremely unpleasant user experience for the task to be cancelled then restarted after that. This brings us back to something I mentioned earlier: calling setRetainInstance(true) on a Fragment. As I had described, setRetainInstance(true) tells Android to not destroy a Fragment when a configuration change happens. If you use a retained Fragment to host your AsyncTask, you can avoid ever having to restart your tasks. There are still a few things you should keep in mind though. Your retained Fragment should have no UI. Instead, you can declare an interface that your Activity will implement and that your Fragment will use to tell the Activity to update the UI (or anything else). In onAttach, you should cast your Activity to that interface and save it in a listener member variable. In onDetach you must set that listener variable to null, so you don’t leak an Activity reference. In the AsyncTask callbacks (onPreExecute, onProgressUpdate, onPostExecute and onCancelled), you should make sure the listener isn’t null then trigger the appropriate callback on the listener and let it handle the rest. Here’s an example of how to implement this:
So Where Does That Leave Us?
Hopefully, by this point, you are familiar with four main ideas to help you with your orientation changes: saving state, restoring state, only adding/instantiating Fragments when you’re not recreating an Activity, and using retained Fragments to host AsyncTasks. Even though it may seem like a lot of extra work at first, it will also make the user’s experience with your app much better.
About the Author
Noah Tajwar is a High School Co-op on the Android team, entering Grade 12 at Riverside Secondary School. In his spare time, he enjoys writing Android/Java applications, playing soccer and playing piano.
Источник