- Ориентация
- Вступление
- Узнать ориентацию программно
- Кручу-верчу, запутать хочу!
- Установить ориентацию программно и через манифест
- Запрет на создание новой активности
- Исчезающий текст
- Проверка на существование
- Запоминаем значения переменных
- Ориентация у фрагментов
- Жизненный цикл при повороте
- Полный список
- Размер превью
- Поворот превью
- Прочее
Ориентация
Вступление
Когда создавались первые портативные устройства — КПК и смартфоны, то за основу бралась настольная операционная система и допиливалась под мобильное устройство. Лишние функции удалялись, а некоторые функции добавлялись. Но при этом как-то совсем упустили из виду, что в отличие от громоздких мониторов и экранов ноутбуков, карманные устройства можно вращать в руках. Первые устройства не умели менять ориентацию экрана. Некоторые программисты самостоятельно стали создавать программы, которые умели переключаться в разные режимы. Затем эту возможность стали включать в настройки аппарата. Позже аппараты научились самостоятельно определять ориентацию экрана.
Всего существует два режима — портретный и альбомный. На большинстве телефонов используется по умолчанию портретный режим (как на паспорте). Альбомный режим знаком нам по обычным мониторам.
Рассмотрим следующий случай. Предположим, у нас в приложении имеется одно текстовое поле и шесть кнопок. Вроде всё нормально.
Но стоит нам повернуть устройство на 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), а потом мы его создали своим кодом. Для этого и нужна проверка. Обратите внимание, что здесь мы не прибавляем единицу к счётчику, как у кнопки. Если скопировать код у кнопки, то получится, что счётчик будет увеличиваться самостоятельно при поворотах без нажатия на кнопку. Прикольно, конечно, но может ввести в заблуждение пользователя. Хотя, если вы пишите приложение «Я твой дом труба шатал», то такой способ может пригодиться для подсчёта, сколько раз вы вертели телефон, чтобы разрушить чей-то дом.
Обращаю ваше внимание, что данный способ используется для сохранения промежуточных результатов во время действия программы. В следующих уроках вы узнаете, как можно сохранять результат между запусками приложения.
Ориентация у фрагментов
Позже вы узнаете о существовании фрагментов. Может возникнуть такая ситуация, когда вы захотите выводить конкретный фрагмент в нужной ориентации. У фрагментов есть собственный жизненный цикл, и вы можете реализовать свой код в методах фрагмента:
Я с таким случаем не встречался, но оставлю как памятку.
Жизненный цикл при повороте
При повороте активность проходит через цепочку различных состояний. Порядок следующий.
Источник
Полный список
— используем объект Camera для получения изображения с камеры
— подгоняем изображение под размеры экрана
— учитываем поворот устройства
Разберемся, какие основные объекты нам понадобятся для вывода изображения с камеры на экран. Их три: Camera, SurfaceView, SurfaceHolder.
Camera используется, чтобы получить изображение с камеры. А чтобы это изображение в приложении отобразить, будем использовать SurfaceView.
Нормального перевода слова Surface я не смог подобрать. «Поверхность» — как-то слишком абстрактно. Поэтому так и буду называть – surface. Это будет означать некий компонент, который отображает изображение с камеры.
Работа с surface ведется не напрямую, а через посредника – SurfaceHolder (далее holder). Именно с этим объектом умеет работать Camera. Также, holder будет сообщать нам о том, что surface готов к работе, изменен или более недоступен.
А если подытожить, то: Camera берет holder и с его помощью выводит изображение на surface.
Напишем приложение, в котором реализуем вывод изображения с камеры на экран.
Project name: P1321_CameraScreen
Build Target: Android 2.3.3
Application name: CameraScreen
Package name: ru.startandroid.develop.p1321camerascreen
Create Activity: MainActivity
SurfaceView по центру экрана.
В манифест добавьте права на камеру:
В onCreate настраиваем Activity так, чтобы оно было без заголовка и в полный экран. Затем мы определяем surface, получаем его holder и устанавливаем его тип = SURFACE_TYPE_PUSH_BUFFERS (настройка типа нужна только в Android версии ниже 3.0).
Далее для holder создаем callback объект HolderCallback (о нем чуть дальше), через который holder будет сообщать нам о состояниях surface.
В onResume получаем доступ к камере, используя метод open. На вход передаем id камеры, если их несколько (задняя и передняя). Этот метод доступен с API level 9. В конце этого урока есть инфа о том, как получить id камеры.
Также существует метод open без требования id на вход. Он даст доступ к задней камере. Он доступен и в более ранних версиях.
После этого вызываем метод setPreviewSize, в котором настраиваем размер surface. Его подробно обсудим ниже.
В onPause освобождаем камеру методом release, чтобы другие приложения могли ее использовать.
Класс HolderCallback, реализует интерфейс SurfaceHolder.Callback. Напомню, что через него holder сообщает нам о состоянии surface.
В нем три метода:
surfaceCreated – surface создан. Мы можем дать камере объект holder с помощью метода setPreviewDisplay и начать транслировать изображение методом startPreview.
surfaceChanged – был изменен формат или размер surface. В этом случае мы останавливаем просмотр (stopPreview), настраиваем камеру с учетом поворота устройства (setCameraDisplayOrientation, подробности ниже), и снова запускаем просмотр.
surfaceDestroyed – surface более недоступен. Не используем этот метод.
С этими методами, кстати, есть одна странность. В хелпе к методу surfaceChanged написано, что он обязательно будет вызван не только при изменении, но и при создании surface, т.е. сразу после surfaceCreated. Но при этом в хелпе к камере методы запуска просмотра (setPreviewDisplay, startPreview) вызываются и в surfaceCreated и в surfaceChanged. Т.е. при создании surface мы зачем-то два раза стартуем просмотр. Мне непонятно, зачем нужно это дублирование.
Если очистить метод surfaceCreated, то все продолжает работать. Но в уроке я, пожалуй не рискну так делать. Вдруг я чего не понимаю и в этом есть какой-то смысл. Если кто знает – пишите на форуме.
Размер превью
Метод setPreviewSize. Немного нетривиальный, особенно если вы никогда не работали с объектами Matrix и RectF.
В нем мы определяем размеры surface с учетом экрана и изображения с камеры, чтобы картинка отображалась с верным соотношением сторон и на весь экран.
Дальнейшие выкладки можно пропустить, если неохота мозг ломать и вникать в механизм. Хотя я постарался сделать эти выкладки понятными, интересными и даже картинки нарисовал. Если вы все поймете, будет отлично!) Когда-нибудь эти знания пригодятся.
Итак, у нас есть картинка, которая приходит с камеры – назовем ее превью. И у нас есть экран, на котором нам надо это превью отобразить.
Рассмотрим конкретный пример, чтобы было нагляднее. Планшет Galaxy Tab, задняя камера, нормальное горизонтальное положение.
Есть экран. Размер: 1280×752. Соотношение сторон: 1280/752 = 1,70
Есть превью. Размер: 640×480. Соотношение сторон: 640/480 = 1,33.
Допустим, что мы камеру навели на какой-то круг.
Мы хотим получить картинку на весь экран. Какие есть варианты? Их три.
1) Растянуть превью на экран. Плохой вариант, т.к. для этого соотношение сторон должно быть одинаковым, а у нас оно разное. Но все же попытаемся, чтобы увидеть результат.
Для этого нам ширину превью надо умножить на 1280/640 = 2. А высоту на 752/480 = 1,57. В итоге имеем:
видно, что картинка деформировалась и стала растянутой по горизонтали. Нам это не подходит.
2) Втиснуть превью в экран с сохранением пропорций. Для этого мы будем менять размеры превью (сохраняя соотношение сторон), пока оно изнутри не упрется в границы экрана по высоте или ширине. В нашем случае оно упрется по высоте.
Для этого нам надо умножить ширину и высоту превью на меньшее из чисел: 1280/640 = 2 и 752/480 = 1,57, т.е. на 1.57.
Смотрим, чего получилось
стало гораздо лучше. Теперь картинка превью не искажена. Единственное, что немного смущает – пустые области по бокам экрана. Но ничего не мешает закрасить их черным и пусть все думают, что так и задумано. Зато мы будем видеть полную и неискаженную картинку. Так, например, обычно делается в видео-плеерах.
3) Втиснуть экран в превью. Т.е. сделать второй вариант наоборот. Менять размер экрана (сохраняя соотношение сторон) до тех пор пока он изнутри не упрется в границы превью по высоте или ширине.
Для этого нам надо было бы ширину и высоту экрана разделить на большее из чисел: 1280/640 = 2 и 752/480 = 1,57, т.е. на 2.
Но т.к. менять размеры экрана мы не можем физически, то мы будем менять размеры превью чтобы достигнуть описанного результата.
Для этого нам надо умножить ширину и высоту превью на большее из чисел: 1280/640 = 2 и 752/480 = 1,57, т.е. на 2.
Картинка не искажена и занимает полный экран. Но есть нюанс. Мы не видим всего изображения. Оно выходит за границы экрана сверху и снизу.
На всякий случай укажу, что это лишь один пример. В других может быть по другому. Например, во втором варианте пустые области могут быть не по бокам, а сверху и снизу. А на мелких девайсах размер превью будет больше размера экрана. Но общий смысл и алгоритм от этого не меняются.
Мы рассмотрели три варианта, и увидели, что первый совсем плох, а второй и третий вполне годятся для реализации.
От картинок возвращаемся к коду. Метод setPreviewSize(boolean fullScreen) реализует второй (если fullScreen == false) и третий (если fullScreen == true) варианты.
Красота метода в том, что все преобразования за нас делает Matrix (матрица). И нам самим не надо будет ничего умножать или делить.
Сначала мы получаем размеры экрана и превью. Для экрана сразу выясняем что больше: ширина или высота. Т.е. если ширина больше, то устройство находится в горизонтальной ориентации, если высота больше – в вертикальной.
Для преобразований матрица потребует от нас RectF объекты. Если никогда еще не работали с ними, то это просто объект, который содержит координаты прямоугольника: left, top, right, bottom.
В качестве left и top мы всегда будем использовать 0, а в right и bottom помещать ширину и высоту экрана или превью. Тем самым мы будем получать прямоугольники точно совпадающие по размерам с экраном и превью.
rectDisplay – экран, rectPreview – превью. У превью обычно ширина всегда больше высоты. Если устройство в горизонтальной ориентации, то мы создаем rectPreview соответственно его размерам. А если устройство вертикально, то изображение с камеры будет также повернуто вертикально, следовательно ширина и высота поменяются местами.
Теперь самое интересное – подготовка преобразования. Используем метод setRectToRect. Он берет на вход два RectF. И вычисляет, какие преобразования надо сделать, чтобы первый втиснуть во второй. Про третий параметр метода я сейчас рассказывать не буду, мы всегда используем START. (Если все же интересно, задавайте вопрос на форуме, там обсудим)
Т.е. этот метод пока не меняет объекты. Это только настройка матрицы. Теперь матрица знает, какие расчеты ей надо будет произвести с координатами объекта, который мы ей позже предоставим.
Смотрим код. Если (!fullScreen), то это второй вариант, т.е. превью будет втиснут в экран. Для этого мы просто сообщаем матрице, что нам объект с размерами превью надо будет втиснуть в объект с размерами экрана. Т.е. если обратиться ко второму варианту, то матрица поняла, что ей надо будет умножить стороны объекта на 1.57. И когда мы ей потом предоставим объект с размерами превью – она это сделает и мы получим необходимые нам размеры.
Если же fullScreen (третий вариант), то алгоритм чуть сложнее. Мы сообщаем матрице, что нам надо объект с размерами экрана втиснуть в объект с размерами превью. Смотрим третий вариант. Поначалу мы выяснили, что экран надо будет разделить на два. Но потом мы поняли, что мы не можем менять размеры экрана и нам надо делать наоборот – не экран делить на два, а превью умножить на два. Это мы можем объяснить и матрице вызвав метод invert. Матрица возьмет алгоритм из переданной ей матрицы (т.е. из самой себя), и сделает все наоборот. Т.е. вместо того, чтобы разделить на два – умножит.
Очень надеюсь, что изложил понятно. Если же не понятно – перечитайте раз 5 и сверяйтесь с описанием вариантов и картинками в примере выше. Если все равно не понятно, вернитесь к этому где-нить через недельку. Мозг к тому времени уже усвоит и как-то уложит эту инфу. И повторное прочтение может пройти гораздо легче. По крайне мере у меня обычно это так) Я могу что-то прочесть – ничего не понять. Но через неделю/месяц/полгода снова заглянуть туда и удивиться: «а что здесь собственно непонятного то было?»
Итак, мы подготовили матрицу к преобразованию, осталось только вручить ей объект, который она этим преобразованием подвергнет. Для этого используем метод mapRect и передаем ему объект с размерами превью. Как и в примере выше, все преобразования мы будем проводить с ним.
После проведения преобразований мы берем получившиеся координаты и настраиваем по ним surface, которое отображает превью.
Поворот превью
Если мозг еще не разрушен, сейчас мы это исправим! Разбираем метод setCameraDisplayOrientation, который будет превью вращать.
Снова рассмотрим пример, когда используется планшет в горизонтальном состоянии, камера – задняя. Допустим, мы через камеру смотрим на такой объект:
Видим его на экране, все ок.
Важное замечание. Через стандартное приложение камеры нижеописанный пример не воспроизведется, т.к. стандартное приложение обрабатывает поворот устройства. А я хочу продемонстрировать, что было бы если бы не обрабатывало.
Я поворачиваю планшет по часовой (направо) на 90 градусов. При этом, разумеется поворачивается и камера. На экране я вижу теперь такую картинку:
Кстати, такую же картинку увидите и вы, если наклоните голову вправо на 90 градусов)
Т.е. система хоть и среагировала на поворот и повернула основное изображение, но камера возвращает нам именно такой повернутый вид. Его мы и видим.
Что надо сделать, чтобы это исправить? Повернуть картинку на 90 по часовой. Т.е. сделать тот же поворот, что сделала камера.
Получилась Аксиома Поворота Камеры: насколько и в какую сторону повернута камера, на столько же и в ту же сторону нам надо поворачивать и превью, чтобы получать правильную картинку.
Для этого будем использовать метод setDisplayOrientation. Он принимает на вход угол, на который камера будет поворачивать по часовой превью, перед тем как отдать его нам. Т.е. от нас ждут угол поворота превью по часовой. Его мы можем узнать, выяснив насколько повернута по часовой камера (см. Аксиому Поворота Камеры).
Для этого используем такую конструкцию — getWindowManager().getDefaultDisplay().getRotation(). Она возвращает нам градусы, на которые система поворачивает изображение по часовой, чтобы оно нормально отображалось при поворотах устройства.
Т.е. когда вы наклоняете устройство на 90 против часовой, система должна поворачивать изображение на 90 по часовой, чтобы компенсировать поворот. (сейчас речь не о камере, а просто об изображении которое показывает телефон, например — Home)
Аксиома Поворота Устройства: насколько и в какую сторону повернуто устройство, на столько же, но в другую сторону система поворачивает изображение, чтобы получать его в правильной ориентации.
Отсюда следует, что getWindowManager().getDefaultDisplay().getRotation() сообщает нам насколько устройство повернуто против часовой.
Кстати, от getRotation мы получаем константы, а далее в switch преобразуем их в градусы.
Итак, переменная degrees содержит кол-во градусов, на которые повернуто устройство против часовой.
До сих пор мозг цел? Тогда держите такой факт: камера в устройстве может быть повернута относительно этого устройства.
Так обычно делается на смартфонах. Т.е. там камера повернута на 90 градусов. И ее нормальная ориентация совпадает с горизонтальной ориентацией устройства. Чтобы и в превью и на экране ширина получалась больше высоты.
И вот этот поворот нам тоже надо учитывать при повороте превью. Получить данные о камере можно методом getCameraInfo. На вход требует id камеры и объект CameraInfo, в который будет помещена инфа о камере.
Нас интересует поле CameraInfo.orientation, которое возвращает на сколько по часовой надо повернуть превью, чтобы получить нормальное изображение. Т.е. исходя из Аксиомы Поворота Камеры – на столько же повернута по часовой и сама камера.
Ну и добиваем мозг следующим фактом. Камера может быть задней и передней (фронтальной). И для них по разному надо считать повороты)
Поле CameraInfo.facing содержит инфу о том, какая камера – задняя или передняя.
Попробуем посчитать. Напомню, что метод setDisplayOrientation ждет от нас градус поворота превью по часовой. Т.е. мы можем просто посчитать поворот камеры по часовой (Аксиома Поворота Камеры) и получим нужное значение.
Чтобы узнать итоговый поворот камеры по часовой в пространстве – надо сложить поворот устройства по часовой и CameraInfo.orientation. Это для задней камеры. А для передней – надо CameraInfo.orientation вычесть, потому что она смотрит в нашу сторону. И все, что для нас по часовой, для нее — против.
Все, считаем. У нас есть degrees — кол-во градусов, на которые повернуто устройство против часовой. Чтобы конвертнуть это кол-во в градусы по часовой, надо просто вычесть их из 360.
Т.е. (360 – degrees) – это поворот устройства по часовой. Я специально выделил в коде это выражение скобками для наглядности. Далее мы к этому значению прибавляем или вычитаем (задняя или передняя камера) встроенный поворот камеры. В случае с передней камерой на всякий случай прибавляем 360 градусов, чтобы не получилось отрицательное число. И в конце определяем итоговое кол-во градусов в пределах от 0 до 360, вычисляя остаток от деления на 360.
И торжественно передаем камере это значение.
На редкость мозгодробительная штука – работа с камерой, правда? В итоге, когда вы все это запустите, вы должны видеть адекватное изображение с камеры.
В начале кода есть две константы: CAMERA_ID и FULL_SCREEN.
Если у вас две камеры, вы можете передать в CAMERA_ID не 0, а 1, и получите картинку с передней камеры.
Ну а меняя FULL_SCREEN изменяйте вид превью.
Прочее
Как определить, есть ли камера в устройстве? Об этом сообщит конструкция context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
Получить id камеры, можно используя метод getNumberOfCameras (доступен с API Level 9). Он вернет нам некое кол-во камер N, которые доступны на устройстве. Соответственно их ID будут 0, 1, …, N-1. По этому id уже получаете CameraInfo и определяете, что это за камера.
Метод open может вернуть Exception при запуске если по каким-то причинам не удалось получить доступ к камере. Имеет смысл это обрабатывать и выдавать сообщение пользователю, а не вылетать с ошибкой.
Обработка поворота может криво работать на некоторых девайсах. Например, я тестил на HTC Desire (4.2.2) и Samsung Galaxy Tab (4.2.2) – было все ок. А на Samsung Galaxy Ace (2.3.6) сложилось ощущение, что камера просто игнорит градус поворота, который я ей сообщаю.
На следующем уроке:
— делаем снимок
— пишем видео
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник