ISBN: 978-5-4483-8090-7

Текст
                    Тимур Машнин
Разработка Android-приложений с Augmented Reality

Разработка Android-приложений с Augmented Reality
Тимур Машнин
© Тимур Машнин, 2017
ISBN 978-5-4483-8090-7
Создано в интеллектуальной издательской системе Ridero

Введение
Пригласить автора в проект [битая ссылка] admin@tmsoftstudio.com


Дополненная реальность (Augmented Reality) не является какой-то новой технологией, но ее применение было замечено широкой публикой с появлением игры Pokemon GO, которая показала, что технология AR имеет большой потенциал. Помимо игры Pokemon GO, такие технологии как Google Tango и Microsoft HoloLens также находятся на переднем крае AR. «Дополненная» означает сделать нечто более сложное, добавляя что-то к чему-то. «Реальность» это состояние вещей, как они на самом деле существуют. Например, сцена изображения камеры дополняется 3D Android логотипом в верхней части. Имейте в виду, что дополненная реальность не ограничивается только изображением, также возможны звук и другие сенсорные усовершенствования. Дополненная реальность (AR) накладывает куски виртуального мира на реальный мир (в отличие от виртуальной реальности (VR), которая заменяет реальный мир виртуальным миром). Для мобильных устройств, это просто означает улучшение того, что вы можете видеть через камеру устройства. Например, вы можете навести вашу камеру на постер фильма и посмотреть его трейлер, или вы можете навести камеру на звезду в небе и узнать ее имя. Так что, в основном AR сводится к следующим трем основным вопросам: ГДЕ показать ЧТО и КАК. ГДЕ может включать в себя такие области, как согласование 2-D изображений и их отслеживание, согласование 3-D объектов и их отслеживание, обнаружения лиц и их отслеживание, SLAM (Simultaneous Localization and Mapping), отслеживание местоположения (с помощью GPS, акселерометра, компаса, гироскопа). Иногда, ГДЕ это ничего больше, как некоторые заранее определенные точки местоположения Points of Interest (POIs). С другой стороны, ЧТО и КАК может использовать рендеринг 3-D модели, анимацию и обнаружение жестов. В общем, ЧТО может быть любой частью цифровой информации (например, текст, изображение, видео), с которыми пользователь мог бы иметь возможность взаимодействовать (например, повернуть или переместить). Используя смартфон в качестве примера, AR технология работает с помощью приложения, которое выполняет поиск маркера, как правило, черно-белого штрих-кода или другого изображения. После того, как маркер найден, на маркер затем накладывается 3D-объект. С помощью камеры телефона, отслеживая относительное положение устройства и маркера, пользователь может ходить вокруг маркера и просматривать 3D-объект со всех точек зрения. Это занимает много ресурсов, так как телефон должен отслеживать свое положение, а также положение маркеров, чтобы 3D-объект выглядел правильно. Такие игры, как Pokemon GO, работают немного по-другому. Вместо использования физического маркера с привязкой к нему визуализации объекта, Pokemon GO просто отображает 3D-объект в видоискателе камеры. Используя этот метод, Pokemon GO не предоставляет возможность ходить вокруг покемонов, как традиционное использование технологии AR. На самом деле, в этой игре нет никакого отслеживания дистанции, вы можете свободно ходить вокруг, и покемон все равно останется на таком же расстоянии от вас, до тех пор, пока вы не пойдете в правильном направлении. Этот метод может в конечном итоге быть более общим способом реализации дополненной реальности в мобильном пространстве. Технология Google Tango обеспечивает более сложную реализацию дополненной реальности для мобильных устройств, так как Tango устройство имеет специальное оборудование для
этого. Tango устройство использует компьютерное зрение, чтобы отслеживать движение, имеет глубину восприятия и изучает пространство вокруг вас для самостоятельного исправления деталей. Tango устройство включает в себя стандартную камеру, камеру обнаружения движения рыбий глаз и датчик глубины. По способу привязки виртуальных объектов к реальному миру, AR системы можно разделить на два типа – основанные на сенсорах и основанные на компьютерном зрении. AR приложения, основанные на сенсорах, используют GPS, акселерометры, магнитометры и гироскопы для определения глобальной позиции пользователя в реальном мире и имеют ограничения по использованию вне помещения и скорости перемещения пользователя из-за запаздывания передачи GPS информации. AR приложения, основанные на компьютерном зрении, используют камеру устройства для компьютерной обработки изображения и регистрации виртуального объекта в реальном мире и имеют ограничения по мощности используемых устройств, так как компьютерная обработка изображения камеры потребляет значительные ресурсы. Существует несколько способов разработки приложений с дополненной реальностью, от нативной разработки в Android Studio до использования таких движков, как Unity. На сегодняшний день доступны несколько десятков SDK для разработки AR приложений, ниже перечислены некоторые из них: Vuforia – разработан компанией Qualcomm. Этот SDK компьютерного зрения обеспечивает разработку приложений с дополненной реальностью, основанной на отслеживании маркеров, для Android и ОС IOS с поддержкой Unity. Vuforia поддерживает несколько целей одновременно, Smart Terrain (реконструкция физического мира), а также локальные и облачные базы данных. FastCV Computer Vision SDK – разработан компанией Qualcomm. Обеспечивает распознавание жестов, обнаружение, слежение и распознавание человеческого лица, распознавание и отслеживание текста, дополненную реальность. Библиотека FastCV представляет собой оптимизированную для мобильных устройств библиотеку компьютерного зрения, включающую в себя наиболее часто используемые функции обработки компьютерного зрения для использования в широком спектре мобильных устройств. OpenCV (Open Source Computer Vision Library) – библиотека компьютерного зрения и машинного обучения с открытым исходным кодом. OpenCV обеспечивает общую инфраструктуру для приложений компьютерного зрения. ARToolKit – библиотека компьютерного зрения, обеспечивающая надежное отслеживание маркеров, включая отслеживание изображений Natural Feature Tracking, поддержку калибровки камеры, одновременное отслеживание и поддержку стерео камеры, мультиязычность, оптимизацию для мобильных устройств, полную поддержку Unity3D и OpenSceneGraph. OpenSpace3D – является «свободным программным обеспечением» для развития проектов виртуальной и дополненной реальности. Цель OpenSpace3D состоит в том, чтобы демократизировать 3D-приложения реального времени и предоставить инструмент для всех творческих умов, а не только разработчиков. OpenSpace3D поддерживает два метода дополненной реальности для создания AR-приложений. Обнаружение маркера, позволяющее отслеживать произвольное изображение с помощью камеры, и обнаружение Aruco реперного
маркера, что позволяет делать быстрые приложения с помощью нескольких маркеров, а также использовать их в качестве материального интерфейса. BeyondAR – платформа предлагает ресурсы для разработки приложений с дополненной реальностью, основанной на георасположении на смартфонах и планшетах. С помощью нескольких строк кода можно создавать 2D-объекты, чтобы увидеть их через камеру. Beyond Reality Face Nxt – платформа позволяет создавать веб, мобильные и настольные приложения, отслеживающие человеческое лицо, с использованием Actionscript или HTML5/Javascript. VISION SDK – полностью настраиваемая, легкая в использовании библиотека представлений дополненной реальности, которая позволяет включение AR в любое приложение для Apple или Android устройств без необходимости быть экспертом в программировании. С VISION SDK любое мобильное приложение может быть улучшено благодаря этой технологии, которая интегрирует цифровую информацию в общий вид окружающего пространства естественным образом. ARLab – больше, чем просто SDK, ARLab также имеет 3D-движок, который может быть использован для создания AR-приложений. ARLab не является бесплатным, и предлагает несколько различных вариантов цен в зависимости от того, какие функции вы хотите включить в ваше приложение. ARLab обеспечивает виртуальные кнопки, отслеживание изображения и сопоставление изображения. Wikitude SDK – платная, включает в себя распознавание образов и отслеживание, рендеринг 3D моделей, видео с наложением, AR, основанное на местоположении, и многое другое, поддерживает Android, iOS, Smartphone, Tablet, Smart Glasses, Cordova/PhoneGap, Titanium, Xamarin. Intel RealSense SDK – требует наличия двух камер User Facing (SR300) and World Facing (R200), обеспечивает распознавание жестов, лиц, 3D сканирование стационарных объектов, удаление фона, отслеживание объектов, распознавание речи (требует наличия только микрофона), создание цифрового представления наблюдаемой среды и оценка положения камеры в реальном масштабе времени, улучшение фотографий и видео за счет использования 3D глубины. Vuforia Для использования Unity в разработке AR приложений, в качестве первого шага, необходимо скачать платформу Unity. После установки, откройте Unity и создайте новый проект, при этом убедитесь, что выбрана опция «3D». Закройте Unity. Загрузите Vuforia Unity Extension и дважды кликните на файле *.unitypackage. При открытии Unity, выберете созданный проект. Импортируйте Vuforia Unity Extension в проект.
Удалите из сцены Main Camera и перетащите в сцену камеру ARCamera в папке Assets/Vuforia/Prefabs окна Project. В окне Inspector откройте свойства ARCamera. Получите лицензионный ключ для приложения на сайте Vuforia и вставьте его в поле App Lisense Key.
Поместим камеру в начало координат Position 0, 0, 0, Rotation 90, 0, 0. На сайте Vuforia создайте базу данных маркеров, к которым будут прикрепляться 3Dобъекты. Для этого нужно нажать кнопку Add Database во вкладке Develop/Target Manager. Сделайте фотографию какого-либо предмета и с помощью кнопки Add Target загрузите изображение в базу маркеров. С помощью кнопки Download Database скачайте базу маркеров и дважды кликните на скачанном Unity пакете. Импортируйте маркеры в Unity проект.
Добавим 3D модель, которую будет прикреплять к маркеру. Для этого в Unity откроем окно Window/Asset Store и скачаем Unity пакет с 3D моделями. Импортируем скачанный пакет в Unity проект. Из папки Assets/Vuforia/Prefabs окна Project перетащим в сцену объект Image Target в позицию Position 0, -10, 0, Rotation 0, 0, 0. В свойствах Image Target в окне Inspector в разделе Image Target Behaviour в поле Database выберем импортированную базу маркеров и увидим наше изображение в сцене. Перетащим в сцену 3D модель, импортированную из Asset Store, в позицию Position 0, 0, 0, Rotation 0, 0, 0. В поле Scale свойств модели подберем масштаб модели относительно нашего
маркера. В окне Hierarchy перетащим модель в узел ImageTarget и таким образом сделаем модель дочерним объектом объекта Image Target. Теперь, когда маркер Image Target будет обнаруживаться камерой мобильного устройства, все дочерние объекты маркера Image Target также будут появляться в камере. Активируем базу маркеров для камеры ARCamera. Для этого в свойствах ARCamera в окне Inspector в разделе Dataset Load Behaviour выберем Load markers Database и Activate. Соберем Unity проект в виде приложения для Android устройства. Для этого в меню File выберем Build Settings, выберем платформу Android и кнопкой Add Open Scenes добавим созданную сцену.
Кнопка Player Settings открывает окно настроек сборки приложения.
В поле Company Name введем свое имя, в разделе Other Settings в поле Bundle Identifier введем имя пакета, состоящее из полей Company Name и Product Name.
Нажмем кнопку Build и сохраним APK файл. Установим APK файл на мобильное устройство. Теперь при наведении камеры устройства на ранее сфотографированный предмет, будет появляться виртуальная 3D модель. ARToolKit ARToolKit является библиотекой компьютерного зрения, которая обеспечивает функциональные возможности отслеживания, необходимые для создания приложений дополненной реальности. ARToolKit плагин для Unity ARToolKit плагин для Unity можно скачать на странице https://artoolkit.org/downloadartoolkit-sdk#unity. Более актуальную и стабильную версию плагина для Unity можно скачать на GitHub https://github.com/artoolkit/arunity5. После скачивания пакета откроем Unity и создадим новый 3D проект. В меню Assets выберем Import Package/Custom Package, откроем и импортируем скачанный пакет.
В сцене Unity проекта удалим все объекты и с помощью меню Create/Create Empty создадим объект GameObject, который назовем ARToolKit. Этот объект будет содержать два AR конфигурационных объекта ARController и ARMarker. Поэтому перетащим их из папки Scripts в объект ARToolKit.
ARController скрипт отвечает за видео-фон и за создание и управление AR отслеживанием. ARMarker скрипт обеспечивает маркер отслеживания, к которому прикрепляется 3D модель. В поле Marker tag введем идентификатор маркера. По умолчанию тип маркера Square, и изображение маркера установлено из папки Resources/ardata/markers.
Для использования этого маркера, изображение нужно распечатать из каталога ARUnitytools\doc\patterns дополнительного набора инструментов Additional Unity Tools, который можно скачать на странице https://artoolkit.org/download-artoolkit-sdk. Другой тип маркера это Square Barcode. Square Barcode это изображение, имеющее шаблон, предопределенный для распознавания библиотекой ARToolKit, в виде матрицы из черных и белых квадратов. Использование данного типа маркера ускоряет его распознавание камерой и обеспечивает надежность его идентификации. Для распечатки маркеры Square Barcode находятся в каталоге ARUnity-tools\doc\patterns дополнительного набора инструментов Additional Unity Tools. Для использования маркера Square Barcode в свойствах ARController скрипта в разделе Square tracking options в поле Pattern detection mode выберем AR_MATRIX_CODE_DETECTION, а в поле Matrix Code Type выберем тип набора маркеров. В свойствах ARMarker скрипта в поле Type выберем Square Barcode, в поле Barcode ID введем номер маркера из набора. Следующий тип маркера это Multimarker, состоящий из множества маркеров Square Barcode для прикрепления к одному 3D объекту. Для распечатки маркеры Multimarker находятся в каталоге ARUnity-tools\doc\patterns дополнительного набора инструментов Additional Unity Tools. Преимущество использования Multimarker состоит в повышенной устойчивости к окклюзии, даже когда один маркер затемняется, другой маркер все равно виден, а также в эффективном покрытии большего оптического угла, что приводит к уменьшению ошибок. Для использования маркера Multimarker в свойствах ARController скрипта в разделе Square tracking options в поле Pattern detection mode выберем AR_MATRIX_CODE_DETECTION, в свойствах ARMarker скрипта в поле Type выберем Multimarker, в поле Multimarker config. введем имя конфигурационного файла из каталога StreamingAssets.
Следующий тип маркера это NFT маркер. Функция Natural Feature Tracking (NFT) позволяет распознавать и отслеживать обычное изображение, которое не содержит предопределенные маркеры. Для распечатки примеры NFT маркеров находятся в каталоге ARUnity-tools\doc\Marker images дополнительного набора инструментов Additional Unity Tools. Для использования NFT маркера в свойствах ARMarker скрипта в поле Type выберем NFT, в поле NFT dataset name введем имя набора данных из каталога StreamingAssets. Примеры сцен, созданных с использованием различных типов маркеров, можно посмотреть в каталоге Example Scenes.
Создадим свой маркер, к которому будет прикрепляться 3D модель. Для этого сфотографируем и сохраним jpeg изображение предмета, который будет служить NFT маркером. Переместим изображение в папку bin каталога дополнительного набора инструментов Additional Unity Tools, в которой находится инструмент genTexData. exe генерации NFT набора данных В командной строке наберем: genTexData. exe image.jpeg Далее в процессе генерации нужно будет ввести уровень извлечения характерных точек изображения, а также диапазон разрешения изображения, в котором характерные точки изображения будут извлекаться при приближении или удалении камеры. После окончания генерации NFT набора мы получим три файла. iset,.fset и. fset3, которые перетащим в каталог StreamingAssets Unity проекта. Далее в свойствах ARMarker скрипта в поле Type выберем NFT, в поле NFT dataset name введем имя полученного NFT набора из каталога StreamingAssets. Для дальнейшего формирования сцены с помощью меню Create/Create Empty создадим объект GameObject, который назовем Scene root. Перетащим из папки Scripts в объект Scene root скрипт AROrigin, представляющий центр ARToolkit мира и являющийся корневым объектом сцены. В свойствах объекта Scene root в окне Inspector в поле Layer выберем 9 слой сцены.
При этом в свойствах ARController скрипта в разделе Video Options в поле Layer должен быть выбран 8 слой сцены, а также выбрана опция Allow non-RGB video.
С помощью меню Create/Create Empty Child создадим объект GameObject, дочерний для объекта Scene root, который назовем Marker. Перетащим из папки Scripts в объект Marker скрипт ARTrackedObject, представляющий маркер. Его дочерние объекты будут автоматически прикрепляться к этому маркеру. В свойствах скрипта ARTrackedObject в поле Marker tag введем тот же идентификатор, что и в поле Marker tag свойствах скрипта ARMarker. К объекту Marker прикрепим дочерний объект – 3D модель.
Нажмем правой кнопкой мышки на объекте Scene root и прикрепим к нему дочерний объект Camera.
Перетащим из папки Scripts в объект Camera скрипт ARCamera, связывающий Unity камеру с AR контентом. В свойствах объекта Camera в поле Culling Mask выберем 9 слой сцены. Нажмем кнопку проигрывания сцены и поднесем к камере маркер – должен появиться 3D объект.
Для сборки Android приложения в меню File выберем Build Settings, кнопкой Add Open Scenes добавим нашу сцену, выберем платформу Android и нажмем кнопку Player Settings. В поле Company Name введем имя пакета, в поле Product Name введем имя приложения, в разделе Other Settings в поле Bundle Identifier введем com. [Company Name]. [Product Name]. В других разделах введем остальные настройки приложения. В каталоге Plugins/Android откроем файл манифеста AndroidManifest. xml и в атрибуте package введем com. [Company Name]. [Product Name]. После этого нажмем Build и соберем APK файл Android приложения. ARToolKit для Android Скачаем и установим: Java Development Kit 1.7+ Android Studio IDE 1.5.x+ Android SDK Git При установке Git в окне Adjusting your PATH environment выберем Use Git from Git Bash only, в окне Configuring the line ending conversions выберем Checkout Windows-style, commit Unix-style line ends. Для установки Android NDK откроем Android Studio и в меню File откроем Project Structure. Воспользуемся ссылкой Download Android NDK.
В результате в каталоге Android SDK будет создана папка ndk-bundle, содержащая Android NDK. Установим переменные среды. ANDROID_HOME = …\android-sdks ANDROID_NDK_ROOT = %ANDROID_HOME%\ndk-bundle NDK = %ANDROID_HOME%\ndk-bundle ;%NDK%\ добавить в Path Из GitHub скачаем и распакуем artoolkit5 и в папке artoolkit5\android запустим Git скрипт build.sh и build_native_examples.sh. Возможно, в скрипте нужно будет вывести переменную $WinsVerNum и поменять ее значение в коде $WinsVerNum = «6.3». else #Checking for Windows in a non-cygwin dependent way. WinsOS=
if [[$OS]]; then WinsVerNum=$ {OS##*-} echo $WinsVerNum if [[$WinsVerNum = «10.0» || $WinsVerNum = «6.3»]]; then if [[$WinsVerNum = «10.0»]]; then WinsOS=«Wins10» else WinsOS=«Wins8.1» fi echo Building on Microsoft $ {WinsOS} Desktop \ ($ {ARCH} \) export HOST_OS=«windows» NDK_BUILD_SCRIPT_FILE_EXT=".cmd» CPUS=`/usr/bin/nproc` fi fi fi if [[! $CPUS]]; then echo **Development platform not supported, exiting script** read -rsp $«Press enter to continue…\n’ exit 1 В результате будет сгенерирована папка libs с файлами libARWrapper.so и libc++_shared.so для различных CPU архитектур в папке artoolkit5\android, а также в проектах каталога artoolkit5\AndroidStudioProjects. Откроем Android Studio и откроем проект ARSimpleProj каталога AndroidStudioProjects.
В меню File откроем Project Structure и увидим, что модуль аRSimple имеет зависимость от модуля аRBaseLib, представленного проектом ARBaseLibProj. Если этой зависимости нет, ее нужно добавить с помощью меню File/Project Structure. Библиотека ARBaseLib предоставляет Java классы ARToolKit, ARActivity и ARRenderer для создания ARToolKit приложения и обеспечивает с помощью JNI связь с нативной C++ библиотекой ARWrapper, представленной файлами libARWrapper.so и libc++_shared.so, которая управляет жизненным циклом ARToolkit приложения, включая инициализацию, добавление маркеров и др. При создании своего ARToolKit приложения, в каталог src\main проекта нужно включить папку libs с файлами libARWrapper.so и libc++_shared.so для различных CPU архитектур, а также добавить зависимость от модуля аRBaseLib с помощью меню File/Project Structure/Add a new module/Import. JAR/.AAR Package/AndroidStuiodProjects/ARBaseLibProj/arBaseLib/build/outputs/aar/ ARBaseLib. aar. Документация API библиотеки ARBaseLib находится в папке AndroidStudioProjects\ARBaseLibProj\doc. При запуске приложения ARSimple, при наведении камеры устройства на маркер, будет появляться 3D объект – куб. Из предыдущего раздела ARToolKit плагин для Unity возьмем изображение gibraltar.jpg и инструмент genTexData. exe и создадим набор данных маркера. genTexData. exe gibraltar.jpg Поместим файлы. iset,.fset и. fset3 в папку AndroidStudioProjects\ARSimpleProj\aRSimple\src\main\assets\Data. В классе SimpleRenderer изменим код добавления маркера. @Override public boolean configureARScene () { markerID = ARToolKit.getInstance().addMarker («nft; Data/gibraltar»); if (markerID <0) return false; return true; } В файле модуля build.gradle увеличим значение versionCode для обновления кэша. Теперь при наведении камеры устройства на простое изображение, будет появляться 3D объект – куб.
По умолчанию, свойства камеры устройства содержатся в файле camera_para. dat папки src\main\assets\Data проекта ARToolKit приложения. Параметры камеры устройства по умолчанию являются достаточными для базового отслеживания для широкого спектра различных камер. OpenSpace3D Проект OpenSpace3D, основанный на языке Scol 3D приложений режима реального времени и многопользовательских приложений и 3D-движке SO3Engine, предоставляет набор свободного программного обеспечения для разработки проектов приложений виртуальной и дополненной реальности. В OpenSpace3D отсутствует возможность монетизации готового приложения и возможность экспорта сцены в распространенные форматы. В OpenSpace3D есть возможность регистрации маркера динамически, на лету, также имеется Face Tracking. Скачаем и установим OpenSpace3D, а также дополнительные пакеты (http://www.openspace3d.com/lang/en/support/download/). При этом будет создан каталог для OpenSpace3D проектов C:\Users\user\Documents\OpenSpace3D. После скачивания и установки дистрибутива OpenSpace3D, откроем OpenSpace3D редактор. В правом верхнем углу, в меню Preferences выберем язык интерфейса редактора.
В качестве примера откроем готовую сцену tutorial01_quickstart. xos папки OpenSpace3D\demos\tutorial с помощью меню Open scene или с помощью меню Import scene. Приблизим ящик сцены – это будет узел cardboard сцены и нажмем правой кнопкой мышки.
Выберем Set physic.
Увидим, что для этого объекта выбран Body type shape и установлена масса 0.1 кг. Выберем узел cave сцены, нажмем правой кнопкой мышки и выберем Set physic.
Увидим, что для этого объекта выбран Body type collisionTree. Нажмем правой кнопкой мышки на узле Scene и выберем Set physic setting.
Увидим, что физика включена при проигрывании сцены, а значение ускорения свободного падения установлено как -9.8. Если минус поменять на плюс, тогда ящики окажутся на потолке. В зоне редактирования функций щелкнем два раза на сцене и во вкладке Scene увидим, что подключен компонент FPS Like Controller, обеспечивающий перемещение в сцене от первого лица. FPS like Controller PlugIT можно подключить, нажав правой кнопкой мышки в зоне редактирования функций вкладки Scene и выбрав navigation/FPS like controller. Удалить компонент можно кнопкой Delete клавиатуры.
Щелкнув два раза на компоненте FPS like controller, можно установить его свойства, такие как угол камеры, скорость передвижения и др.
Нажав кнопку Play/Stop панели инструментов, походим по сцене и разобьем стопку ящиков. В 3D представлении сцены можно добавлять, удалять и перемещать 3D объекты, а также редактировать их свойства. Для выбора объекта сцены кликнем на нем левой кнопкой мышки. После этого для редактирования его свойств или его удаления кликнем правой кнопкой мышки.
Remove удаляет объект. Clone дублирует объект, после чего нужно потянуть за ось Х, чтобы разъединить объекты. Set orientation позволяет положить объект. Перемещать объекты по сцене можно, нажав кнопку Move панели инструментов и перетаскивая объект за оси. Кнопка Rotate панели инструментов позволяет вращать объект, а кнопка Scale панели инструментов позволяет масштабировать объект. Чтобы придать реальность объекту, в его свойствах выберем Set physic, выберем объекту форму shape и установим его массу, при этом в узле cave сцены должна быть установлена форма collisionTree. Если отмечен флажок Freezed, тогда физика объекта будет ожидать столкновения, перед тем как заработать. Чтобы объединить объекты в группу, на узле Scene нажмем правой кнопкой мышки и выберем Add dummy, затем в дереве сцены перетащим объекты в созданный узел dummy.
Теперь можно манипулировать сразу группой объектов. Компоненты PlugIT обеспечивают взаимодействие пользователя со сценой. Связываясь между собой, компоненты PlugIT образуют систему событий и действий.
В зоне редактирования функций вкладки Scene удалим PlugIT FPS like Controller с помощью клавиши Delete клавиатуры. Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/object click. Присвоим имя switch экземпляру PlugIT. В поле Object name свяжем экземпляр PlugIT с объектом switch дерева сцены. Отметим флажок Enable hand cursor. В поле Bubble label введем click to light и нажмем Ok.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/light, свяжем этот PlugIT экземпляр с объектом Omnidir.
Нажмем правой кнопкой мышки на экземпляре switch и выберем LeftClick, свяжем его с экземпляром light_room, выбрав Off. Теперь при проигрывании сцены, при нажатии левой кнопкой мышки на выключателе у двери, лампочка будет выключаться. С помощью клавиши Delete удалим связь между экземпярами. Нажмем правой кнопкой мышки на зоне редактирования функций и выберем misc/sequence.
Нажмем правой кнопкой мышки на экземпляре switch и выберем LeftClick, свяжем его с экземпляром On/Off sequence, выбрав Input. Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out1, свяжем его с экземпляром light_room, выбрав Off. Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out2, свяжем его с экземпляром light_room, выбрав On. Теперь при проигрывании сцены, при нажатии левой кнопкой мышки на выключателе у двери, лампочка будет выключаться и включаться. Однако при выключении цвет лампочки остается желтым. Чтобы это исправить нажмем правой кнопкой мышки на зоне редактирования функций и выберем material/material color. Свяжем этот экземпляр с объектом lamp. Установим цвет материала.
Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out1, свяжем его с экземпляром material color, выбрав Enable. Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out2, свяжем его с экземпляром material color, выбрав Disable.
Теперь при проигрывании сцены, при нажатии левой кнопкой мышки на выключателе у двери, лампочка будет выключаться и включаться, а ее цвет будет меняться. Сделаем так, чтобы на телевизоре сцены проигрывалось видео. Для этого нажмем правой кнопкой мышки на зоне редактирования функций и выберем media/Video. Отметим флажок Apply on texture и свяжем этот экземпляр с объектом oldscreen. В поле Url введем ссылку. Отметим флажок Auto play и Play in loop, в поле Volume поставим 0, уберем флажок Transparency.
Теперь по телевизору можно смотреть видео. Выберем объект lense и нажмем Zoom on selected object в панели инструментов.
Нажмем правой кнопкой на объекте и выберем Add dynamic reflection map. Выберем материал Lense, текстуру reflection и размер 512, отметим флажок Enable. Нажмем правой кнопкой на объекте и выберем Add dynamic reflection map. Выберем материал Lense, текстуру refraction и размер 512, отметим флажок Enable и Revert clip plane. В результате получим нормальную линзу. Импортируем в сцену робота. Для этого выберем меню Import scene/Standard file formats/OpenSpace3D\demos\tutorial\andy.scene/As a new group/Do you want to import this environment setting/No.
Нажмем правой кнопкой мышки на узле walk_inplace и уберем флажок Enable. Для этого нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/object click. Свяжем этот экземпляр с объектом andy_robot.
Нажмем правой кнопкой мышки на узле pose_init и отметим флажок Enable. Для этого нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/animation transition. First animation name свяжем с posed_init, а Second animation name свяжем с posed2.
Нажмем правой кнопкой мышки на wake up и выберем LeftClick и свяжем с animation transition, выбрав Fade in. Теперь, при проигрывании сцены, при нажатии на роботе, он будет выпрямляться. Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/animation transition. First animation name свяжем с posed2, а Second animation name свяжем с idle1.
Нажмем правой кнопкой мышки на animation transition и выберем Transition ended и свяжем с animation transition1, выбрав Fade in. Нажмем правой кнопкой мышки на узле idle и выберем Edit setting. Отметим флажок Loop. Таким образом, получим двухступенчатую анимацию. Приступим к созданию дополненной реальности. Создадим новую сцену, в которую импортируем модель OpenSpace3D\assets\models\library\Primitive_World\Edaphosaurus\Edafossauro.mesh. Нажмем правой кнопкой мышки на узле default_skl, выберем Edit setting и отметим флажок Loop.
Нажмем правой кнопкой мышки на узле Scene и выберем Add dummy. Перетащим модель в узел dummy. Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/AR capture.
Нажмем правой кнопкой мышки на узле Scene и выберем Add camera. Нажмем кнопку Move панели инструментов и перетащим камеру по оси Z.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/set active camera. Свяжем этот экземпляр с объектом сцены AR_camera. Отметим флажок Enable on init. Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/AR marker. В поле Bitmap path выберем произвольное изображение, которое поместим в каталог OpenSpace3D и распечатаем. Подождем пока изображение не будет обработано в качестве маркера. Изменим размер маркера и свяжем его с объектом dummy.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем oblect/hide. Свяжем этот экземпляр с объектом dummy и отметим флажок Hide on start.
Нажмем правой кнопкой мышки на экземпляре AR marker и выберем Found, соединим его с экземпляром hide и выберем Show. Нажмем правой кнопкой мышки на экземпляре AR marker и выберем Lost, соединим его с экземпляром hide и выберем Hide. Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/keyboard. Выберем клавишу.
Нажмем правой кнопкой мышки на экземпляре keyboard и выберем Key down, соединим его с экземпляром AR marker, выбрав Register current frame. Теперь маркер будет регистрироваться динамически, при нажатии соответствующей клавиши – поднесем к камере изображение и нажмем клавишу – должна появиться 3D модель. В свойствах экземпляра AR marker, в качестве маркера можно установить реперный маркер, сохранив его для распечатывания.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/AR face tracker. Изменим размер маркера и свяжем его с объектом dummy.
Нажмем правой кнопкой мышки на экземпляре AR face tracker и выберем Found, соединим его с экземпляром hide и выберем Show. Нажмем правой кнопкой мышки на экземпляре AR face tracker и выберем Lost, соединим его с экземпляром hide и выберем Hide. Теперь, если посмотреть прямо в камеру, появится 3D модель. Соберем проект в Android приложение. Для этого в меню Export to Openspace3D player выберем As an Android Application.
Добавим или выберем хранилище ключей и в результате получим Android проект с готовым APK файлом в папке bin. BeyondAR BeyondAR это фреймворк, обеспечивающий ресурсы для разработки приложений с дополненной реальностью, основанной на георасположении на смартфонах и планшетах. Для начала работы скачаем BeyondAR фреймворк с Github https://github.com/BeyondAR/beyondar. Импортируем проект BeyondAR_Examples в среду разработки Android Studio. Для запуска этого приложения на Android устройстве требуется наличие датчика ориентации. Для сборки APK файла с большим количеством методов в коде, в Gradle файл добавим: defaultConfig { multiDexEnabled true } dependencies { compile 'com.android.support: multidex:1.0.0» } android {
dexOptions { javaMaxHeapSize «4g» } } В файл манифеста: <application android:name="android.support.multidex.MultiDexApplication»> Для двух классов, возможно, придется реализовать OnMapReadyCallback. package com.beyondar. example; import android.content.Context; import android. location. LocationManager; import android. os. Bundle; import android.support.v4.app.FragmentActivity; import android.view.View; import android.view.View. OnClickListener; import android. widget. Button; import android.widget.Toast; import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin; import com.beyondar.android.util.location.BeyondarLocationManager; import com.beyondar.android.world.GeoObject; import com.beyondar.android. world. World; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps. GoogleMap; import com.google.android.gms.maps. GoogleMap. OnMarkerClickListener; import com.google.android.gms.maps. OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; public class BeyondarLocationManagerMapActivity extends FragmentActivity implements OnMarkerClickListener, OnClickListener, OnMapReadyCallback { private GoogleMap mMap; private GoogleMapWorldPlugin mGoogleMapPlugin; private World mWorld; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.map_google); Button myLocationButton = (Button) findViewById(R.id.myLocationButton); myLocationButton.setVisibility(View.VISIBLE); myLocationButton.setOnClickListener (this); ((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this); } @Override public boolean onMarkerClick (Marker marker) { // To get the GeoObject that owns the marker we use the following // method: GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker); if (geoObject!= null) { Toast.makeText (this, «Click on a marker owned by a GeoOject with the name: " + geoObject.getName (), Toast.LENGTH_SHORT).show (); } return false;
} @Override protected void onResume () { super. onResume (); // When the activity is resumed it is time to enable the // BeyondarLocationManager BeyondarLocationManager. enable (); } @Override protected void onPause () { super. onPause (); // To avoid unnecessary battery usage disable BeyondarLocationManager // when the activity goes on pause. BeyondarLocationManager. disable (); } @Override public void onClick (View v) { // When the user clicks on the button we animate the map to the user // location LatLng userLocation = new LatLng(mWorld.getLatitude (), mWorld.getLongitude ()); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom (userLocation, 15)); mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); } @Override public void onMapReady (GoogleMap googleMap) { mMap=googleMap;
// We create the world and fill the world mWorld = CustomWorldHelper.generateObjects (this); // As we want to use GoogleMaps, we are going to create the plugin and // attach it to the World mGoogleMapPlugin = new GoogleMapWorldPlugin (this); // Then we need to set the map in to the GoogleMapPlugin mGoogleMapPlugin.setGoogleMap (mMap); // Now that we have the plugin created let’s add it to our world. // NOTE: It is better to load the plugins before start adding object in // to the world. mWorld.addPlugin (mGoogleMapPlugin); mMap.setOnMarkerClickListener (this); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15)); mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); // Lets add the user position to the map GeoObject user = new GeoObject (1000l); user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ()); user.setImageResource (R. drawable. flag); user.setName («User position»); mWorld.addBeyondarObject (user); BeyondarLocationManager.addWorldLocationUpdate (mWorld); BeyondarLocationManager.addGeoObjectLocationUpdate (user); // We need to set the LocationManager to the BeyondarLocationManager. BeyondarLocationManager .setLocationManager ((LocationManager) getSystemService (Context. LOCATION_SERVICE)); }
} package com.beyondar. example; import android. os. Bundle; import android.support.v4.app.FragmentActivity; import android.widget.Toast; import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin; import com.beyondar.android.world.GeoObject; import com.beyondar.android. world. World; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps. GoogleMap; import com.google.android.gms.maps. GoogleMap. OnMarkerClickListener; import com.google.android.gms.maps. OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.Marker; public class GoogleMapActivity extends FragmentActivity implements OnMarkerClickListener, OnMapReadyCallback { private GoogleMap mMap; private GoogleMapWorldPlugin mGoogleMapPlugin; private World mWorld; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.map_google); ((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this); } @Override
public boolean onMarkerClick (Marker marker) { // To get the GeoObject that owns the marker we use the following // method: GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker); if (geoObject!= null) { Toast.makeText (this, «Click on a marker owned by a GeoOject with the name: " + geoObject.getName (), Toast.LENGTH_SHORT).show (); } return false; } @Override public void onMapReady (GoogleMap googleMap) { mMap=googleMap; // We create the world and fill the world mWorld = CustomWorldHelper.generateObjects (this); // As we want to use GoogleMaps, we are going to create the plugin and // attach it to the World mGoogleMapPlugin = new GoogleMapWorldPlugin (this); // Then we need to set the map in to the GoogleMapPlugin mGoogleMapPlugin.setGoogleMap (mMap); // Now that we have the plugin created let’s add it to our world. // NOTE: It is better to load the plugins before start adding object in to the world. mWorld.addPlugin (mGoogleMapPlugin); mMap.setOnMarkerClickListener (this); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15));
mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); // Lets add the user position GeoObject user = new GeoObject (1000l); user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ()); user.setImageResource (R. drawable. flag); user.setName («User position»); mWorld.addBeyondarObject (user); } } После запуска приложения на Android устройстве появится список с примерами.
Simple AR camera – показывает набор изображений на фоне камеры. При этом изображения расположены в пространстве вокруг устройства.
Simple camera with a max/min distance far for rendering – показывает набор изображений на фоне камеры с возможностью регулировки расстояния до изображений. BeyondAR World in Google maps – показывает набор изображений на карте. AR camera with Google maps – показывает набор изображений на фоне камеры с кнопкой переключения на карту. Camera with touch events – показывает набор изображений на фоне камеры, а также сообщение при нажатии на одном из изображений. Camera with screenshot – показывает набор изображений на фоне камеры с кнопкой скриншота. Change GeoObject images on touch – показывает набор изображений на фоне камеры, которые заменяются на другие изображения при нажатии. Attach view to GeoObject – показывает набор изображений на фоне камеры с добавлением вида к изображению при нажатии. Set static view to geoObject – вместо изображений показывает виды на фоне камеры, а также сообщение при нажатии на одном из видов. Customize sensor filter – показывает набор изображений на фоне камеры с возможностью регулировки чувствительности датчика ориентации. Simple AR camera with a radar view – показывает набор изображений на фоне камеры, а также расположение изображений вокруг устройства. Using BeyondarLocationManager – показывает набор изображений на карте с кнопкой обновления местоположения. Для работы BeyondAR фреймворка в файле манифеста приложения декларируются необходимые разрешения и наличие сенсоров устройства. <! – Minimum permissions for Beyondar – > <uses-permission android:name="android.permission.CAMERA» /> <! – For Beyondar this is not mandatory unless you want to load something from Internet (for instance images) – > <uses-permission android:name="android.permission.INTERNET» /> <! – BeyondAR needs the following features – > <uses-feature android:name="android.hardware.camera» />
<uses-feature android:name="android.hardware.sensor.accelerometer» /> <uses-feature android:name="android.hardware.sensor.compass» /> Активность SimpleCameraActivity, отображающая набор изображений на фоне камеры, имеет достаточно простой код. package com.beyondar. example; import android. os. Bundle; import android.support.v4.app.FragmentActivity; import android.view. Window; import com.beyondar.android.fragment.BeyondarFragmentSupport; import com.beyondar.android. world. World; public class SimpleCameraActivity extends FragmentActivity { private BeyondarFragmentSupport mBeyondarFragment; private World mWorld; /** Called when the activity is first created. */ @Override public void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); // Hide the window title. requestWindowFeature (Window. FEATURE_NO_TITLE); setContentView(R.layout.simple_camera);
mBeyondarFragment = (BeyondarFragmentSupport) getSupportFragmentManager().findFragmentById(R.id.beyondarFragment); // We create the world and fill it… mWorld = CustomWorldHelper.generateObjects (this); // … and send it to the fragment mBeyondarFragment.setWorld (mWorld); // We also can see the Frames per seconds mBeyondarFragment.showFPS (true); } } В методе onCreate создается фрагмент BeyondarFragmentSupport, отвечающий за отображение вида камеры и вида BeyondarGLSurfaceView, рисующего дополненную реальность. Для этого используется файл компоновки. <?xml version=«1.0» encoding=«utf-8»? > <FrameLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«match_parent» android: layout_height=«match_parent» android: id="@+id/parentFrameLayout»> <fragment android: id="@+id/beyondarFragment» android:name="com.beyondar.android.fragment.BeyondarFragmentSupport» android: layout_width=«match_parent»
android: layout_height=«match_parent» /> </FrameLayout> Далее создается объект World – контейнер объектов дополненной реальности, который затем добавляется во фрагмент BeyondarFragmentSupport. Метод mBeyondarFragment.showFPS (true) показывает количество кадров в секунду в левом верхнем углу экрана. Вся магия по созданию объектов дополненной реальности осуществляется в классе CustomWorldHelper. Здесь создается новый контейнер World, устанавливается его местоположение в реальном мире, а также на основе изображений создаются объекты GeoObject, которые добавляются в контейнер World. public static World sharedWorld; sharedWorld = new World (context); sharedWorld.setGeoPosition (41.90533734214473d, 2.565848038959814d); GeoObject go4 = new GeoObject (4l); go4.setGeoPosition (41.90518862002349d, 2.565662767707665d); go4.setImageUri("assets://creature_7.png»); go4.setName («Image from assets»); sharedWorld.addBeyondarObject (go4); По умолчанию для контейнера World и для его объектов, в классе CustomWorldHelper, задаются фиксированные координаты в реальном мире. Исправим это, привязав координаты контейнера World к местоположению устройства. Для определения местоположения устройства используем Fused location provider API (Android API Level> v9, Android Build Tools> v21). Изменим код классов CustomWorldHelper, GoogleMapActivity и SimpleCameraActivity. import android.annotation.SuppressLint;
import android.content.Context; import android. location. Location; import android.widget.Toast; import com.beyondar.android.world.GeoObject; import com.beyondar.android. world. World; @SuppressLint («SdCardPath») public class CustomWorldHelper { public static final int LIST_TYPE_EXAMPLE_1 = 1; public static World sharedWorld; public static World generateObjects (Context context, Location mCurrentLocation) { sharedWorld = new World (context); // The user can set the default bitmap. This is useful if you are // loading images form Internet and the connection get lost sharedWorld.setDefaultImage(R.drawable.beyondar_default_unknow_icon); // User position (you can change it using the GPS listeners form Android // API) if (mCurrentLocation== null) { mCurrentLocation=new Location (»»); mCurrentLocation.setLatitude (41.90533734214473d); mCurrentLocation.setLongitude (2.565848038959814d); }
sharedWorld.setGeoPosition(mCurrentLocation.getLatitude(),mCurrentLocation.getLongitude ()); // Create an object with an image in the app resources. // And the same goes for the app assets GeoObject go = new GeoObject (1l); go.setGeoPosition(mCurrentLocation.getLatitude()+0.00005,mCurrentLocation.getLongitude () 0.0001); go.setImageUri("assets://creature_7.png»); go.setName («Image from assets»); // Add the GeoObjects to the world sharedWorld.addBeyondarObject (go); return sharedWorld; } } import android.content.pm.PackageManager; import android. location. Location; import android. os. Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentActivity; import android.widget.Toast; import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin;
import com.beyondar.android.world.GeoObject; import com.beyondar.android. world. World; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common. api. GoogleApiClient; import com.google.android.gms. location. LocationListener; import com.google.android.gms. location. LocationRequest; import com.google.android.gms. location. LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps. GoogleMap; import com.google.android.gms.maps. GoogleMap. OnMarkerClickListener; import com.google.android.gms.maps. OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.Marker; public class GoogleMapActivity extends FragmentActivity implements OnMarkerClickListener, OnMapReadyCallback, LocationListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient. OnConnectionFailedListener { private GoogleMap mMap; private GoogleMapWorldPlugin mGoogleMapPlugin; private World mWorld; GoogleApiClient mGoogleApiClient; Location mCurrentLocation; LocationRequest mLocationRequest; @Override protected void onCreate (Bundle savedInstanceState) {
super. onCreate (savedInstanceState); setContentView(R.layout.map_google); ((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this); buildGoogleApiClient (); } /** * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the * LocationServices API. */ protected synchronized void buildGoogleApiClient () { mGoogleApiClient = new GoogleApiClient. Builder (this) .addConnectionCallbacks (this) .addOnConnectionFailedListener (this) .addApi (LocationServices. API) .build (); createLocationRequest (); } protected void createLocationRequest () { mLocationRequest = LocationRequest.create (); // Sets the desired interval for active location updates. This interval is // inexact. You may not receive updates at all if no location sources are available, or
// you may receive them slower than requested. You may also receive updates faster than // requested if other applications are requesting location at a faster interval. mLocationRequest.setInterval (10000); // Sets the fastest rate for active location updates. This interval is exact, and your // application will never receive updates faster than this value. mLocationRequest.setFastestInterval (5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } @Override public void onStart () { super. onStart (); mGoogleApiClient.connect (); } @Override public void onStop () { super. onStop (); mGoogleApiClient. disconnect (); } @Override public void onResume () { super. onResume ();
// Within {@code onPause ()}, we pause location updates, but leave the // connection to GoogleApiClient intact. Here, we resume receiving // location updates if the user has requested them. if (mGoogleApiClient.isConnected ()) { startLocationUpdates (); } } @Override protected void onPause () { super. onPause (); // Stop location updates to save battery, but don’t disconnect the GoogleApiClient object. if (mGoogleApiClient.isConnected ()) { stopLocationUpdates (); } } protected void startLocationUpdates () { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { return; } LocationServices.FusedLocationApi.requestLocationUpdates (
mGoogleApiClient, mLocationRequest, this); } /** * Removes location updates from the FusedLocationApi. */ protected void stopLocationUpdates () { // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. // The final argument to {@code requestLocationUpdates ()} is a LocationListener // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html). LocationServices.FusedLocationApi.removeLocationUpdates (mGoogleApiClient, this); } @Override public boolean onMarkerClick (Marker marker) { // To get the GeoObject that owns the marker we use the following // method: GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker); if (geoObject!= null) { Toast.makeText (this, «Click on a marker owned by a GeoOject with the name: " + geoObject.getName (), Toast.LENGTH_SHORT).show (); }
return false; } @Override public void onMapReady (GoogleMap googleMap) { mMap=googleMap; // We create the world and fill the world mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation); // As we want to use GoogleMaps, we are going to create the plugin and // attach it to the World mGoogleMapPlugin = new GoogleMapWorldPlugin (this); // Then we need to set the map in to the GoogleMapPlugin mGoogleMapPlugin.setGoogleMap (mMap); // Now that we have the plugin created let’s add it to our world. // NOTE: It is better to load the plugins before start adding object in to the world. mWorld.addPlugin (mGoogleMapPlugin); mMap.setOnMarkerClickListener (this); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15)); mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); // Lets add the user position GeoObject user = new GeoObject (1000l); user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ());
user.setImageResource (R. drawable. flag); user.setName («User position»); mWorld.addBeyondarObject (user); } @Override public void onConnected (@Nullable Bundle bundle) { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { return; } Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation (mGoogleApiClient); if (mLastLocation!= null) { mCurrentLocation = mLastLocation; String lat = String.valueOf(mCurrentLocation.getLatitude ()); String lon = String.valueOf(mCurrentLocation.getLongitude ()); Toast toast = Toast.makeText (this, «Last location» + lat + " " + lon, Toast. LENGTH_LONG); toast.show (); mWorld.clearWorld (); mMap.clear (); mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation); mGoogleMapPlugin = new GoogleMapWorldPlugin (this);
mGoogleMapPlugin.setGoogleMap (mMap); mWorld.addPlugin (mGoogleMapPlugin); mMap.setOnMarkerClickListener (this); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15)); mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); GeoObject user = new GeoObject (1000l); user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ()); user.setImageResource (R. drawable. flag); user.setName («User position»); mWorld.addBeyondarObject (user); } else { startLocationUpdates (); } } @Override public void onConnectionSuspended (int i) { } @Override public void onConnectionFailed (@NonNull ConnectionResult connectionResult) { }
@Override public void onLocationChanged (Location location) { mCurrentLocation = location; String lat = String.valueOf(mCurrentLocation.getLatitude ()); String lon = String.valueOf(mCurrentLocation.getLongitude ()); Toast toast = Toast.makeText (this,«Current location " + lat+" "+lon, Toast. LENGTH_LONG); toast.show (); mWorld.clearWorld (); mMap.clear (); mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation); mGoogleMapPlugin = new GoogleMapWorldPlugin (this); mGoogleMapPlugin.setGoogleMap (mMap); mWorld.addPlugin (mGoogleMapPlugin); mMap.setOnMarkerClickListener (this); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15)); mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); GeoObject user = new GeoObject (1000l); user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ()); user.setImageResource (R. drawable. flag); user.setName («User position»); mWorld.addBeyondarObject (user); } } import android.content.pm.PackageManager; import android. location. Location;
import android. os. Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentActivity; import android.view. Window; import android.widget.Toast; import com.beyondar.android.fragment.BeyondarFragmentSupport; import com.beyondar.android. opengl. util. LowPassFilter; import com.beyondar.android. world. World; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common. api. GoogleApiClient; import com.google.android.gms. location. LocationListener; import com.google.android.gms. location. LocationRequest; import com.google.android.gms. location. LocationServices; public class SimpleCameraActivity extends FragmentActivity implements LocationListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient. OnConnectionFailedListener { private BeyondarFragmentSupport mBeyondarFragment; private World mWorld; GoogleApiClient mGoogleApiClient; Location mCurrentLocation; LocationRequest mLocationRequest; /** Called when the activity is first created. */
@Override public void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); // Hide the window title. requestWindowFeature (Window. FEATURE_NO_TITLE); setContentView(R.layout.simple_camera); mBeyondarFragment = (BeyondarFragmentSupport) getSupportFragmentManager () .findFragmentById(R.id.beyondarFragment); // We also can see the Frames per seconds mBeyondarFragment.showFPS (false); // We create the world and fill it… mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation); // … and send it to the fragment mBeyondarFragment.setWorld (mWorld); LowPassFilter.ALPHA = 0.003f; buildGoogleApiClient (); } /** * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the * LocationServices API. */
protected synchronized void buildGoogleApiClient () { mGoogleApiClient = new GoogleApiClient. Builder (this) .addConnectionCallbacks (this) .addOnConnectionFailedListener (this) .addApi (LocationServices. API) .build (); createLocationRequest (); } protected void createLocationRequest () { mLocationRequest = LocationRequest.create (); // Sets the desired interval for active location updates. This interval is // inexact. You may not receive updates at all if no location sources are available, or // you may receive them slower than requested. You may also receive updates faster than // requested if other applications are requesting location at a faster interval. mLocationRequest.setInterval (10000); // Sets the fastest rate for active location updates. This interval is exact, and your // application will never receive updates faster than this value. mLocationRequest.setFastestInterval (5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } @Override public void onStart () {
super. onStart (); mGoogleApiClient.connect (); } @Override public void onStop () { super. onStop (); mGoogleApiClient. disconnect (); } @Override public void onResume () { super. onResume (); // Within {@code onPause ()}, we pause location updates, but leave the // connection to GoogleApiClient intact. Here, we resume receiving // location updates if the user has requested them. if (mGoogleApiClient.isConnected ()) { startLocationUpdates (); } } @Override protected void onPause () { super. onPause (); // Stop location updates to save battery, but don’t disconnect the GoogleApiClient object.
if (mGoogleApiClient.isConnected ()) { stopLocationUpdates (); } } protected void startLocationUpdates () { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { return; } LocationServices.FusedLocationApi.requestLocationUpdates ( mGoogleApiClient, mLocationRequest, this); } /** * Removes location updates from the FusedLocationApi. */ protected void stopLocationUpdates () { // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. // The final argument to {@code requestLocationUpdates ()} is a LocationListener
// (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html). LocationServices.FusedLocationApi.removeLocationUpdates (mGoogleApiClient, this); } @Override public void onConnected (@Nullable Bundle bundle) { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult (int requestCode, String [] permissions, // int [] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation (mGoogleApiClient); if (mLastLocation!= null) { mCurrentLocation = mLastLocation; String lat = String.valueOf(mCurrentLocation.getLatitude ()); String lon = String.valueOf(mCurrentLocation.getLongitude ()); Toast toast = Toast.makeText (this, «Last location» + lat + " " + lon, Toast. LENGTH_LONG);
toast.show (); mWorld.clearWorld (); mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation); mBeyondarFragment.setWorld (mWorld); } else { startLocationUpdates (); } } @Override public void onConnectionSuspended (int i) { mGoogleApiClient.connect (); } @Override public void onConnectionFailed (@NonNull ConnectionResult connectionResult) { } @Override public void onLocationChanged (Location location) { mCurrentLocation = location; String lat = String.valueOf(mCurrentLocation.getLatitude ()); String lon = String.valueOf(mCurrentLocation.getLongitude ()); Toast toast = Toast.makeText (this,«Current location " + lat+" "+lon, Toast. LENGTH_LONG); toast.show ();
mWorld.clearWorld (); mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation); mBeyondarFragment.setWorld (mWorld); } } Теперь дополненная реальность будет привязана к текущему местоположению пользователя. В качестве примера использования фреймворка BeyondAR создадим игровое приложение Creatures in Camera, в котором пользователь сможет расставлять 2D объекты в реальном мире, а потом наблюдать их через камеру. Создадим новый проект в Android Studio, используя шаблон Navigation Drawer Activity. Для сборки APK файла с большим количеством методов в коде, в Gradle файл добавим: defaultConfig { multiDexEnabled true } dependencies { compile 'com.android.support: multidex:1.0.0» } android { dexOptions { javaMaxHeapSize «4g» } } В файл манифеста добавим: <application
android:name="android.support.multidex.MultiDexApplication»> Добавим зависимость от библиотек beyondar-googlemap-plugin-v0.9.0.jar, beyondar-radarplugin-v0.9.1.jar и beyondar-v0.9.3.jar, скопировав соответствующие файлы в папку libs проекта. Добавим зависимость от библиотеки Google Play Services. compile 'com.google.android.gms: play-services:9.6.1» Добавим необходимые разрешения в файл манифеста. <! – Google maps stuff – > <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE» /> <uses-permission android:name="android.permission. WRITE_EXTERNAL_STORAGE» /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES» /> <! – Minimum permissions for BeyondAR – > <uses-permission android:name="android.permission.CAMERA» /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION» /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION» /> <! – For BeyondAR this is not mandatory unless you want to load something from the network – > <uses-permission android:name="android.permission.INTERNET» /> <! – BeyondAR needs the following features – > <uses-feature android:name="android.hardware.camera» /> <uses-feature android:name="android.hardware.sensor.accelerometer» /> <uses-feature android:name="android.hardware.sensor.compass» /> Для использования Google Map добавим Google API Key в файл манифеста. Для того получим ключ в Google Developers Console и добавим в тег <application> файла манифеста. <meta-data
android:name="com.google.android.geo. API_KEY» android: value=«AIzaSyBcRu9Vvb7…» /> Изменим файл компоновки content_main. xml. <?xml version=«1.0» encoding=«utf-8»? > <android.support.v4.widget.NestedScrollView xmlns: android="http://schemas.android.com/apk/res/android" xmlns: app="http://schemas.android.com/apk/res-auto" xmlns: tools="http://schemas.android.com/tools" android: layout_width=«match_parent» android: layout_height=«match_parent» android: paddingLeft="@dimen/activity_horizontal_margin» android: paddingRight="@dimen/activity_horizontal_margin» android: paddingTop="@dimen/activity_vertical_margin» android: paddingBottom="@dimen/activity_vertical_margin» android: fillViewport=«true» android: layout_gravity=«fill_vertical» app: layout_behavior="@string/appbar_scrolling_view_behavior» tools:context=".MainActivity» tools: showIn="@layout/app_bar_main» android: id="@+id/content_main» > <RelativeLayout android: layout_width=«match_parent» android: layout_height=«match_parent»>
<fragment android: id="@+id/beyondarFragment» android:name="com.beyondar.android.fragment.BeyondarFragmentSupport» android: layout_width=«match_parent» android: layout_height=«match_parent» /> </RelativeLayout> </android.support.v4.widget.NestedScrollView> Изменим код класса главной активности. package com.tmsoftstudio.aryourworld; import android. app. Dialog; import android.content. DialogInterface; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android. os. Bundle;; import android.support.design. widget. FloatingActionButton; import android.support. v4.app. DialogFragment; import android.support.v4.widget.NestedScrollView; import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat;
import android.support. v4.widget. DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support. v7.app. AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.Manifest; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; import android. location. Location; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.ProgressBar; import android. widget. RadioButton; import android. widget. RadioGroup; import android.widget.Toast; import com.beyondar.android.fragment.BeyondarFragmentSupport; import com.beyondar.android.plugin. radar. RadarView; import com.beyondar.android.plugin. radar. RadarWorldPlugin;
import com.beyondar.android.sensor.BeyondarSensorListener; import com.beyondar.android.sensor.BeyondarSensorManager; import com.beyondar.android. world. World; import com.beyondar.android. opengl. util. LowPassFilter; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common. api. GoogleApiClient; import com.google.android.gms. location. LocationListener; import com.google.android.gms. location. LocationRequest; import com.google.android.gms. location. LocationServices; import org. json. JSONArray; import org. json. JSONObject; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; public class MainActivity extends AppCompatActivity implements NavigationView. OnNavigationItemSelectedListener, BeyondarSensorListener, LocationListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient. OnConnectionFailedListener { private BeyondarFragmentSupport mBeyondarFragment; private World mWorld; private RadarView mRadarView; private RadarWorldPlugin mRadarPlugin; private Location mCurrentLocation;
private Context context; GoogleApiClient mGoogleApiClient; LocationRequest mLocationRequest; private float [] mLastAccelerometer = new float [3]; private float [] mLastMagnetometer = new float [3]; private float [] mR = new float [9]; private float [] mOrientation = new float [3]; private static boolean flagLocationUpdate=true; private static SharedPreferences mSettings; private Set <String> boLat=new LinkedHashSet (); private Set <String> boLon=new LinkedHashSet (); private static ProgressBar spinner; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar (toolbar); spinner = (ProgressBar)findViewById(R.id.progressBar); spinner.setVisibility (View. GONE);
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle ( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener (toggle); toggle.syncState (); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener (this); final NestedScrollView nestedScrollView = (NestedScrollView)findViewById(R.id.content_main); nestedScrollView.getViewTreeObserver().addOnGlobalLayoutListener ( new ViewTreeObserver. OnGlobalLayoutListener () { @Override public void onGlobalLayout () { int height = nestedScrollView.getHeight (); int width = nestedScrollView.getWidth (); if (height> width) height=width; if (width> height) width=height; ViewGroup.LayoutParams params = nestedScrollView.getLayoutParams (); params. width=width; params. height=height; nestedScrollView.setLayoutParams (params); nestedScrollView.getViewTreeObserver().removeGlobalOnLayoutListener (this); } });
context = this; mSettings = getSharedPreferences («APP_PREFERENCES», Context.MODE_PRIVATE); if (!mSettings.contains («BOLAT»)) { SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLAT», boLat); editor.commit (); } if(!mSettings.contains («BOLON»)) { SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLON», boLon); editor.commit (); } if(!mSettings.contains («CREATURES»)) { JSONArray creatures = new JSONArray (); SharedPreferences. Editor editor = mSettings. edit (); editor.putString("CREATURES",creatures.toString ()); editor.commit (); } if(!mSettings.contains («USERLON»)) { SharedPreferences. Editor editor = mSettings. edit (); editor. putString («USERLON», «82.9346»);
editor.commit (); } if(!mSettings.contains («USERLAT»)) { SharedPreferences. Editor editor = mSettings. edit (); editor. putString («USERLAT», «55.0415»); editor.commit (); } checkPermissions (); mBeyondarFragment = (BeyondarFragmentSupport) getSupportFragmentManager().findFragmentById(R.id.beyondarFragment); mRadarView = (RadarView) findViewById(R.id.radarView); mRadarPlugin = new RadarWorldPlugin (this); mRadarPlugin.setRadarView (mRadarView); mRadarPlugin.setMaxDistance (100); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (this); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); LowPassFilter.ALPHA = 0.001f; BeyondarSensorManager.registerSensorListener (this); mBeyondarFragment.setMaxDistanceToRender (10);
FloatingActionButton fabAdd = (FloatingActionButton) findViewById(R.id.fabAdd); fabAdd.setOnClickListener (new View. OnClickListener () { @Override public void onClick (View view) { SelectCreatureDialogFragment dialog = new SelectCreatureDialogFragment (); dialog.show (getSupportFragmentManager (), «SelectCreatureDialogFragment»); } }); FloatingActionButton fabUpdate = (FloatingActionButton) findViewById(R.id.fabUpdate); fabUpdate.setOnClickListener (new View. OnClickListener () { @Override public void onClick (View view) { startLocationUpdates (); } }); buildGoogleApiClient (); } protected void checkPermissions () { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED
||ActivityCompat.checkSelfPermission (this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions (this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.CAMERA }, 0); } } /** * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the * LocationServices API. */ protected synchronized void buildGoogleApiClient () { mGoogleApiClient = new GoogleApiClient. Builder (this) .addConnectionCallbacks (this) .addOnConnectionFailedListener (this) .addApi (LocationServices. API) .build (); createLocationRequest (); } protected void createLocationRequest () { mLocationRequest = LocationRequest.create (); mLocationRequest.setInterval (10000);
mLocationRequest.setFastestInterval (5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } @Override public void onStart () { super. onStart (); mGoogleApiClient.connect (); } @Override public void onStop () { super. onStop (); if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { return; } mGoogleApiClient. disconnect (); } @Override public void onResume () { super. onResume (); if (mGoogleApiClient.isConnected ()) {
startLocationUpdates (); } } @Override protected void onPause () { super. onPause (); if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { return; } if (mGoogleApiClient.isConnected ()) { stopLocationUpdates (); } } protected void stopLocationUpdates () { LocationServices.FusedLocationApi.removeLocationUpdates (mGoogleApiClient, this); } protected void startLocationUpdates () { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
return; } if (!isOnline ()) { Toast.makeText (getApplicationContext (),«No internet connection",Toast.LENGTH_SHORT).show (); mCurrentLocation=LocationServices.FusedLocationApi.getLastLocation (mGoogleApiClient); if (flagLocationUpdate==false) { double RE = 6378.1; //Radius of the Earth double d = 0.005; //Distance in km double dist = d/RE; double brng = mOrientation [0]; double lat1 = Math.toRadians(mCurrentLocation.getLatitude ()); double lon1 = Math.toRadians(mCurrentLocation.getLongitude ()); double lat2 = Math.asin (Math.sin(lat1)*Math.cos (dist) + Math.cos(lat1)*Math.sin(dist)*Math.cos (brng)); double a = Math.atan2(Math.sin(brng)*Math.sin(dist)*Math.cos (lat1), Math.cos(dist)Math.sin(lat1)*Math.sin (lat2)); double lon2 = lon1 + a; lon2 = (lon2+3*Math. PI) % (2*Math. PI) – Math. PI; double lat=Math.toDegrees (lat2); double lon=Math.toDegrees (lon2);
boLat = mSettings.getStringSet («BOLAT», null); boLon = mSettings.getStringSet («BOLON», null); boLat.add(Double.toString (lat)); boLon.add(Double.toString (lon)); SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLAT», boLat); editor. putStringSet («BOLON», boLon); editor.putString("USERLAT",Double.toString(mCurrentLocation.getLatitude ())); editor.putString("USERLON",Double.toString(mCurrentLocation.getLongitude ())); editor.commit (); mWorld.clearWorld (); mWorld.removePlugin (mRadarPlugin); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (context); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); flagLocationUpdate=true; spinner.setVisibility (View. GONE); } else { SharedPreferences. Editor editor = mSettings. edit (); editor.putString("USERLAT",Double.toString(mCurrentLocation.getLatitude ())); editor.putString("USERLON",Double.toString(mCurrentLocation.getLongitude ()));
editor.commit (); mWorld.clearWorld (); mWorld.removePlugin (mRadarPlugin); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (context); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); } } else { LocationServices.FusedLocationApi.requestLocationUpdates (mGoogleApiClient, mLocationRequest, this); } } @Override public void onSensorChanged (float [] floats, SensorEvent event) { switch (event.sensor.getType ()) { case Sensor. TYPE_ACCELEROMETER: mLastAccelerometer = floats; break; case Sensor. TYPE_MAGNETIC_FIELD: mLastMagnetometer = floats; break; }
if (mLastAccelerometer == null || mLastMagnetometer == null) return; SensorManager.getRotationMatrix (mR, null, mLastAccelerometer, mLastMagnetometer); SensorManager.getOrientation (mR, mOrientation); } @Override public void onLocationChanged (Location location) { mCurrentLocation = location; if (flagLocationUpdate==false) { double RE = 6378.1; //Radius of the Earth double d = 0.005; //Distance in km double dist = d/RE; double brng = mOrientation [0]; double lat1 = Math.toRadians(mCurrentLocation.getLatitude ()); double lon1 = Math.toRadians(mCurrentLocation.getLongitude ()); double lat2 = Math.asin (Math.sin(lat1)*Math.cos (dist) + Math.cos(lat1)*Math.sin(dist)*Math.cos (brng)); double a = Math.atan2(Math.sin(brng)*Math.sin(dist)*Math.cos (lat1), Math.cos(dist)Math.sin(lat1)*Math.sin (lat2)); double lon2 = lon1 + a; lon2 = (lon2+3*Math. PI) % (2*Math. PI) – Math. PI;
double lat=Math.toDegrees (lat2); double lon=Math.toDegrees (lon2); boLat = mSettings.getStringSet («BOLAT», null); boLon = mSettings.getStringSet («BOLON», null); boLat.add(Double.toString (lat)); boLon.add(Double.toString (lon)); SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLAT», boLat); editor. putStringSet («BOLON», boLon); editor.putString("USERLAT",Double.toString(mCurrentLocation.getLatitude ())); editor.putString("USERLON",Double.toString(mCurrentLocation.getLongitude ())); editor.commit (); mWorld.clearWorld (); mWorld.removePlugin (mRadarPlugin); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (context); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); flagLocationUpdate=true; spinner.setVisibility (View. GONE); } else {
SharedPreferences. Editor editor = mSettings. edit (); editor.putString("USERLAT",Double.toString(mCurrentLocation.getLatitude ())); editor.putString("USERLON",Double.toString(mCurrentLocation.getLongitude ())); editor.commit (); mWorld.clearWorld (); mWorld.removePlugin (mRadarPlugin); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (context); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); } stopLocationUpdates (); } @Override public void onConnected (@Nullable Bundle bundle) { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { return; } startLocationUpdates (); } @Override
public void onConnectionSuspended (int i) { mGoogleApiClient.connect (); } @Override public void onConnectionFailed (@NonNull ConnectionResult connectionResult) { } @Override public void onBackPressed () { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super. onBackPressed (); } } /* @Override public boolean onCreateOptionsMenu (Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; }
@Override public boolean onOptionsItemSelected (MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest. xml. int id = item.getItemId (); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super. onOptionsItemSelected (item); } */ @SuppressWarnings («StatementWithEmptyBody») @Override public boolean onNavigationItemSelected (MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId (); if (id == R.id.nav_addCreature) { SelectCreatureDialogFragment dialog = new SelectCreatureDialogFragment (); dialog.show (getSupportFragmentManager (), «SelectCreatureDialogFragment»); }
if (id == R.id.nav_showMap) { Intent intent = new Intent (this, GoogleMapActivity.class); startActivity (intent); } if (id == R.id.nav_updateLocation) { startLocationUpdates (); } if (id == R.id.nav_clearWorld) { boLat=new LinkedHashSet (); boLon=new LinkedHashSet (); SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLAT», boLat); editor. putStringSet («BOLON», boLon); JSONArray arrCreatures = new JSONArray (); editor.putString("CREATURES",arrCreatures.toString ()); editor.commit (); mWorld.clearWorld (); mWorld.removePlugin (mRadarPlugin); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (context); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); }
if (id == R.id.nav_removeVisCreature) { if (mSettings.contains («BOLAT») && mSettings.contains («BOLON») && mSettings.contains («CREATURES») && mSettings.contains («BOLATVIS») && mSettings.contains («BOLONVIS») && mSettings.contains («CREATURESVIS»)) { try { boLat = mSettings.getStringSet («BOLAT», null); boLon = mSettings.getStringSet («BOLON», null); String creatures = mSettings.getString («CREATURES», null); JSONArray arrCreatures = new JSONArray (creatures); Set <String> boLatVis = mSettings.getStringSet («BOLATVIS», null); Set <String> boLonVis = mSettings.getStringSet («BOLONVIS», null); String creaturesVis = mSettings.getString («CREATURESVIS», null); JSONArray arrCreaturesVis = new JSONArray (creaturesVis); Iterator iteratorLatVis = boLatVis.iterator (); Iterator iteratorLonVis = boLonVis.iterator (); while (iteratorLatVis. hasNext ()) { String latVis = (String) iteratorLatVis.next (); String lonVis = (String) iteratorLonVis.next (); boLat.remove (latVis); boLon.remove (lonVis); } JSONArray arrTemp=new JSONArray (); for (int i = 0; i <arrCreaturesVis. length (); i++) {
JSONObject jsonVis = arrCreaturesVis.getJSONObject (i); String nameVis = jsonVis.getString («name»); for (int j = 0; j <arrCreatures. length (); j++) { JSONObject json = arrCreatures.getJSONObject (j); String name = json.getString («name»); if (!name. equals (nameVis)) { arrTemp.put(arrCreatures.get (j)); } } } SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLAT», boLat); editor. putStringSet («BOLON», boLon); editor. putString («CREATURES», arrTemp.toString ()); editor.commit (); mWorld.clearWorld (); mWorld.removePlugin (mRadarPlugin); CustomWorldHelper.setActivity (this); mWorld = CustomWorldHelper.generateObjects (context); mWorld.addPlugin (mRadarPlugin); mBeyondarFragment.setWorld (mWorld); } catch (Exception e) {
e.printStackTrace (); } } } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } public static class SelectCreatureDialogFragment extends DialogFragment { private View view; private AlertDialog alert; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ()); LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout. dialog_creature, null); builder.setView (view) .setPositiveButton(R.string. button_ok, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) { } })
.setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { SelectCreatureDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { /*Button btnPositive = alert.getButton (Dialog. BUTTON_POSITIVE); btnPositive.setTextSize(TypedValue.COMPLEX_UNIT_PX, 30.0f); btnPositive.setTextColor(Color.parseColor («#FFFFFF»)); btnPositive.setGravity (Gravity. RIGHT); Button btnNegative = alert.getButton (Dialog. BUTTON_NEGATIVE); btnNegative.setTextSize(TypedValue.COMPLEX_UNIT_PX, 30.0f); btnNegative.setTextColor(Color.parseColor («#FFFFFF»)); btnNegative.setGravity (Gravity. LEFT); */ } }); return alert; } @Override public void onStart () {
super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { Boolean wantToCloseDialog = false; ((MainActivity) getActivity ()).flagLocationUpdate=false; ((MainActivity)getActivity()).spinner.setVisibility(View.VISIBLE); RadioGroup radioButtonGroup = (RadioGroup) view.findViewById(R.id.radiogroup_creature); int radioButtonID = radioButtonGroup.getCheckedRadioButtonId (); RadioButton radioButton = (RadioButton) radioButtonGroup.findViewById (radioButtonID); String creature = radioButton.getText().toString (); try { if(!mSettings.contains («CREATURES»)) { JSONArray arrCreatures = new JSONArray (); SharedPreferences. Editor editor = mSettings. edit (); editor.putString("CREATURES",arrCreatures.toString ()); editor.commit (); } String creatures = ((MainActivity)getActivity()).mSettings.getString («CREATURES», null); JSONArray arr = new JSONArray (creatures); JSONObject json = new JSONObject (); json. put («id», arr. length ()); json. put («name», creature); arr. put (json);
SharedPreferences. Editor editor = mSettings. edit (); editor.putString("CREATURES",arr.toString ()); editor.commit (); } catch (Exception e) { e.printStackTrace (); } ((MainActivity)getActivity()).startLocationUpdates (); wantToCloseDialog = true; if (wantToCloseDialog) { alert. dismiss (); } } }); } } private boolean isOnline () { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = cm.getActiveNetworkInfo (); return netInfo!= null && netInfo.isConnectedOrConnecting (); } } Класс главной активности проекта расширяет класс android.support. v7.app. AppCompatActivity и реализует интерфейс android.support.design.widget.NavigationView. OnNavigationItemSelectedListener.
Класс AppCompatActivity здесь дает возможность использования методов setSupportActionBar (toolbar) определяет Toolbar как ActionBar и onBackPressed () вызывается системой при нажатии кнопки назад. Интерфейс NavigationView. OnNavigationItemSelectedListener дает метод onNavigationItemSelected (MenuItem item), вызываемый системой при выборе элемента navigation drawer view. Компоновка главной активности начинается с корневого элемента android.support. v4.widget. DrawerLayout. Это является обязательным условием для того, чтобы добавить в приложение панель Navigation Drawer, отображающую основные элементы навигации приложения по левому краю экрана. В методе onCreate класса главной активности загружается компоновка activity_main, обеспечивающая боковую выдвигающуюся панель меню NavigationView и основной контент, который формируется компоновкой app_bar_main. <?xml version=«1.0» encoding=«utf-8»? > http://schemas.android.com/apk/res/android"pport. v4.widget. DrawerLayout xmlns: android="<android.su xmlns: app="http://schemas.android.com/apk/res-auto" xmlns: tools="http://schemas.android.com/tools" android: id="@+id/drawer_layout» android: layout_width=«match_parent» android: layout_height=«match_parent» android: fitsSystemWindows=«true» tools: openDrawer=«start»> <include layout="@layout/app_bar_main» android: layout_width=«match_parent» android: layout_height=«match_parent» /> <android.support.design.widget.NavigationView android: id="@+id/nav_view»
android: layout_width=«wrap_content» android: layout_height=«match_parent» android: layout_gravity=«start» android: fitsSystemWindows=«true» app: headerLayout="@layout/nav_header_main» app: menu="@menu/activity_main_drawer» android: background="@color/nav_drawer_bg» app: itemTextColor="@color/item_drawer» app: itemBackground="@drawable/navigation_drawer_background» > <TextView android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_gravity=«bottom|center» android: maxLines=«10» android: id="@+id/creature_info» /> </android.support.design.widget.NavigationView> </android.support. v4.widget. DrawerLayout> Боковая панель NavigationView содержит заголовок, определяемый компоновкой nav_header_main, и меню activity_main_drawer. Кроме того, NavigationView содержит текстовый элемент TextView, который будет выводить информацию о текущем местоположении и количестве существ в реальном мире. Компоновка nav_header_main: <?xml version=«1.0» encoding=«utf-8»? > <LinearLayout xmlns: android="http://schemas.android.com/apk/res/android" xmlns: app="http://schemas.android.com/apk/res-auto"
android: layout_width=«match_parent» android: layout_height=«wrap_content» android: background="@drawable/side_nav_bar» android: gravity=«bottom» android: orientation=«vertical» android: paddingBottom="@dimen/activity_vertical_margin» android: paddingLeft="@dimen/activity_horizontal_margin» android: paddingRight="@dimen/activity_horizontal_margin» android: paddingTop="@dimen/activity_vertical_margin» android: theme="@style/ThemeOverlay. AppCompat. Dark»> <ImageView android: id="@+id/imageView» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: src="/storage/public/books/4d/0d/4d0da3ee-23a0-42f0-a4a502d62ab5e344/@drawable/logo» android: paddingTop="@dimen/activity_vertical_margin» /> </LinearLayout> Меню activity_main_drawer: <?xml version=«1.0» encoding=«utf-8»? > <menu xmlns: android="http://schemas.android.com/apk/res/android"> <group android: checkableBehavior=«single»> <item
android: id="@+id/nav_addCreature» android: icon="@drawable/add_image» android: title="@string/nav_addCreature» /> <item android: id="@+id/nav_updateLocation» android: icon="@drawable/worldwide_location» android: title="@string/nav_updateLocation» /> <item android: id="@+id/nav_clearWorld» android: icon="@drawable/broom» android: title="@string/nav_clearWorld» /> <item android: id="@+id/nav_removeVisCreature» android: icon="@drawable/delete» android: title="@string/nav_removeVisCreature» /> <item android: id="@+id/nav_showMap» android: icon="@drawable/world_map» android: title="@string/nav_showMap» /> </group> </menu> Корневой элемент CoordinatorLayout компоновки основного контента app_bar_main обеспечивает такие эффекты, как перемещение кнопки действия Floating Action Button вверх и вниз, чтобы освободить место для Snackbar, отображение и скрытие панели инструментов Toolbar и кнопки действия FAB при прокрутке списка содержания вниз и вверх.
Первый дочерний элемент CoordinatorLayout это элемент AppBarLayout, обеспечивающий, чтобы вложенный элемент Toolbar реагировал на прокрутку. Здесь Toolbar выступает как ActionBar. Для этого в манифесте AndroidManifest. xml стиль активности объявлен как android:theme="@style/AppTheme.NoActionBar» и в методе onCreate активности вызван метод setSupportActionBar (toolbar). Также компоновка основного контента app_bar_main содержит компоновку content_main фрагмента BeyondarFragmentSupport и компас RadarView, показывающий существа в радиусе 100 метров. <?xml version=«1.0» encoding=«utf-8»? > http://schemas.android.com/apk/res/android"ordinatorLayout xmlns: android="<android.support.design.widget.Co xmlns: app="http://schemas.android.com/apk/res-auto" xmlns: tools="http://schemas.android.com/tools" android: layout_width=«match_parent» android: layout_height=«match_parent» android: fitsSystemWindows=«true» tools:context="com.tmsoftstudio.aryourworld.MainActivity» > <android.support.design. widget. AppBarLayout android: layout_width=«match_parent» android: layout_height=«wrap_content» android: theme="@style/AppTheme. AppBarOverlay»> <android.support.v7.widget.Toolbar android: id="@+id/toolbar» android: layout_width=«match_parent» android: layout_height=»? attr/actionBarSize» android: background=»? attr/colorPrimary»
app: popupTheme="@style/AppTheme. PopupOverlay» app: layout_scrollFlags=«scroll|enterAlways» /> </android.support.design. widget. AppBarLayout> <include layout="@layout/content_main» /> <android.support.design. widget. FloatingActionButton android: id="@+id/fabAdd» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_gravity=«bottom|end» android: layout_margin="@dimen/fab_margin» app: srcCompat="@android: drawable/ic_input_add» /> <FrameLayout android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_gravity=«bottom|center» android: background="@drawable/radar_bg_small»> <com.beyondar.android.plugin. radar. RadarView android: id="@+id/radarView» android: layout_width=«wrap_content» android: layout_height=«wrap_content»
android: src="/storage/public/books/4d/0d/4d0da3ee-23a0-42f0-a4a502d62ab5e344/@drawable/radar_north_small» /> </FrameLayout> <android.support.design. widget. FloatingActionButton android: id="@+id/fabUpdate» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_gravity=«bottom|start» android: layout_margin="@dimen/fab_margin» app: srcCompat="@android: drawable/ic_menu_mylocation» /> <ProgressBar android: id="@+id/progressBar» style=»? android: attr/progressBarStyleLarge» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_gravity=«center» /> </android.support.design.widget.CoordinatorLayout> Чтобы определить связь между AppBarLayout и видом, который будет прокручиваться, нужно добавить атрибут app: layout_behavior к любому виду, способному содержать прокрутку, такому как NestedScrollView. Поэтому в файле компоновки content_main. xml элемент RelativeLayout обернут в элемент NestedScrollView. В методе onCreate класса главной активности объявляется индикатор ProgressBar, который будет отображаться при создании существа в реальном мире. В методе onCreate класса главной активности следующий код отвечает за появление в панели инструментов значка гамбургера, при нажатии на который выскакивает навигационный ящик: DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle (this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); toggle.syncState (); Вызов метода drawer.addDrawerListener (toggle) обеспечивает анимацию значка гамбургера при движении навигационного ящика. Чтобы вид камеры дополненной реальности имел равные высоту и ширину, в методе onCreate к элементу NestedScrollView компоновки content_main добавляется слушатель ViewTreeObserver. OnGlobalLayoutListener, в методе onGlobalLayout которого переопределяются параметры ViewGroup.LayoutParams компоновки. Файл SharedPreferences приложения будет хранить координаты BOLAT и BOLON существ, имена существ CREATURES, и координаты пользователя USERLON и USERLAT. Метод checkPermissions проверяет и в случае необходимости запрашивает критичные для работы приложения разрешения. Далее в методе onCreate создается мир дополненной реальности с использованием класса CustomWorldHelper и методом mBeyondarFragment.setMaxDistanceToRender (10) устанавливается видимость существ в камере в пределах 10 метров. Кнопка FloatingActionButton fabAdd показывает диалоговое окно выбора и добавления существа в реальный мир, а кнопка FloatingActionButton fabUpdate обновляет текущее местоположение пользователя. Метод buildGoogleApiClient создает объект GoogleApiClient сервиса локации. В методе startLocationUpdates в зависимости от того есть Интернет соединение или его нет, запрашивается обновление местоположения или используется последнее местоположение пользователя. При этом если флаг flagLocationUpdate установлен в false, это означает что создается новое существо, и вычисляются координаты нового существа, исходя из ориентации и координат устройства, на расстоянии 5 метров от пользователя. Меню боковой панели onNavigationItemSelected позволяет вызвать диалог создания нового существа, открыть активность Google карты, показывающей местоположений существ, обновить текущее местоположение, очистить мир от существ, удалить все существа в пределах 10 метров. Диалог SelectCreatureDialogFragment позволяет выбрать существо из папки drawable и добавить его в реальный мир. Класс CustomWorldHelper в методе generateObjects извлекает текущее местоположение, координаты и имена существ и создает мир дополненной реальности. package com.tmsoftstudio.aryourworld;
import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android. location. Location; import android. widget. TextView; import com.beyondar.android.util.math. Distance; import com.beyondar.android.world.GeoObject; import com.beyondar.android. world. World; import org. json. JSONArray; import org. json. JSONException; import org. json. JSONObject; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; public class CustomWorldHelper { public static World sharedWorld; private static SharedPreferences mSettings; private static Set <String> boLatVis=new LinkedHashSet (); private static Set <String> boLonVis=new LinkedHashSet (); private static JSONArray creaturesVis=new JSONArray (); private static String ceature_info; private static Activity activity;
public static void setActivity (Activity _activity) { activity=_activity; } public static World generateObjects (Context context) { mSettings = context.getSharedPreferences («APP_PREFERENCES», Context.MODE_PRIVATE); String userLat = mSettings.getString («USERLAT», «55.0415»); String userLon = mSettings.getString («USERLON», «82.9346»); Location location=new Location (»»); location.setLatitude(Double.parseDouble (userLat)); location .setLongitude(Double.parseDouble (userLon)); sharedWorld = new World (context); ceature_info=«Current location: " + String.format (»%.2f», location.getLatitude ()) + " " + String.format (»%.2f», location.getLongitude ()); sharedWorld.setLocation (location); if (mSettings.contains («BOLAT») && mSettings.contains("BOLON")&&mSettings.contains («CREATURES»)) { try { Set <String> boLat = mSettings.getStringSet («BOLAT», null); Set <String> boLon = mSettings.getStringSet («BOLON», null); String creatures = mSettings.getString («CREATURES», null); JSONArray arrCreatures = new JSONArray (creatures);
ceature_info=ceature_info+"\n“+„Creatures Amount: " + arrCreatures. length (); Iterator iteratorLat = boLat.iterator (); Iterator iteratorLon = boLon.iterator (); int j=0; for (int i = 0; i <arrCreatures. length (); i++) { JSONObject json = arrCreatures.getJSONObject (i); String nameCreature = json.getString («name»); double lat = Double.parseDouble ((String) iteratorLat.next ()); double lon = Double.parseDouble ((String) iteratorLon.next ()); // Create an object with an image in the app resources. int drawableResourceId = context.getResources().getIdentifier (nameCreature, «drawable», context.getPackageName ()); GeoObject go = new GeoObject (i); go.setGeoPosition (lat, lon); go.setImageResource (drawableResourceId); go.setName (nameCreature); // Add the GeoObjects to the world sharedWorld.addBeyondarObject (go); double goLon = go.getLongitude (); double goLat = go.getLatitude (); double distanceFromUser = Distance.calculateDistanceMeters (goLon, goLat, location.getLongitude (), location.getLatitude ()); if (distanceFromUser <= 100) { ceature_info=ceature_info+"\n»+«Distance to the\n»+ nameCreature +», meters: " + String.format (»%.2f», distanceFromUser);
} if (distanceFromUser <= 10) { boLatVis.add(Double.toString (goLat)); boLonVis.add(Double.toString (goLon)); JSONObject jsonVis = new JSONObject (); try { jsonVis. put («id», j); jsonVis. put («name», nameCreature); creaturesVis. put (json); } catch (JSONException e) { e.printStackTrace (); } j++; SharedPreferences. Editor editor = mSettings. edit (); editor. putStringSet («BOLATVIS», boLatVis); editor. putStringSet («BOLONVIS», boLonVis); editor.putString("CREATURESVIS",creaturesVis.toString ()); editor.commit (); } } } catch (Exception e) { e.printStackTrace (); } } if (activity!=null) { TextView txtView = (TextView) activity.findViewById(R.id.creature_info);
txtView.setText (ceature_info); } return sharedWorld; } } Активность GoogleMapActivity показывает текущее местоположение и расположение существ на карте. package com.tmsoftstudio.aryourworld; import android. os. Bundle; import android.support.v4.app.FragmentActivity; import android.widget.Toast; import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin; import com.beyondar.android.world.GeoObject; import com.beyondar.android. world. World; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps. GoogleMap; import com.google.android.gms.maps. GoogleMap. OnMarkerClickListener; import com.google.android.gms.maps. OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.Marker; public class GoogleMapActivity extends FragmentActivity implements OnMarkerClickListener, OnMapReadyCallback {
private GoogleMap mMap; private GoogleMapWorldPlugin mGoogleMapPlugin; private World mWorld; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.activity_google_map); ((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this); } @Override public boolean onMarkerClick (Marker marker) { GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker); if (geoObject!= null) { Toast.makeText (this, «Creature with the name: " + geoObject.getName (), Toast.LENGTH_SHORT).show (); } return false; } @Override
public void onMapReady (GoogleMap googleMap) { mMap=googleMap; // We create the world and fill the world mWorld = CustomWorldHelper.generateObjects (this); // As we want to use GoogleMaps, we are going to create the plugin and // attach it to the World mGoogleMapPlugin = new GoogleMapWorldPlugin (this); // Then we need to set the map in to the GoogleMapPlugin mGoogleMapPlugin.setGoogleMap (mMap); // Now that we have the plugin created let’s add it to our world. // NOTE: It is better to load the plugins before start adding object in to the world. mWorld.addPlugin (mGoogleMapPlugin); mMap.setOnMarkerClickListener (this); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15)); mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null); // Lets add the user position GeoObject user = new GeoObject (1000l); user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ()); user.setImageResource (R. drawable. flag); user.setName («User position»); mWorld.addBeyondarObject (user);
} } Скачать готовое приложение можно по адресу https://play.google.com/store/apps/details?id=com.tmsoftstudio.aryourworld. Beyond Reality Face Проект Beyond Reality Face Nxt (https://www.beyond-reality-face.com/overview) предоставляет набор SDK для создания веб, мобильных и настольных приложений, отслеживающих человеческое лицо, с использованием Actionscript или HTML5/Javascript. Nxt обеспечивает определение человеческого лица на изображении или в вебкамере и возвращает прямоугольник, маркирующий найденное лицо. Затем Nxt проводит анализ лица с использованием индивидуальных характерных точек, обеспечивая доступ к контуру глаз, носа, рта и лица. В дальнейшем Nxt отслеживает движение головы человека, позволяя прикреплять виртуальные объекты или обрабатывать моргание и движения рта. Для начала работы с Nxt скачаем BRF SWC SDK (http://www.tastenkunst.com/#/brf/download), среду разработки FDT (the flexible development toolkit http://fdt.powerflasher.com/buydownload/) и Flex SDK (http://flex.apache.org/installer.html). Импортируем в среду FDT проект BRF_NXT_AS3_EXAMPLES. Установим для проекта Flex SDK.
Скопируем пакет away3d. textures из папки src_3dengine_extensions в папку src. Нажмем правой кнопкой мышки на файле ExamplePointTracking папки examples и выберем Run As> Run Configurations. Выберем Flex Compiler.
Нажмем Apply и закроем конфигурацию. Нажмем правой кнопкой мышки на файле ExamplePointTracking папки examples и выберем Run As> FDT SWF Application. При этом в папке bin появится откомпилированный файл app. swf. Скачаем и установим Python (https://www.python.org/downloads/). В командной строке наберем: cd C:\Users\user python -m SimpleHTTPServer 8080 В браузере откроем адрес http://127.0.0.1:8080/ и откроем папку BRF_NXT_AS3_EXAMPLES/bin/. В результате увидим работу SWF файла. Аналогично можно посмотреть работу остальных примеров. Для развертывания примеров на Android устройстве скачаем BRF ANE SDK Adobe AIR for Mobile (iOS/Android) (http://www.tastenkunst.com/#/brf/download). Импортируем проект BRF_NXT_ANE_EXAMPLES в среду FDT. Установим для проекта Flex SDK.
Откроем свойства проекта и удалим, а затем снова добавим библиотеку BRF_NXT_ANE_TK060716_v3.0.16_trial.ane. Скопируем пакет away3d. textures из папки src_3dengine_extensions в папку src. Откроем Android свойства проекта.
Создадим сертификат для подписи APK файла. Выберем пример и откроем Run Configurations.
Кнопкой Browse выберем пример. Выберем Flex Compiler.
Установим аргумент компиляции —swf-version=33. Подсоединим устройство к компьютеру. Нажмем кнопку Apply и Run. В результате пример будет запущен на Android устройстве.
Далее можно экспортировать готовый для публикации APK файл, предварительно отредактировав Android свойства. VISION SDK VISION SDK (http://www.vision-sdk.com/index.php) это полностью настраиваемая, легкая в использовании библиотека дополненной реальности, позволяющая включать дополненную реальность в любое приложение для iOS или Android устройств, при этом, не являясь специалистом в языке программирования. С VISION SDK, любое мобильное приложение можно обогатить благодаря возможностям, которые предоставляет новая технология, объединяющая цифровую информацию с окружающим нас миром естественным образом. VISION SDK предоставляет набор методов, позволяющих разработчикам создавать AR приложения, благодаря фреймворку в качестве основы для разработки приложений дополненной реальности в быстрой и эффективной манере, исключающей риск ошибок, поскольку фреймворк обеспечивает стабильный и проверенный код. Кроме того, фреймворк предоставляет средства, необходимые для настройки производительности и AR интерфейса. Дополненная реальность с VISION SDK основана на изображениях, сделанных камерой смартфона или планшета, акселерометре, компасе и GPS для определения координат устройства и цифровой (виртуальной) информации на реальном изображении. Приложение с VISION SDK распознает окружение пользователя и показывает расположение различных POI объектов вокруг. Эти точки интереса или POI создаются разработчиком. Каждый POI объект имеет координаты для привязки местоположения, а также дополнительную информацию в виде текста, изображения, гиперссылки или видео. Объекты POI сгруппированы по категориям и подкатегориям, которые позволяют сортировать их по критериям поиска, обеспечивая отображение уровней информации на основе поиска. Архитектура VISION SDK для Android OS:
Архитектура VISION SDK основана на четырех основных классах: VisionCore – контейнер для основных модулей и служит точкой доступа к основным функциям Дополненной Реальности, а также обеспечивает доступ к модулям контроля (исходные данные, параметры и т. д.). VisionConfiguration – менеджер хранения значений (константы и переменные), которые определяют параметры VISION SDK. Эти параметры считываются всеми остальными модулями, чтобы обеспечить персонализацию дополненной реальности VISION SDK. VisionModelManager – управление Моделью Данных, который используется VISION SDK для отображения деталей дополненной реальности в представлении приложения. VisionARManager – основной модуль, отвечающий за поведение представления дополненной реальности. С помощь этого модуля устанавливается компоновка представлений дополненной реальности, которые являются видимыми для пользователя, а также внешний вид каждого из них (состав меню, действия для кнопок, сообщение о состоянии и др.). Кроме того, этот модуль обеспечивает через свой интерфейс действия, необходимые для выполнений изменений текущего представления, таких как отображение и скрытие меню. VISION SDK основан на модели расширения путем наследования, где фреймворк предоставляет функциональные возможности и стандартные поведения, которые могут быть расширены в трех из четырех основных объектов, VisionConfiguration, VisionModelManager и VisionARManager с помощью создания новых классов: MyConfiguration – расширяет VisionConfiguration, адаптируя объект к собственным настройкам нового приложения, добавляя при расширении этого класса новые параметры для хранения. MyModelManager – расширяет VisionModelManager, адаптируя объект к источникам данных для нового приложения. При создании пользовательского VisionModelManager класса, вы можете установить новую реализацию для методов, которые выполняют загрузку данных для дополненной реальности.
MyARManager – расширяет VisionARManager, адаптируя объект к новым требованиям к работе VISION SDK представлений в приложении. Путем расширения VisionARManager можно персонализировать внешний вид и доступ к представлениям из нового приложения дополненной реальности. Для представления информации в дополненной реальности видение VISION SDK основывается на простой модели данных с использованием двух основных элементов: Точки интереса (POI) – это основной информационный блок. Это представление местоположения через его координаты, содержащих информацию, связанную с этим местом, такую как тексты, изображения, URL-адреса и видео. Категория – это группа или набор POI объектов, имеющих одинаковые характеристики. Они могут быть разделены по иерархически структурированным категориям. Категории могут иметь детей, которые являются подкатегориями или POI. Так же, точки интереса POI могут принадлежать к нескольким категориям. Весь фреймворк VISION SDK основан на структуре, определенной в Модели Данных. Поэтому, прежде чем приступить к разработке приложения, необходимо определить, какие элементы приложения будут коррелировать с категориями, подкатегориями и точками интересов POI. Точки интереса или POI являются основными элементами информации, которые пользователь может просматривать в представлении дополненной реальности. Точки интереса представляют собой места, привязанные географически с помощью координат, и содержат всю информацию, касающуюся данного места, внося сведения, представляющие интерес для пользователя (например, имя, физический адрес, номер телефона, сайт, электронная почта, описание, картинки, видео и т. д.), для ресторана, остановки автобуса, заправки или банка.
Каждая категория состоит из набора Точек Интереса и определенным отношениям между ними. Категория может, в свою очередь, состоять из нескольких подкатегорий, которые представляют более узкие группы Точек Интереса, образуя иерархическое дерево. Примерами категорий могут быть жилые помещения, рестораны, заправочные станции, транспортные узлы и т. д., и примерами подкатегорий могут быть отели и апартаменты, как подкатегории помещения.
View является представлением информации о ближайших объектах POI. VISION SDK предлагает 5 различных представлений, которые можно активировать или деактивировать в зависимости от потребностей разработчика: Panel View – полностью соответствует концепции дополненной реальности. «Панели» накладываются на изображение с камеры, показывая расположение и расстояние от ближайших объектов POI. В верхней части экрана появляется изображение радара POI объектов. При выборе панели, пользователь получает доступ к информации, связанной с данной инфраструктурой.
Arrows View – это вид сверху (камера направлена в сторону земли). Он представляет точки интереса графически как «стрелы», направление которых указывает на каждый POI объект. Когда пользователь касается одной из «стрелы», она будет отображаться в центре экрана с большими размерами, как руководство для достижения выбранной POI. Когда пользователь снова касается «стрелы», он получает доступ к информации, связанной с POI.
Radar View – показывает местоположение каждого POI объекта в виде значка на представлении «радар». Как только пользователь нажмет на одном из этих значков, значок будет выделен и некоторая основная информация отобразится в верхнем баннере. Когда пользователь коснется группы близких точек, то они будут развернуты, чтобы облегчить выбор нужного элемента. Наконец, когда пользователь нажмет на верхнем баннере, отобразится информация, связанная с выбранной POI.
Map View – показывает POI объекты в виде значков на Google карте. При нажатии на значок, появляется всплывающее окно с основной. Выбрав POI, пользователь получает доступ к информации, связанной с данной инфраструктурой.
List View – состоит из представления ближайших POI объектов в виде списока. Этот список обновляется автоматически по мере изменения положения пользователя. Выбрав элемент списка, пользователь получает доступ к информации, связанной с данной инфраструктурой.
Для начала работы с VISION SDK скачайте и распакуйте SDK и Demo. Откройте среду Eclipse с ADT плагином. С помощью команды «File» -> «Import» -> «Android» -> «Existing Android Code into Workspace» импортируем библиотеку google_play_services_lib. Импортируем SDK каталог vision_sdk_library.
Импортируем проект VisionDemo. После исправления ошибок в свойствах проекта VisionDemo, подсоединим Android устройство и нажмем правой кнопкой мышки и выберем Run As -> Android Application. В результате будет запущен пример VISION SDK.
Для доступа к основным функциям управления модуля дополненной реальности нужно создать экземпляр класса VisionCore. public VisionCore (Context context, String packageName, boolean defaultInit)
VisionCore vs = new VisionCore(this.getApplicationContext (), true); Класс VisionCore включает в себя все компоненты, составляющие фреймворк, и предоставляет доступ к представлениям дополненной реальности. Параметр defaultInit указывает, должны ли устанавливаться стандартные классы ядра дополненной реальности или будут назначены пользовательские классы, расширяющие стандартные классы. Для проверки возможности получения местоположения, можно использовать GPS менеджер: VisionGPSManager.isGPSAvailable (context) Для создания точек интереса используются классы VisionGeoPoi, VisionCategory, VisionImage. VisionImage iconCat1 = new VisionImage (); iconCat1.setImageURL («icon2»); VisionImage iconCat2 = new VisionImage (); iconCat2.setImageURL («cs2003»); VisionImage iconCat3 = new VisionImage (); iconCat3.setImageURL («cns2003»); VisionCategory turistCategory = new VisionCategory (); turistCategory.setTitle («Tourist places»); turistCategory.setIcon (iconCat1); turistCategory.setSelectedIcon (iconCat2); turistCategory.setNoSelectedIcon (iconCat3); VisionCore.getCategories().add (turistCategory); VisionGeoPoi poi = new VisionGeoPoi (); poi.setId («0001»); poi.setTitle («Puerta del Sol»); poi.setSubtitle («Puerta del Sol 1, Madrid»); poi.setLatitude (40.41687);
poi.setLongitude (-3.703412); http://en.wikipedia.org/wiki/Puerta_del_Sol");tWeb (»poi.se poi.getCategories().add (turistCategory); VisionCore.core.model.getPois().add (poi); Загрузка категорий и точек интереса осуществляется с помощью вызова метода loadData. public void loadData (Context c) VisionCore.core.loadData(MainActivity.this); Этот метод должен вызываться из отдельного потока (Thread, AsyncTask) и является основным методом добавления данных в приложение. Для запуска дополненной реальности VISION SDK используется метод startAR. public static void startAR (Context c) VisionCore.startAR(MainActivity.this); Класс VisionConfiguration представляет собой набор свойств, позволяющий настраивать следующие параметры фреймворка напрямую: Language – код языка, который будет использоваться VISION SDK для поиска файлов ресурсов. По умолчанию это английский язык. Update_distance – расстояние (в метрах) для обновления данных. По умолчанию, 75 метров. Max_pois – максимальное количество POI, которые будут отображаться одновременно на экране. По умолчанию 15 объектов. Reorder_distance – расстояние (в метрах) для повторного вычисления угла и расстояния отображаемых POI. По умолчанию 15 метров. RadarPosition – указывает, где размещается мини-радар дополненной реальности. Допустимые значения этого параметра: RADAR_POSITION_LEFT (слева) и RADAR_POSITION_RIGHT (справа). ShowAppLogo – указывает отображение логотипа проекта “vision_logo.png». По умолчанию отображение отключено. Для обработки нажатия на POI можно использовать слушатель VisionGeoPoiClickListener. VisionCore.core.model.setVisionGeoPoiClickListener (new VisionGeoPoiClickListener () { @Override public void onVisionGeoPoiClick (VisionGeoPoi poi, Activity act) {
Toast.makeText (act, "< – - POI: "+poi.getTitle () +" – ->», Toast.LENGTH_SHORT).show (); } }); VISION SDK использует набор XML-файлов папки raw в качестве источника данных по умолчанию. Информация, содержащаяся в этих файлах, представляет категории и точки интереса. Можно менять префиксы этих файлов. VisionCore.core.model.categoriesFrom = «file_categories»; VisionCore.core.model. poisFrom = «file_pois»; Для работы Google Map необходимо получить API key и вставить его в файл манифеста. <meta-data android:name="com.google.android.geo. API_KEY» android: value=«AIzaSyBcRu9Vvb…» /> Для того чтобы убрать водяной знак VISION SDK нужно добавить в файл ресурсов приложения: <string name=«vision_key»> 21312… </string> Чтобы получить значение ключа добавим код: public static String getKey () { Object object; String string = VisionCore.core.appContext.getApplicationContext().getPackageName (); string = new StringBuffer(string).reverse().toString (); String string2 = «»; int n = 0; while (n <string. length ()) { string2 = String.valueOf (string2) + string.charAt (n); n += 2; } n = 1; while (n <string. length ()) {
string2 = String.valueOf (string2) + string.charAt (n); n += 2; } byte [] arrby = null; try { object = MessageDigest.getInstance («SHA-1»); arrby = ((MessageDigest)object).digest(string2.getBytes ()); } catch (NoSuchAlgorithmException var4_6) { var4_6.printStackTrace (); } object = «»; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (arrby); int n2 = byteArrayInputStream.read (); while (n2!= -1) { String string3 = Integer.toHexString (n2); if (string3.length () <2) { object = String.valueOf (object) + «0»; } object = String.valueOf (object) + string3; n2 = byteArrayInputStream.read (); } return object.toString (); }
public void writeToFile (String data) { // Get the directory for the user’s public pictures directory. final File path = Environment.getExternalStoragePublicDirectory ( //Environment. DIRECTORY_PICTURES Environment. DIRECTORY_DCIM + "/ar/» ); // Make sure the path directory exists. if (!path. exists ()) { // Make it, if it doesn’t exit path.mkdirs (); } final File file = new File (path, «key. txt»); // Save your stream, don’t forget to flush () it before closing it. try { file.createNewFile (); FileOutputStream fOut = new FileOutputStream (file); OutputStreamWriter myOutWriter = new OutputStreamWriter (fOut);
myOutWriter. append (data); myOutWriter.close (); fOut. flush (); fOut.close (); } catch (IOException e) { Log. e («Exception», «File write failed: " + e.toString ()); } } writeToFile(MainActivity.getKey ()); Как уже говорилось выше, точка входа в фреймворк это класс VisionCore. Этот класс включает в себя экземпляры реализации основных модулей: менеджера конфигурации, менеджера представления и менеджера данных. Для расширения фреймворка можно создать класс, который наследует от каждого из этих модулей, и включить этот новый экземпляр в класс VisionCore во время выполнения. VisionCore vs = new VisionCore(this.getApplicationContext (), false); VisionCore.core.ar=new MyARManager (…); VisionCore.core.model=new MyModelManager (…); VisionCore.core.configuration=new VisionConfiguration (…); Компонент VisionModelManager отвечает за управление Моделью Данных приложения. В этот класс включены необходимые методы для добавления и обработки данных, которые необходимо представлять с помощью дополненной реальности. VisionModelManager управляет тремя структурами данных: списком категорий, общим списком «загруженные» POI и списком «ближайшие видимые» POI. Источник данных можно настроить двумя способами: используя менеджер ПРЕДВАРИТЕЛЬНОЙ загрузки данных (по умолчанию) и менеджер НЕПРЕРЫВНОЙ загрузки данных. Менеджер ПРЕДВАРИТЕЛЬНОЙ загрузки загружает категории и точки интереса до начала представления дополненной реальности. Далее менеджер отвечает только за ведение списка
близлежащих точек интереса, который он будет обновлять при изменении местоположения пользователя на определенное в VisionConfiguration количество метров. Менеджер НЕПРЕРЫВНОЙ загрузки обновляет общий список точек интереса непрерывно, как правило, из внешнего источника данных. За загрузку категорий и точек интереса отвечают методы loadCategories и loadPois класса VisionModelManager, использующие для загрузки из XML файлов парсеры VisionCategoryParser и VisionGeoPoiParser. Метод loadData класса VisionModelManager вызывает методы loadCategories и loadPois. Метод generateLoadCategoriesURL класса VisionModelManager генерирует URL-адрес XMLфайла, который хранит категории для приложения. Этот URL-адрес может быть локальным или Интернет-ресурсом. Реализация по умолчанию генерирует URL-адрес XML файла categories_xx. xml, где xx соответствует текущему языку приложения. Метод generateLoadPoisURL класса VisionModelManager генерирует URL-адрес XML-файла, в котором хранятся точки интереса для приложения. Этот URL-адрес может быть локальным или Интернет-ресурсом. Реализация по умолчанию генерирует URL-адрес XML файла pois_xx. xml, где xx соответствует текущему языку приложения. @Override public String generateLoadPoisURL () { String tmp=«my_custom_pois_" + VisionCore.core.configuration.getLanguage (); http://"))||(tmp.startsWith("https://")))artsWith (»if((tmp.st tmp=tmp+». xml»; return tmp; } @Override public String generateLoadCategoriesURL () { String tmp=«my_custom_categories_" + VisionCore.core.configuration.getLanguage (); http://"))||(tmp.startsWith("https://")))artsWith (»if((tmp.st tmp=tmp+». xml»; return tmp; }
Для настройки менеджера непрерывной загрузки необходимо задать значение true свойству loadPoisContinously VisionModelManager и определить новую реализацию методов generateLoadPoisURL и generateLoadCategoriesURL, которые получают POI для загрузки. При этом реализация методов loadPois и loadCategories не изменится, при условии, что данные, возвращаемые внешней службой, поддерживают XML-структуру VISION SDK. public Boolean loadPoisContinouosly=true; @Override public String generateLoadPoisURL () { Location l=this.getLocation (); String tmp=generatePoisURL (l) //Generated String using location and language returntmp; } @Override public String generateLoadCategoriesURL () { Location l=this.getLocation (); String tmp=generateCategoriesURL (l) //Generated String using location and language returntmp; } VISION SDK обеспечивает по умолчанию два класса, которые представляют модель данных – VisionCategory и VisionGeoPoi, в которые путем расширения можно добавить новые поля. Public class CustomGeoPoi extends VisionGeoPoi { private String poiRating; } Также необходимо создать новый парсер, который сможет обрабатывать новые поля данных. Для этого нужно создать новый класс, который наследует от VisionGeoPoiParser и определить реализацию дополнительных метода, которые будут обнаруживать новые элементы, найденные в XML.
publicclass CustomGeoPoiParser extends VisionGeoPoiParser { public CustomGeoPoiParser (Context ctx, String f) { super (ctx, f); } public CustomGeoPoiParser (Context ctx, String f, List <VisionCategory> categories) { super (ctx, f, categories); } @Override publicvoid parse () { try { // Get document root InputStream in; if http://"))artsWith (»(from.st { in = VisionUtils.getInputFromURL (from); } else { in = VisionUtils.getInputFromRawFile (from, context); } /* Get a SAXParser from the SAXPArserFactory. */ SAXParserFactory spf = SAXParserFactory.newInstance (); SAXParser sp = spf.newSAXParser (); /* Get the XMLReader of the SAXParser we created. */
XMLReader xr = sp.getXMLReader (); /* Create a new ContentHandler and apply it to the XML-Reader */ CustomGeoPoiHandler myExampleHandler = newCustomGeoPoiHandler (); xr.setContentHandler (myExampleHandler); /* Parse the xml-data from our URL. */ xr.parse (new InputSource (in)); /* Parsing has finished. */ } catch (Exception e) { e.printStackTrace (); } } // parse privateclass CustomGeoPoiHandler extends VisionGeoPoiHandler { CustomGeoPoi p; @Override publicvoid startDocument () throws SAXException { super.startDocument (); } @Override publicvoid endDocument () throws SAXException { super. endDocument (); }
@Override publicvoid startElement (String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { if (localName. equals (POI)) { p = newCustomGeoPoi (); try { p.setId (getStringAttribute (atts, «poiIDStr»)); p.setLatitude(Double.parseDouble (getStringAttribute (atts, «poiLatitudeDouble»))); p.setLongitude(Double.parseDouble (getStringAttribute (atts, «poiLongitudeDouble»))); p.setTitle (getStringAttribute (atts, «poiTitleStr»)); p.setSubtitle (getStringAttribute (atts, «poiSubtitleStr»)); p.setText (getStringAttribute (atts, «poiTextStr»)); p.setWeb (getStringAttribute (atts, «poiWebStr»)); p.setEmail (getStringAttribute (atts, «poiEmailStr»)); p.setPhone (getStringAttribute (atts, «poiPhoneStr»)); p.setVideo (getStringAttribute (atts, «poiVideoStr»)); //User new values p.setPoiRating (getStringAttribute (atts, «poiRating»)); } catch (Exception e) { e.printStackTrace (); p = null; } } elseif (localName. equals (CATEGORY)) {
try { if (p!= null) { String catId = getStringAttribute (atts, «poiCategoryIDStr»); VisionCategory cat = VisionModelManager.searchCategoryById (myCategories, catId, true); if (cat!= null) { p.getCategories().add (cat); cat.getPois().add (p); } } } catch (Exception e) { e.printStackTrace (); } } elseif (localName. equals (ICON)) { try { if (p == null) {// general icons VisionImage icon = newVisionImage (); icon.setId (getStringAttribute (atts, «poiIconIDStr»)); if (VisionModelManager.searchImageById(VisionCore.core.model.getIcons (), icon.getId ()) == null) {// unknown icon String url = getStringAttribute (atts, «poiIconStr»); icon.setImageURL (getImageUrl (url)); VisionCore.core.model.getIcons().add (icon); } } else {// poi icon String iconId = getStringAttribute (atts, «poiIconIDStr»);
VisionImage icon = VisionModelManager.searchImageById(VisionCore.core.model.getIcons (), iconId); if (icon!= null) { p.getIcons().add (icon); } } } catch (Exception e) { e.printStackTrace (); } } elseif (localName. equals (AUDIO)) { try { if (p!= null) { VisionAudio audio = newVisionAudio (); audio.setAudioURL (getStringAttribute (atts, «poiAudioStr»)); audio.setText (getStringAttribute (atts, «poiAudioTextStr»)); audio.setTitle (getStringAttribute (atts, «poiAudioTitleStr»)); p.getAudios().add (audio); } } catch (Exception e) { e.printStackTrace (); } } elseif (localName. equals (IMAGE)) { try { if (p!= null) { VisionImage draw = newVisionImage (); String url = getStringAttribute (atts, «poiImageStr»);
draw.setImageURL (getImageUrl (url)); p.getImages().add (draw); } } catch (Exception e) { e.printStackTrace (); } } // else } // startElement @Override publicvoid characters (char ch [], int start, int length) { ; } @Override publicvoid endElement (String namespaceURI, String localName, String qName) throws SAXException { if (localName. equals («poi»)) { if (p!= null) { pois.add (p); } } } // endElement } // VisionGeoPoiHandler
} После создания нового POI класса и нового парсера, нужно изменить VisionModelManager для принудительного использования нового парсера для загрузки данных. Для этого необходимо реализовать метод loadPois. @Override publicvoid loadPois (Context ctx, String url, boolean update) { loadingPois = true; try { new CustomGeoPoiParser (ctx, url).parse (); for (int i=0; i <pois.size (); i++) { pois.get(i).setVisionGeoPoiClickListener (this); } if (update) { updateNearestPois (ctx); } } catch (Exception e) { e.printStackTrace (); } finally { loadingPois = false; } } Класс VisionGeoPoi содержит метод canBeShow, определяющий отображение точек интереса. Путем его повторной реализации, могут быть добавлены дополнительные условия для показа или скрытия POI. Процесс расширения структуры данных категорий полностью аналогичен POI, но с использованием классов VisionCategory, VisionCategoryParser и метода loadCategories класса VisionModelManager.
Компонент VisionARManager отвечает за управление отображением дополненной реальности. Порядок представления дополненной реальности определяется двумя массивами значений, включенных в VisionARManager – verticalViews и horizontalViews. Кроме того, переменная viewsNum определяет число видимых представлений, определенных в массивах. Значениями по умолчанию являются: /** Number of existent views per orientation */ protected int viewsNum = 4; /** Orderer list of vertical available views. It defines the order for transitions between vertical views */ protected int [] verticalViews = {FLOATING_PANEL, FLOATING_PANEL, MAP, POI_LIST}; /** Orderer list of horizontal available views. It defines the order for transitions between horizontal views */ protected int [] horizontalViews = {FLOATING_ARROW, RADAR, MAP, POI_LIST}; Чтобы внести изменения в представления, нужно изменить эти переменные в конструкторе нового класса, расширяющего класс VisionARManager. Важно подчеркнуть, что представления с таким же индексом в verticalViews и horizontalViews обмениваются друг с другом при переводе экрана в вертикальное или горизонтальное положение. Например, если экран в горизонтальном положении находится в режиме «FLOATING ARROW», при переходе в вертикальное положение он будет в режиме «FLOATING_PANEL», так как этому соответствует один и тот же индекс. Например, для того чтобы в приложении отображались только представления FLOATING_ARROW, FLOATING_PANEL и RADAR, нужно определить в новом классе: /** Number of existent views per orientation */ protected int viewsNum = 2; /** Orderer list of vertical available views. It defines the order for transitions between vertical views */ protected int [] verticalViews = {FLOATING_PANEL, FLOATING_PANEL};
/** Orderer list of horizontal available views. It defines the order for transitions between horizontal views */ protected int [] horizontalViews = {FLOATING_ARROW, RADAR}; VISION SDK предоставляет систему меню, закрепленных в четырех углах представления дополненной реальности. Каждый из углов может содержать кнопки с действиями, определенным в VISION SDK, или новыми действиями, созданными пользователем SDK. Эти действия сопоставляются с переменными topLeftAction, topRightAction, bottomLeftAction и bottomRigthAction класса VisionARManager, определяющими расположение кнопок. По умолчанию, будет отображаться только выпадающее меню в нижнем левом углу экрана. VISION SDK обеспечивает четыре основные действия для кнопок. ACTION_SHOWCHANGEVIEW – выпадающий список с доступными представлениями. ACTION_SHOWOPTIONS – выпадающий список опций. ACTION_CHANGEVIEW – изменяет текущее представление на новое представление VISION SDK. ACTION_EXIT – закрытие представления дополненной реальности. package com.geomobile.vision.demo; import android.graphics.Color; import android.widget.LinearLayout; import android.widget.Toast; import com.geomobile.arcore.ar.VisionARActivity; import com.geomobile.arcore.ar.VisionARManager; import com.geomobile.arcore.ar.VisionCameraView; import com.geomobile.arcore.model.VisionAction; import com.geomobile.arcore.model.VisionImage; import com.geomobile.arcore.utils.VisionLanguage; public class MyARManager extends VisionARManager {
private final int ACTIONBUTTON1 = 10; private final int OPTION1 = 11; private final int OPTION2 = 12; private final int OPTION3 = 13; private final int ACTIONCLOSE = 14; public Boolean map; public Boolean list; public Boolean teleport; public MyARManager (Boolean map, Boolean list, Boolean teleport) { super (); this.map = map; this.list = list; this.teleport = teleport; if (map && list) { int [] vertical = {FLOATING_PANEL, FLOATING_PANEL, MAP, POI_LIST}; int [] horizontal = {FLOATING_ARROW, RADAR, MAP, POI_LIST}; verticalViews = vertical; horizontalViews = horizontal; viewsNum = 4; } else if (map) { int [] vertical = {FLOATING_PANEL, FLOATING_PANEL, MAP}; int [] horizontal = {FLOATING_ARROW, RADAR, MAP}; verticalViews = vertical; horizontalViews = horizontal;
viewsNum = 3; } else if (list) { int [] vertical = {FLOATING_PANEL, FLOATING_PANEL, POI_LIST}; int [] horizontal = {FLOATING_ARROW, RADAR, POI_LIST}; verticalViews = vertical; horizontalViews = horizontal; viewsNum = 3; } else { int [] vertical = {FLOATING_PANEL, FLOATING_PANEL}; int [] horizontal = {FLOATING_ARROW, RADAR}; verticalViews = vertical; horizontalViews = horizontal; viewsNum = 2; } VisionAction a1 = new VisionAction (); a1.action = ACTIONBUTTON1; a1.title = «button1»; a1.icon = null; topLeftAction = a1; VisionAction a2 = new VisionAction (); a2.action = ACTION_SHOWOPTIONS; a2.title = «button2»; a2.icon = null; bottomLeftAction = a2;
VisionAction o1 = new VisionAction (); o1.action = OPTION1; o1.title = «option1»; o1.icon = null; a2.options.add (o1); VisionAction o2 = new VisionAction (); o2.action = OPTION2; o2.title = «option2»; o2.icon = null; a2.options.add (o2); VisionAction o3 = new VisionAction (); o3.action = OPTION3; o3.title = «option3»; o3.icon = null; a2.options.add (o3); VisionAction a3 = new VisionAction (); a3.action = ACTIONCLOSE; a3.title = «close»; a3.icon = null; topRightAction = a3; VisionAction a4 = new VisionAction ();
a4.action = VisionARManager.ACTION_SHOWCHANGEVIEW; a4.title = «close»; a4.icon = null; bottomRightAction = a4; messageColor = Color.argb (187,0, 100, 0); } @Override protected boolean doAction (VisionAction action, int index, LinearLayout dropdown, boolean left, boolean top, int orientation, VisionCameraView camera, VisionARActivity activity) { switch (action.action) { case ACTIONBUTTON1: Toast.makeText (activity, "< – - "+VisionLanguage.getString («button1») +" – ->», Toast.LENGTH_SHORT).show (); return true; case OPTION1: Toast.makeText (activity, "< – - "+VisionLanguage.getString («option1») +" – ->», Toast.LENGTH_SHORT).show (); return true; case OPTION2: Toast.makeText (activity, "< – - "+VisionLanguage.getString («option2») +" – ->», Toast.LENGTH_SHORT).show (); return true; case OPTION3: Toast.makeText (activity, "< – - "+VisionLanguage.getString («option3») +" – ->», Toast.LENGTH_SHORT).show (); return true;
case ACTIONCLOSE: activity.finish (); return true; default: return super.doAction (action, index, dropdown, left, top, orientation, camera, activity); } } // doAction @Override public boolean canBeTeleported () { // TODO Auto-generated method stub return teleport; } } В верхней части всех представлений VISION SDK отображаются сообщения состояния приложения, которые контролируются методом updateMessage класса VisionARManager. @Override Public void updateMessage (VisionARActivity activity, int poisSize) { if (condion) activity.setMessage («My message»); else super. updateMessage (activity, poisSize); } Чтобы изменить внешний вид сообщения состояния, необходимо изменить значения переменных messageTeleportColor, messagecolor и messageTextColor. /** Top message background color */
public int messageColor = 0xbb000000; /** Top message text color */ public int messageTextColor = 0xffffffff; /** Top message background color when telepor is activated */ public int teleportMessageColor = 0xbb635a71; Опция телепорт, включенная в представление карты VISION SDK, активна по умолчанию. Чтобы отключить эту опцию, вы должны реализовать метод canBeTeleported класса наследника VisionARManager класса. @Override publicboolean canBeTeleported () { return false; } Настройка отображения точек интереса может быть выполнена в классе, расширяющем класс VisionGeoPoi. Загрузка пользовательских точек интереса CustomGeoPoi производится в методе loadPois класса MyModelManager. Представления для POI панелей дополненной реальности, которые отображаются поверх вида камеры, создаются в методе getPanelTextureLayout класса VisionGeoPoi. Этот способ создает компоновку панели с требуемым дизайном. При реализации этого метода в подклассе можно изменить расположение компонентов, скрыть или удалить ненужные и добавлять новые компоненты, представляющие новые поля, которые были добавлены к POI. Примечание: Важно использовать Layout размер 256px на 128px, который движок дополненной реальности использует для размещения панелей на экране. @Override public LinearLayout getPanelTextureLayout (Context ctx) { // TODO Auto-generated method stub float density = VisionCore.core.configuration.getScreenDensity (); LinearLayout lay = new LinearLayout (ctx); lay.setOrientation(LinearLayout.VERTICAL);
lay.setLayoutParams (new LinearLayout.LayoutParams (256, 128)); lay.setPadding (32, 14, 32, 8); lay.setBackgroundDrawable (getPanelBackground (ctx)); lay.layout (0, 0, 256, 128); TextView vista = new TextView (ctx); vista.setLayoutParams (new LinearLayout.LayoutParams (180, LayoutParams. WRAP_CONTENT)); vista.setTextColor (0xffffffff); vista.setTextSize ((int) (25 / density)); vista.setText (this. title); vista.setGravity (Gravity. CENTER_HORIZONTAL); vista.setMaxLines (2); vista.layout (40, 5, 210, 100); lay.addView (vista); LinearLayout top = new LinearLayout (ctx); top.setOrientation (LinearLayout. HORIZONTAL); top.setLayoutParams (new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, 56)); top.layout (40, 70, 210, 155); lay.addView (top); vista = new TextView (ctx); vista.setText(VisionUtils.getDistanceToString (this. distance)); vista.layout (0, 0, 105, 40); vista.setTextColor (0xffffffff); vista.setTextSize ((int) (20 / density));
top.addView (vista); vista = new TextView (ctx); vista.setText(this.getPoiRating ()); vista.layout (95, 0, 110, 40); vista.setTextColor (0xffffffff); vista.setTextSize ((int) (20 / density)); top.addView (vista); LinearLayout icons = newLinearLayout (ctx); icons.setOrientation (LinearLayout. HORIZONTAL); icons.setLayoutParams (new LinearLayout.LayoutParams (110, 40)); icons.layout (110, 0, 200, 40); top.addView (icons); if (categories!= null&&categories.size ()> 0) { ImageView iv; iv = new ImageView (ctx); iv.setLayoutParams (new LayoutParams ((int) (30/density), (int) (30/density))); iv.setScaleType (ScaleType. CENTER_INSIDE); iv.setImageResource (R. drawable. logo_g); icons.addView (iv); iv.layout (0, 0, 30, 30); int j = 0; for (int i = 0; i <categories.size (); i++) { if (categories.get(i).getIcon ()!=
null&&categories.get(i).getIcon().getImage (null, true, false, true)!= null) { iv = new ImageView (ctx); iv.setLayoutParams (new LayoutParams ((int) (30/density), (int) (30/density))); iv.setScaleType (ScaleType. CENTER_INSIDE); iv.setImageDrawable(categories.get(i).getIcon().getImage (null, true, false, true)); icons.addView (iv); iv.layout (30 * (j+1), 0, 35 * (j +2) – 5, 30); j++; } } } // if return lay; } Здесь используется метод getPanelBackground для получения фонового изображения панели. Для изменения фонового изображения можно переопределить этот метод и сгенерировать URL к ресурсу, который вы хотите использовать. Реализация по умолчанию возвращает изображение, связанное с категорией POI, если оно существует, в противном случае она возвращает изображение по умолчанию, установленное в проекте для панели дополненной реальности. Представления для стрелок дополненной реальности создаются в методе getArrowTextureLayout класса VisionGeoPoi. @Override public LinearLayout getArrowTextureLayout (Context ctx) { // TODO Auto-generated method stub float density = VisionCore.core.configuration.getScreenDensity (); LinearLayout lay = new LinearLayout (ctx);
lay.setOrientation(LinearLayout.VERTICAL); lay.setLayoutParams (new LinearLayout.LayoutParams (256, 128)); lay.setPadding (16, 16, 16, 8); lay.setBackgroundDrawable (getArrowBackground (ctx)); lay.layout (0, 0, 256, 128); LinearLayout top = new LinearLayout (ctx); top.setOrientation (LinearLayout. HORIZONTAL); top.setLayoutParams (new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, 40)); top.layout (16, 16, 232, 72); lay.addView (top); ImageView iv = new ImageView (ctx); iv.setLayoutParams (new LayoutParams (25, 25)); iv.setScaleType (ScaleType. CENTER_INSIDE); iv.setImageDrawable(ctx.getResources().getDrawable (R. drawable. logo_g)); top.addView (iv); iv.layout (10, 0, 35, 35); TextView vista = newTextView (ctx); vista.setText(VisionUtils.getDistanceToString (this. distance)); vista.layout (100, 0, 240, 40); vista.setTextColor (0xffffffff); vista.setTextSize ((int) (20 / density)); top.addView (vista); vista = new TextView (ctx);
vista.setLayoutParams (new LinearLayout.LayoutParams (180, LayoutParams. WRAP_CONTENT)); vista.setTextColor (0xffffffff); vista.setTextSize ((int) (23 / density)); vista.setGravity (Gravity. CENTER); vista.setMaxLines (2); vista.setText (this. title); vista.layout (32, 57, 170, 120); lay.addView (vista); return lay; } Панель радара может быть изменена в методе drawInfoPanel класса, наследущего от класса VisionGeoPoi. Как и в предыдущем случае, фоновое изображение получается методом getInfoPanelBackground, который можно переопределить @Override public View drawInfoPanel (Context ctx) { RelativeLayout ll = newRelativeLayout (ctx); LinearLayout.LayoutParams p = newLinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams. WRAP_CONTENT); p.setMargins(VisionUtils.scalePixels (10), VisionUtils.scalePixels (20), VisionUtils.scalePixels (10), 0); ll.setLayoutParams (p); ll.setGravity (Gravity. CENTER_VERTICAL); ll.setPadding(VisionUtils.scalePixels (10), 0, VisionUtils.scalePixels (10), VisionUtils.scalePixels (20));
ll.setBackgroundDrawable(this.getInfoPanelBackground (ctx)); TextView rating = new TextView (ctx); RelativeLayout.LayoutParams params = newRelativeLayout.LayoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT); params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); params.addRule (RelativeLayout. CENTER_VERTICAL, RelativeLayout.TRUE); params.setMargins(VisionUtils.scalePixels (5), 0, 0, 0); rating.setLayoutParams (params); rating.setTextColor (0xff000000); rating.setTypeface(Typeface.DEFAULT_BOLD); rating.setText (poiRating); ll.addView (rating); LinearLayout ll2 = new LinearLayout (ctx); params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams. WRAP_CONTENT); ll2.setLayoutParams (params); ll2.setOrientation(LinearLayout.VERTICAL); ll2.setGravity (Gravity. CENTER); ll.addView (ll2); TextView distance = new TextView (ctx); LinearLayout.LayoutParams params2 = newLinearLayout.LayoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT);
distance.setLayoutParams (params2); distance.setTextColor (0xff333333); distance.setTypeface(Typeface.DEFAULT_BOLD); distance.setText(VisionUtils.getDistanceToString (this. distance)); ll2.addView (distance); TextView title = new TextView (ctx); params2 = newLinearLayout.LayoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT); title.setLayoutParams (params2); title.setTextColor (0xffffffff); title.setTextSize (15); title.setTypeface(Typeface.DEFAULT_BOLD); title.setSingleLine (true); title.setEllipsize (TruncateAt. END); title.setText(Html.fromHtml (this. title)); ll2.addView (title); ImageView miniImage = new ImageView (ctx); params = new RelativeLayout.LayoutParams(VisionUtils.scalePixels (25), VisionUtils.scalePixels (25)); params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); params.addRule (RelativeLayout. CENTER_VERTICAL, RelativeLayout.TRUE); params.setMargins (0, 0, VisionUtils.scalePixels (5), 0); miniImage.setLayoutParams (params); miniImage.setScaleType (ScaleType. CENTER_INSIDE);
miniImage.setImageDrawable(ctx.getResources().getDrawable (R. drawable. logo_g)); ll.addView (miniImage); return ll; } Содержание и дизайн каждой из ячеек списка объектов POI определяется реализацией метода drawListItem, который может быть изменен в классе, наследующем от класса VisionGeoPoi. Как и в предыдущем случае, фоновое изображение получается методом getListItemBackground. @Override public View drawListItem (Context ctx, View ll) { ll = new LinearLayout (ctx); ListView.LayoutParams listparams = newListView.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams. WRAP_CONTENT); ll.setLayoutParams (listparams); ll.setPadding(VisionUtils.scalePixels (3), VisionUtils.scalePixels (3), VisionUtils.scalePixels (3), VisionUtils.scalePixels (3)); ll.setBackgroundColor(VisionCore.core.ar.backgroundColor); LinearLayout lay = new LinearLayout (ctx); LinearLayout.LayoutParams params = newLinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams. WRAP_CONTENT); lay.setLayoutParams (params); lay.setOrientation (LinearLayout. HORIZONTAL); lay.setPadding(VisionUtils.scalePixels (10), 0, VisionUtils.scalePixels (30), 0); lay.setGravity (Gravity. CENTER_VERTICAL);
((LinearLayout) ll).addView (lay); ImageView remove = new ImageView (ctx); params = new LinearLayout.LayoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT); params.setMargins (0, 0, VisionUtils.scalePixels (5), 0); remove.setLayoutParams (params); remove.setScaleType(ScaleType.FIT_CENTER); remove.setImageResource(VisionUtils.getResourceIdentifier («vision_icon_remove», «drawable»)); remove.setVisibility (View. GONE); lay.addView (remove); ImageView miniImage = new ImageView (ctx); params = new LinearLayout.LayoutParams(VisionUtils.scalePixels (25), VisionUtils.scalePixels (25)); miniImage.setLayoutParams (params); miniImage.setScaleType(ScaleType.FIT_CENTER); lay.addView (miniImage); LinearLayout lay2 = new LinearLayout (ctx); params = new LinearLayout.LayoutParams (0, LayoutParams. WRAP_CONTENT, 1); lay2.setLayoutParams (params); lay2.setOrientation(LinearLayout.VERTICAL); lay2.setGravity (Gravity. CENTER_VERTICAL);
lay2.setPadding(VisionUtils.scalePixels (10), 0, 0, 0); lay.addView (lay2); LinearLayout lay3 = new LinearLayout (ctx); params = new LinearLayout.LayoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT); params.setMargins (0, 0, VisionUtils.scalePixels (5), 0); lay3.setLayoutParams (params); lay3.setOrientation(LinearLayout.VERTICAL); lay3.setGravity (Gravity. CENTER); lay3.setPadding(VisionUtils.scalePixels (10), 0, 0, 0); lay.addView (lay3); TextView title = new TextView (ctx); params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams. WRAP_CONTENT); title.setLayoutParams (params); title.setTextColor (0xff000000); title.setTextSize (15); title.setSingleLine (true); title.setEllipsize (TruncateAt. END); lay2.addView (title); TextView text = new TextView (ctx); params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams. WRAP_CONTENT);
text.setLayoutParams (params); text.setTextColor (0xff333333); text.setTextSize (12); text.setSingleLine (true); text.setEllipsize (TruncateAt. END); lay2.addView (text); TextView distance = new TextView (ctx); params = new LinearLayout.LayoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT); distance.setLayoutParams (params); distance.setTextColor (0xff333333); lay3.addView (distance); ImageView catImage = new ImageView (ctx); params = new LinearLayout.LayoutParams(VisionUtils.scalePixels (15), VisionUtils.scalePixels (15)); catImage.setLayoutParams (params); catImage.setScaleType(ScaleType.FIT_CENTER); lay3.addView (catImage); // set data lay.setBackgroundDrawable(this.getListItemBackground (ctx)); miniImage.setImageResource (R. drawable. logo_g); title.setText(Html.fromHtml (this. title));
title.setTextColor (0xff000000); text.setText(Html.fromHtml(this.subtitle)); text.setTextColor (0xff000000); distance.setText(VisionUtils.getDistanceToString (this. distance)); distance.setTextColor (0xff000000); Vector <VisionCategory> cats = this.getCategories (); if (cats!= null&& cats.size ()> 0) { int i = 0; Drawable im = null; while (i <cats.size () && im == null) { if (cats.get(i).getIcon ()!= null) { im = cats.get(i).getIcon().getImage (ctx, true, false, true); } i++; } catImage.setImageDrawable (im); } // if return ll; } Представление карты позволяет отображать расширенную информацию о точке интереса POI, когда вы нажимаете на ее значок. Чтобы настроить индивидуальный вид информационного сообщения, нужно переопределить метод drawMapPopUp класса VisionGeoPoi. @Override public View drawMapPopup (Context ctx) {
LinearLayout pop = (LinearLayout) View.inflate(ctx,R.layout.custom_info_window, null); TextView title=(TextView)pop.findViewById(R.id.title); title.setText(this.getTitle ()); TextView subtitle=(TextView)pop.findViewById(R.id.subtitle); subtitle.setText(this.getSubtitle ()); ImageView cat=(ImageView)pop.findViewById(R.id.cat); Vector <VisionCategory> cats = this.getCategories (); if (cats!= null&& cats.size ()> 0) { int i = 0; Drawable im = null; while (i <cats.size () && im == null) { if (cats.get(i).getIcon ()!= null) { im = cats.get(i).getIcon().getImage (ctx, true, false, true); } i++; } cat.setImageDrawable (im); } // if return pop; } <?xmlversion=«1.0"encoding=«utf-8»? > <LinearLayoutxmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: background="@drawable/custom_info_bubble»
android: gravity=«center_vertical» android: orientation=«horizontal»> <ImageView android: layout_width=«20dp» android: layout_height=«20dp» android: layout_marginRight=«5dp» android: adjustViewBounds=«true» android: src="/storage/public/books/4d/0d/4d0da3ee-23a0-42f0-a4a502d62ab5e344/@drawable/logo_g»> </ImageView> <LinearLayout android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_weight=«1» android: orientation=«vertical»> <TextView android: id="@+id/title» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: ellipsize=«end» android: singleLine=«true» android: text=«texto 1» android: textColor=«#ff000000» android: textSize=«14dp»
android: textStyle=«bold»/> <TextView android: id="@+id/subtitle» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: ellipsize=«end» android: singleLine=«true» android: text=«texto 2» android: textColor=«#ffffffff» android: textSize=«12dp»/> </LinearLayout> <ImageView android: id="@+id/cat» android: layout_width=«20dp» android: layout_height=«20dp» android: layout_marginRight=«5dp» android: adjustViewBounds=«true»> </ImageView> </LinearLayout> VISION SDK содержит пакет изображений, которые определяют общий внешний вид элементов в различных представлениях. Если вы хотите внести изменения в эти элементы, чтобы адаптировать дизайн к новому приложению, можно выполнить два действия: Создать новые изображения, которые вы хотите изменить в окончательном проекте, соблюдая размеры и названия оригинальных графических элементов.
Добавить новую группу изображений и сообщить, какой следует использовать префикс при поиске изображений. Чтобы настроить новый префикс к изображению ресурсов, можно использовать метод setPrefixForImages в классе VisionARManager. Для локализации текста нужно использовать файл strings. xml папки vision_sdk_library\res\values. <?xml version=«1.0» encoding=«utf-8»? > <resources> <string name=«map_key»> </string> <! – PUT HERE YOUR GOOGLE MAPS KEY – > <string name=«vision_key»> </string> <! – PUT HERE THE VISION SDK KEY ASSIGNED TO YOUR APP – > <! – English – > <string name=«vision_view1_en»> Panels </string> <string name=«vision_view2_en»> List </string> <string name=«vision_view3_en»> Arrows </string> <string name=«vision_view4_en»> Radar </string> <string name=«vision_view5_en»> Map </string> <string name=«vision_loading_en»> Loading… </string> <string name=«vision_pois_found_en»> points of interest found </string> <string name=«vision_gps_error_en»> Precision \u00B1 </string> <string name=«vision_teleported_to_en»> Located in </string> <string name=«vision_no_teleported_to_en»> No teleportation location available </string> <string name=«vision_teleport_on_en»> teleport mode ON </string> <string name=«vision_teleport_off_en»> teleport mode OFF </string> <string name=«vision_teleport_help_title_en»> Do you want to be teleported? </string>
<string name=«vision_teleport_help_text_en»> To be teleported you just have to move the icon around the map (click the icon until it vibrates and you can move it) or click on the teleport button to find the street you want. You can also save your location and access it whenever you want. </string> <string name=«vision_teleport_help_check_en»> Don\’t show again </string> <string name=«vision_close_en»> Close </string> <string name=«vision_location_name_en»> Put the name of the new location: </string> <string name=«vision_save_en»> Save </string> <string name=«vision_put_name_en»> You should set a name for the location </string> <string name=«vision_my_locations_en»> My locations </string> <string name=«vision_no_items_en»> There are no items to show </string> <string name=«vision_put_address_en»> You don\’t have put the address where to be teleported </string> <string name=«vision_address_not_found_en»> Address not found </string> <string name=«vision_location_added_en»> has been added to your favorites locations </string> <string name=«vision_all_en»> All </string> </resources> Создадим свое приложение, в котором пользователи смогут создавать свои точки интереса и делиться ими с друзьями. Для этого в среде Eclipse создадим проект Android приложения на основе шаблона Blank Activity. В свойствах проекта приложения в разделе Android в Project Build Target добавим зависимость от библиотек vision_sdk и google-play-services_lib. Для передачи точек интереса в облако будем использовать библиотеку Volley (https://mvnrepository.com/artifact/com.mcxiaoke.volley/library/1.0.19), JAR файл которой скопируем в папку libs Android проекта. Для облачного хранения точек интереса создадим в среде Eclipse проект Web Application Project платформы Google App Engine Java. Для работы с JSON скачаем библиотеку (https://mvnrepository.com/artifact/org.json/json/1.520090211) и добавим JAR файл в папку war\WEB-INF\lib проекта. В свойствах проекта в разделе Java Build Path добавим зависимость от JSON библиотеки.
Изменим код класса главной активности Android приложения. import com.geomobile.arcore.VisionConfiguration; import com.geomobile.arcore.VisionCore; import android.Manifest; import android.app.Activity; import android.content.pm.PackageManager; import android.os.AsyncTask; import android. os. Bundle; import android.support.v4.app.ActivityCompat; import android.view.View; import android. widget. Button; import android.widget.ProgressBar; public class MainActivity extends Activity { private ProgressBar progress; private Activity activity; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.activity_main); progress=(ProgressBar)this.findViewById(R.id.progressBar); activity=this;
checkPermissions (); Button bt_launch = (Button) this.findViewById(R.id.launch); bt_launch.setOnClickListener (new View. OnClickListener () { public void onClick (View v) { VisionCore vs = new VisionCore(activity.getApplicationContext (), false); VisionCore.core.ar=new MyARManager (activity); VisionCore.core.configuration=new VisionConfiguration (); VisionCore.core.configuration.setRadarPosition (VisionConfiguration. RADAR_POSITION_RIGHT); VisionCore.core.configuration.showAppLogo (false); VisionCore.core.ar.setPrefixForImages (»»); new LoadDataTask ().execute (); } }); } protected void checkPermissions () { if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED ||ActivityCompat.checkSelfPermission (this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED ||ActivityCompat.checkSelfPermission (this, Manifest.permission.GET_ACCOUNTS)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions (this, new String [] { android.Manifest.permission.CAMERA, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.GET_ACCOUNTS }, 0); } } private class LoadDataTask extends AsyncTask <Void, Void, Void> { public LoadDataTask () { } protected void onPreExecute () { progress.setVisibility(View.VISIBLE); } @Override protected void onPostExecute (Void response) { VisionCore.startAR(MainActivity.this); progress.setVisibility (View. GONE); } @Override protected Void doInBackground (Void… params) {
VisionCore.core.model=new MyModelManager (activity); VisionCore.core.model. loadPoisContinouosly = true; VisionCore.core.loadData(MainActivity.this); return null; } } } В методе onCreate класса главной активности приложения сначала загружается заставка с кнопкой Start запуска дополненной реальности и запрашиваются необходимые для работы приложения разрешения с помощью метода checkPermissions (). После получения всех разрешений можно нажать кнопку Start, при этом создается экземпляр класса VisionCore и экземпляр класса MyARManager, который расширяет класс VisionARManager. Далее запускается задача AsyncTask, в методе doInBackground которой загружаются точки интереса с помощью класса MyModelManager, расширяющего класс VisionModelManager. После выполнения метода doInBackground, в методе onPostExecute, запускается дополненная реальность с помощь вызова метода VisionCore.startAR(MainActivity.this). Компоновка заставки: <RelativeLayout xmlns: android="http://schemas.android.com/apk/res/android" xmlns: tools="http://schemas.android.com/tools" android: layout_width=«match_parent» android: layout_height=«match_parent» android: background=«#87CEFA» tools:context=".MainActivity»> <ImageView android: layout_width=«wrap_content»
android: layout_height=«wrap_content» android: padding=«10dp» android: layout_centerInParent=«true» android: src="/storage/public/books/4d/0d/4d0da3ee-23a0-42f0-a4a502d62ab5e344/@drawable/logo» /> <Button android: id="@+id/launch» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_margin=«50dp» android: padding=«10dp» android: layout_alignParentBottom=«true» android: layout_centerHorizontal =«true» android: text=«Start» android: textColor=«#FF0000» android: background=«#00bfff» android: textSize=«20sp» /> <ProgressBar android: id="@+id/progressBar» style=»? android: attr/progressBarStyleLarge» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_centerInParent=«true» android: visibility=«gone»/>
</RelativeLayout> В классе MyARManager определяются кнопки, с помощью которых переключаются представления дополненной реальности и вызываются диалоги добавления, удаления и редактирования точек интереса. import java.io.IOException; import java.util.ArrayList; import java. util. HashMap; import java.util.LinkedList; import java.util.List; import java. util. Locale; import java.util.Map; import org. json. JSONArray; import org. json. JSONException; import org. json. JSONObject; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android. volley. VolleyError; import com.android.volley.toolbox. JsonArrayRequest; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox. Volley; import com.geomobile.arcore.VisionCore; import com.geomobile.arcore.ar.VisionARActivity; import com.geomobile.arcore.ar.VisionARManager; import com.geomobile.arcore.ar.VisionCameraView;
import com.geomobile.arcore.model.VisionAction; import com.geomobile.arcore.model.VisionImage; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.app.AlertDialog; import android. app. Dialog; import android.content.Context; import android.content. DialogInterface; import android.location.Address; import android.location.Geocoder; import android. location. Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android. os. Bundle; import android.support. v4.app. DialogFragment; import android. text. TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View. OnFocusChangeListener; import android.view. WindowManager; import android.widget.CheckBox; import android. widget. EditText; import android.widget.LinearLayout; import android.widget.ProgressBar;
import android. widget. TextView; import android.widget.Toast; public class MyARManager extends VisionARManager { private final int OPTION1 = 11; private final int OPTION2 = 12; private final int OPTION3 = 13; private Activity activity; public MyARManager (Activity _activity) { super (); activity=_activity; int [] vertical = {FLOATING_PANEL, FLOATING_PANEL, MAP, POI_LIST}; int [] horizontal = {FLOATING_ARROW, RADAR, MAP, POI_LIST}; verticalViews = vertical; horizontalViews = horizontal; viewsNum = 4; VisionAction a = new VisionAction (); a.action = VisionARManager.ACTION_SHOWCHANGEVIEW; a. title = «close»; a. icon = null; bottomRightAction=a;
VisionAction options = new VisionAction (); options.action = ACTION_SHOWOPTIONS; options. title = «»; VisionImage iconOptions = new VisionImage (); iconOptions.setImageURL («add»); options. icon = iconOptions; bottomLeftAction = options; VisionAction o1 = new VisionAction (); o1.action = OPTION1; o1.title = «»; VisionImage iconO1 = new VisionImage (); iconO1.setImageURL («add»); o1.icon = iconO1; options.options.add (o1); VisionAction o2 = new VisionAction (); o2.action = OPTION2; o2.title = «»; VisionImage iconO2 = new VisionImage (); iconO2.setImageURL («edit»); o2.icon = iconO2; options.options.add (o2); VisionAction o3 = new VisionAction (); o3.action = OPTION3;
o3.title = «»; VisionImage iconO3 = new VisionImage (); iconO3.setImageURL («delete»); o3.icon = iconO3; options.options.add (o3); VisionAction a4 = new VisionAction (); a4.action = VisionARManager.ACTION_SHOWCHANGEVIEW; a4.title = «close»; a4.icon = null; bottomRightAction = a4; } @Override protected boolean doAction (VisionAction action, int index, LinearLayout dropdown, boolean left, boolean top, int orientation, VisionCameraView camera, VisionARActivity activity) { switch (action.action) { case OPTION1: if (isOnline ()) { AddPOIDialogFragment dialog = new AddPOIDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «AddPOIDialogFragment»); } else { Toast.makeText (activity, «No Internet Connection», Toast.LENGTH_SHORT).show ();
} return true; case OPTION2: if (isOnline ()) { EditPOIDialogFragment dialog = new EditPOIDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «EditPOIDialogFragment»); } else { Toast.makeText (activity, «No Internet Connection», Toast.LENGTH_SHORT).show (); } return true; case OPTION3: if (isOnline ()) { DeletePOIDialogFragment dialog = new DeletePOIDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «DeletePOIDialogFragment»); } else { Toast.makeText (activity, «No Internet Connection», Toast.LENGTH_SHORT).show (); } return true; default: return super.doAction (action, index, dropdown, left, top, orientation, camera, activity); } } // doAction
@Override public void updateMessage (VisionARActivity activity, int poisSize) { //activity.setMessage (»»); super. updateMessage (activity, poisSize); } public static class AddPOIDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private Location currentLocation; private TextView location; private EditText title; private EditText subtitle; private EditText description; private ProgressBar progress; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ()); LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout.add_poi, null); builder.setView (view) .setPositiveButton(R.string. button_ok, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) {
} }) .setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { AddPOIDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); currentLocation = VisionCore.core.model.getLocation (); location=(TextView)view.findViewById(R.id.add_location); location.setText(currentLocation.getLatitude () +" "+currentLocation.getLongitude ()); Geocoder geocoder = new Geocoder (getActivity (), Locale.getDefault ()); List <Address> addresses = null; try { addresses = geocoder.getFromLocation(currentLocation.getLatitude(),currentLocation.getLongitude (),1); } catch (IOException e) { e.printStackTrace (); }
title=(EditText)view.findViewById(R.id.add_title); if (addresses!=null) { Address address = addresses.get (0); ArrayList <String> addressFragments = new ArrayList <String> (); for (int i = 0; i <address.getMaxAddressLineIndex (); i++) { addressFragments.add(address.getAddressLine (i)); } title.setText(TextUtils.join(System.getProperty("line.separator»),addressFragments)); } subtitle=(EditText)view.findViewById(R.id.add_subtitle); description=(EditText)view.findViewById(R.id.add_description); progress=(ProgressBar)view.findViewById(R.id.add_progressBar); return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { progress.setVisibility(View.VISIBLE); final String url = "http://travelwithfriends-ar.appspot.com/travelwithfriendsbackend"; RequestQueue queue = Volley.newRequestQueue (getActivity ());
StringRequest postRequest = new StringRequest(Request.Method.POST, url, new Response.Listener <String> () { @Override public void onResponse (String response) { VisionCore.core.model=new MyModelManager (getActivity ()); VisionCore.core.model. loadPoisContinouosly = true; VisionCore.core. loadData (getActivity ()); VisionCore.startAR (getActivity ()); progress.setVisibility (View. GONE); alert. dismiss (); } }, new Response.ErrorListener () { @Override public void onErrorResponse (VolleyError error) { // error } } ){ @Override protected Map <String, String> getParams () {
Map <String, String> params = new HashMap <String, String> (); params. put («user», getUsername ()); params. put («lat», Double.toString(currentLocation.getLatitude ())); params. put («lon», Double.toString(currentLocation.getLongitude ())); params. put («title», title.getText().toString ()); params. put («subtitle», subtitle.getText().toString ()); params. put («description», description.getText().toString ()); return params; } }; queue.add (postRequest); } }); } private String getUsername () { AccountManager manager = AccountManager.get (getActivity ()); Account [] accounts = manager.getAccountsByType («com. google»); List <String> possibleEmails = new LinkedList <String> (); for (Account account: accounts) { possibleEmails.add(account.name); } if (!possibleEmails.isEmpty () && possibleEmails.get (0)!= null) {
String email = possibleEmails.get (0); String [] parts = email. split (»@»); if (parts. length> 1) return parts [0]; } return null; } } public static class DeletePOIDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private LinearLayout list; private ProgressBar progress; Map <String, String> params = new HashMap <String, String> (); @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ()); LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout.delete_poi, null); builder.setView (view) .setPositiveButton(R.string. button_delete, new DialogInterface. OnClickListener () { @Override
public void onClick (DialogInterface dialog, int id) { } }) .setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { DeletePOIDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); list=(LinearLayout)view.findViewById(R.id.list_delete_poi); progress=(ProgressBar)view.findViewById(R.id.delete_progressBar); progress.setVisibility(View.VISIBLE); String user = getUsername (); final String url = "http://travelwithfriendsar.appspot.com/travelwithfriendsdeletebackend?user="+user; RequestQueue queue = Volley.newRequestQueue (getActivity ()); JsonArrayRequest req = new JsonArrayRequest (url, new Response.Listener <JSONArray> () { @Override public void onResponse (JSONArray response) {
JSONObject jObject = null; int j=1; for (int i = 0; i <response. length (); i++) { try { jObject = response.getJSONObject (i); CheckBox cb = new CheckBox (getActivity ()); cb.setText(jObject.getString («title»)); cb.setId (j); list.addView (cb); j++; } catch (JSONException e) { e.printStackTrace (); } } progress.setVisibility (View. GONE); } }, new Response.ErrorListener () { @Override public void onErrorResponse (VolleyError error) { } });
queue.add (req); return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { progress.setVisibility(View.VISIBLE); CheckBox cb; int k=1; int j=0; for (int i = 0; i<list.getChildCount (); i++) { cb = (CheckBox)list.getChildAt(i).findViewById (k); if(cb.isChecked ()) { params. put («title»+j, cb.getText().toString ()); j++; } k++; } params. put («size», String.valueOf (j)); params. put («user», getUsername ());
final String url = "http://travelwithfriends-ar.appspot.com/travelwithfriendsdeletebackend"; RequestQueue queue = Volley.newRequestQueue (getActivity ()); StringRequest postRequest = new StringRequest(Request.Method.POST, url, new Response.Listener <String> () { @Override public void onResponse (String response) { VisionCore.core.model=new MyModelManager (getActivity ()); VisionCore.core.model. loadPoisContinouosly = true; VisionCore.core. loadData (getActivity ()); VisionCore.startAR (getActivity ()); progress.setVisibility (View. GONE); alert. dismiss (); } }, new Response.ErrorListener () { @Override public void onErrorResponse (VolleyError error) { // error } } ){
@Override protected Map <String, String> getParams () { return params; } }; queue.add (postRequest); } }); } private String getUsername () { AccountManager manager = AccountManager.get (getActivity ()); Account [] accounts = manager.getAccountsByType («com. google»); List <String> possibleEmails = new LinkedList <String> (); for (Account account: accounts) { possibleEmails.add(account.name); } if (!possibleEmails.isEmpty () && possibleEmails.get (0)!= null) { String email = possibleEmails.get (0); String [] parts = email. split (»@»); if (parts. length> 1) return parts [0];
} return null; } } public static class EditPOIDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private LinearLayout list; private ProgressBar progress; private Map <String, String> params = new HashMap <String, String> (); private Activity activity; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { activity=getActivity (); AlertDialog. Builder builder = new AlertDialog. Builder (activity); LayoutInflater inflater = activity.getLayoutInflater (); view = (View) inflater.inflate(R.layout. edit_poi, null); builder.setView (view) .setPositiveButton(R.string. button_edit, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) { } })
.setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { EditPOIDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); list = (LinearLayout)view.findViewById(R.id.list_edit_poi); progress=(ProgressBar)view.findViewById(R.id.edit_progressBar); progress.setVisibility(View.VISIBLE); String user = getUsername (); final String url = "http://travelwithfriendsar.appspot.com/travelwithfriendseditbackend?user="+user; RequestQueue queue = Volley.newRequestQueue (activity); JsonArrayRequest req = new JsonArrayRequest (url, new Response.Listener <JSONArray> () { @Override public void onResponse (JSONArray response) { JSONObject jObject = null; int j=1;
for (int i = 0; i <response. length (); i++) { try { jObject = response.getJSONObject (i); EditText title = new EditText (activity); title.setText(jObject.getString («title»)); title.setId (j); title.setOnFocusChangeListener (new OnFocusChangeListener () { @Override public void onFocusChange (View v, boolean hasFocus) { if (hasFocus) { alert.getWindow().clearFlags(WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams. FLAG_ALT_FOCUSABLE_IM); alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_AL WAYS_VISIBLE); } } }); list.addView (title); EditText subtitle = new EditText (activity); subtitle.setText(jObject.getString («subtitle»)); subtitle.setId (j++); subtitle.setOnFocusChangeListener (new OnFocusChangeListener () {
@Override public void onFocusChange (View v, boolean hasFocus) { if (hasFocus) { alert.getWindow().clearFlags(WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams. FLAG_ALT_FOCUSABLE_IM); alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_AL WAYS_VISIBLE); } } }); list.addView (subtitle); EditText description = new EditText (activity); description.setText(jObject.getString («description»)); description.setId (j++); description.setOnFocusChangeListener (new OnFocusChangeListener () { @Override public void onFocusChange (View v, boolean hasFocus) { if (hasFocus) { alert.getWindow().clearFlags(WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams. FLAG_ALT_FOCUSABLE_IM); alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_AL WAYS_VISIBLE); }
} }); list.addView (description); TextView label_checkbox = new TextView (activity); label_checkbox.setText(R.string.label_checkbox); label_checkbox.setId (j++); list.addView (label_checkbox); CheckBox cb = new CheckBox (activity); cb.setText(jObject.getString («title»)); cb.setId (j++); cb.setTextSize (0); list.addView (cb); j++; } catch (JSONException e) { e.printStackTrace (); } } progress.setVisibility (View. GONE); } }, new Response.ErrorListener () {
@Override public void onErrorResponse (VolleyError error) { } }); queue.add (req); return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { progress.setVisibility(View.VISIBLE); CheckBox cb; int j=0; for (int i = 0; i<list.getChildCount (); i++) { cb = (CheckBox)list.getChildAt (i+4); if(cb.isChecked ()) { params. put («oldtitle»+j, cb.getText().toString ()); EditText title =(EditText)list.getChildAt (i);
i=i+1; EditText subtitle =(EditText)list.getChildAt (i); i=i+1; EditText description =(EditText)list.getChildAt (i); params. put («title»+j, title.getText().toString ()); params. put («subtitle»+j, subtitle.getText().toString ()); params. put («description»+j, description.getText().toString ()); i=i+2; j++; } else { i=i+4; } } params. put («size», String.valueOf (j)); params. put («user», getUsername ()); final String url = "http://travelwithfriends-ar.appspot.com/travelwithfriendseditbackend"; RequestQueue queue = Volley.newRequestQueue (getActivity ()); StringRequest postRequest = new StringRequest(Request.Method.POST, url, new Response.Listener <String> () { @Override public void onResponse (String response) {
VisionCore.core.model=new MyModelManager (activity); VisionCore.core.model. loadPoisContinouosly = true; VisionCore.core. loadData (getActivity ()); VisionCore.startAR (getActivity ()); progress.setVisibility (View. GONE); alert. dismiss (); } }, new Response.ErrorListener () { @Override public void onErrorResponse (VolleyError error) { // error } } ){ @Override protected Map <String, String> getParams () { return params; } }; queue.add (postRequest);
} }); } private String getUsername () { AccountManager manager = AccountManager.get (activity); Account [] accounts = manager.getAccountsByType («com. google»); List <String> possibleEmails = new LinkedList <String> (); for (Account account: accounts) { possibleEmails.add(account.name); } if (!possibleEmails.isEmpty () && possibleEmails.get (0)!= null) { String email = possibleEmails.get (0); String [] parts = email. split (»@»); if (parts. length> 1) return parts [0]; } return null; } } private boolean isOnline () { ConnectivityManager cm = (ConnectivityManager) activity.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo (); return netInfo!= null && netInfo.isConnectedOrConnecting (); } } Компоновки диалогов добавления, удаления и редактирования точек интереса: add_poi. xml <?xml version=«1.0» encoding=«utf-8»? > <RelativeLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«match_parent» android: layout_height=«match_parent»> <ScrollView android: layout_width=«match_parent» android: layout_height=«match_parent»> <LinearLayout android: orientation=«vertical» android: layout_width=«match_parent» android: layout_height=«wrap_content»> <TextView android: layout_width=«match_parent» android: layout_height=«wrap_content» android: gravity=«center» android: textStyle=«bold»
android: textSize=«20sp» android: id="@+id/add_location» /> <TextView android: layout_width=«match_parent» android: layout_height=«wrap_content» android: gravity=«center» android: textStyle=«bold» android: text="@string/label_title» android: textSize=«20sp»/> <EditText android: id="@+id/add_title» android: layout_width=«match_parent» android: layout_height=«wrap_content» android: inputType=«textMultiLine» android: lines=«5» /> <TextView android: layout_width=«match_parent» android: layout_height=«wrap_content» android: gravity=«center» android: textStyle=«bold» android: text="@string/label_subtitle» android: textSize=«20sp»/> <EditText android: id="@+id/add_subtitle» android: layout_width=«match_parent»
android: layout_height=«wrap_content» android: inputType=«textMultiLine» android: lines=«5» /> <TextView android: layout_width=«match_parent» android: layout_height=«wrap_content» android: gravity=«center» android: textStyle=«bold» android: text="@string/label_description» android: textSize=«20sp»/> <EditText android: id="@+id/add_description» android: layout_width=«match_parent» android: layout_height=«wrap_content» android: inputType=«textMultiLine» android: lines=«5» /> </LinearLayout> </ScrollView> <ProgressBar android: id="@+id/add_progressBar» style=»? android: attr/progressBarStyleLarge» android: layout_width=«wrap_content» android: layout_height=«wrap_content»
android: layout_centerInParent=«true» android: visibility=«gone»/> </RelativeLayout> delete_poi. xml <?xml version=«1.0» encoding=«utf-8»? > <RelativeLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«match_parent» android: layout_height=«match_parent»> <ScrollView android: layout_width=«match_parent» android: layout_height=«match_parent»> <LinearLayout android: orientation=«vertical» android: layout_width=«match_parent» android: layout_height=«wrap_content» android: id="@+id/list_delete_poi»> </LinearLayout> </ScrollView> <ProgressBar
android: id="@+id/delete_progressBar» style=»? android: attr/progressBarStyleLarge» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_centerInParent=«true» android: visibility=«gone»/> </RelativeLayout> edit_poi. xml <?xml version=«1.0» encoding=«utf-8»? > <RelativeLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«match_parent» android: layout_height=«match_parent»> <ScrollView android: layout_width=«match_parent» android: layout_height=«match_parent»> <LinearLayout android: orientation=«vertical» android: layout_width=«match_parent» android: layout_height=«wrap_content» android: id="@+id/list_edit_poi»> </LinearLayout>
</ScrollView> <ProgressBar android: id="@+id/edit_progressBar» style=»? android: attr/progressBarStyleLarge» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_centerInParent=«true» android: visibility=«gone»/> </RelativeLayout> Класс MyModelManager отвечает за загрузку точек интереса из облака. import java.util.ArrayList; import java.util.List; import org. json. JSONArray; import org. json. JSONObject; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android. volley. VolleyError; import com.android.volley.toolbox. JsonArrayRequest; import com.android.volley.toolbox. Volley; import com.geomobile.arcore.VisionCore; import com.geomobile.arcore.model.VisionCategory;
import com.geomobile.arcore.model.VisionGeoPoi; import com.geomobile.arcore.model.VisionImage; import com.geomobile.arcore.model.VisionModelManager; import android.app.Activity; import android.content.Context; public class MyModelManager extends VisionModelManager { private VisionCategory turistCategory; private Activity activity; public MyModelManager (Activity _activity) { activity=_activity; } @Override public void loadCategories (Context ctx, String url) { VisionImage iconCat1 = new VisionImage (); iconCat1.setImageURL («category»); VisionImage iconCat2 = new VisionImage (); iconCat2.setImageURL («point_sel»); VisionImage iconCat3 = new VisionImage (); iconCat3.setImageURL («point_map»); turistCategory = new VisionCategory ();
turistCategory.setTitle («Tourist places»); turistCategory.setIcon (iconCat1); turistCategory.setSelectedIcon (iconCat2); turistCategory.setNoSelectedIcon (iconCat3); VisionCore.getCategories().add (turistCategory); } @Override public void loadPois (Context ctx, String url, boolean update) { // TODO Auto-generated method stub try { loadingPois = true; getUsrPois (); if (update) { updateNearestPois (ctx); } } catch (Exception e) { e.printStackTrace (); } finally { loadingPois = false; } } private void getUsrPois () { final List <VisionGeoPoi> newPois = new ArrayList <VisionGeoPoi> ();
final String url = "http://travelwithfriends-ar.appspot.com/travelwithfriendsbackend"; RequestQueue queue = Volley.newRequestQueue (activity); JsonArrayRequest req = new JsonArrayRequest (url, new Response.Listener <JSONArray> () { @Override public void onResponse (JSONArray response) { JSONObject jObject = null; int j=1; for (int i = 0; i <response. length (); i++) { try { jObject = response.getJSONObject (i); CustomGeoPoi poi = new CustomGeoPoi (); poi.setId(String.format («000%d», j)); poi.setTitle(jObject.getString («title»)); poi.setSubtitle(jObject.getString («subtitle»)); poi.setText(jObject.getString («description»)); poi.setLatitude(Double.parseDouble(jObject.getString («lat»))); poi.setLongitude(Double.parseDouble(jObject.getString («lon»))); poi.getCategories().add (turistCategory); newPois.add (poi); j++; } catch (Exception e) { e.printStackTrace (); }
} pois = newPois; } }, new Response.ErrorListener () { @Override public void onErrorResponse (VolleyError error) { } }); queue.add (req); } } Точки интереса представлены классом CustomGeoPoi, расширяющим класс VisionGeoPoi. import com.geomobile.arcore.model.VisionGeoPoi; import android.app.Activity; import android.content.Intent; public class CustomGeoPoi extends VisionGeoPoi { @Override public void onClick (Activity activity) { super. onClick (activity); final Intent intent = new Intent (activity, CustomGeoPoiClickActivity.class);
activity.startActivity (intent); } } При нажатии на точку интереса открывается активность, отображающая заголовок, подзаголовок и описание точки интереса. import android.app.Activity; import android. os. Bundle; import android.view.View; import android.view.View. OnClickListener; import android.widget.LinearLayout; import android. widget. TextView; import com.geomobile.arcore.VisionCore; import com.geomobile.arcore.utils.VisionUtils; public class CustomGeoPoiClickActivity extends Activity implements OnClickListener { private LinearLayout b_back; private CustomGeoPoi poi; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.activity_custom_poi_click); poi=(CustomGeoPoi)VisionCore.core.model.getSelectedPoi ();
b_back=(LinearLayout)this.findViewById(R.id.buttonBack); b_back.setBackgroundDrawable(VisionUtils.getStateListDrawable («vision_button_topleftcorner»)); b_back.setOnClickListener (this); TextView title=(TextView)this.findViewById(R.id.title); title.setText(poi.getTitle ()); TextView subtitle=(TextView)this.findViewById(R.id.subtitle); subtitle.setText(poi.getSubtitle ()); TextView description=(TextView)this.findViewById(R.id.description); description.setText(poi.getText ()); } @Override public void onClick (View v) { // TODO Auto-generated method stub if (v == b_back) { finish (); } } } <?xml version=«1.0» encoding=«utf-8»? > <RelativeLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«match_parent» android: layout_height=«match_parent»
android: orientation=«vertical»> <LinearLayout android: id="@+id/buttonBack» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_alignParentTop=«true» android: gravity=«center»> <ImageView android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: src="/storage/public/books/4d/0d/4d0da3ee-23a0-42f0-a4a502d62ab5e344/@drawable/vision_icon_back» /> </LinearLayout> <ScrollView android: layout_width=«match_parent» android: layout_height=«wrap_content» android: layout_centerInParent=«true»> <LinearLayout android: orientation=«vertical» android: layout_width=«match_parent» android: layout_height=«wrap_content» android: gravity=«center»>
<TextView android: id="@+id/title» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_margin=«30dp» android: textSize=«20sp» /> <TextView android: id="@+id/subtitle» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_margin=«30dp» android: textSize=«20sp» /> <TextView android: id="@+id/description» android: layout_width=«wrap_content» android: layout_height=«wrap_content» android: layout_margin=«30dp» android: textSize=«20sp» /> </LinearLayout> </ScrollView> </RelativeLayout> Файл манифеста Android приложения: <?xml version=«1.0» encoding=«utf-8»? >
<manifest xmlns: android="http://schemas.android.com/apk/res/android" package="com.tmsoftstudio.travelwithfriends» android: versionCode=«1» android: versionName=«1.0» > <! – Vision SDK – > <uses-feature android:name="android.hardware.camera» /> <uses-feature android:name="android.hardware.sensor.accelerometer» /> <uses-feature android:name="android.hardware.sensor.compass» /> <uses-feature android: name=«android. hardware. location» /> <uses-permission android:name="android.permission.CAMERA» /> <uses-permission android:name="android.permission.VIBRATE» /> <uses-permission android:name="com.geomobile.arcore.permission.MAPS_RECEIVE» /> <uses-permission android:name="android.permission.INTERNET» /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE» /> <uses-permission android:name="android.permission. WRITE_EXTERNAL_STORAGE» /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES» /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION» /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION» /> <uses-permission android:name="android.permission.READ_PHONE_STATE» /> <uses-permission android:name="android.permission.GET_ACCOUNTS» /> <uses-feature
android: glEsVersion=«0x00020000» android: required=«true» /> <permission android:name="com.tmsoftstudio.travelwithfriends.permission.MAPS_RECEIVE» android: protectionLevel=«signature» /> <! – Vision SDK – > <uses-sdk android: minSdkVersion=«11» android: targetSdkVersion=«25» /> <application android: allowBackup=«true» android: icon="@drawable/ic_launcher» android: label="@string/app_name» android:theme="@android:style/Theme.NoTitleBar»> <activity android:name=".MainActivity» android: label="@string/app_name» android: screenOrientation=«portrait»> <intent-filter> <action android:name="android.intent.action.MAIN» /> <category android:name="android.intent.category.LAUNCHER» /> </intent-filter>
</activity> <activity android:name=".CustomGeoPoiClickActivity»> </activity> <! – Vision SDK – > <meta-data android:name="com.google.android.geo. API_KEY» android: value=«AIzaSyBcR…» /> <activity android:name="com.geomobile.arcore.ar.VisionARView» android: screenOrientation=«portrait» /> <activity android:name="com.geomobile.arcore.ar.VisionMapsView2» /> <activity android:name="com.geomobile.arcore.ar.VisionPoiListView» /> <activity android:name="com.geomobile.arcore.ar.VisionMyLocations» /> <! – Vision SDK – > </application> </manifest> Приложение платформы Google App Engine Java обеспечивает облачное хранение, редактирование и удаление точек интереса с помощью соответствующих сервлетов. web. xml <?xml version=«1.0» encoding=«utf-8»? > <web-app xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns: web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi: schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version=«2.5»> <servlet> <servlet-name> TravelWithFriendsBackend </servlet-name> <servlet-class>com.tmsoftstudio.travelwithfriendsbackend.TravelWithFriendsBackendServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name> TravelWithFriendsBackend </servlet-name> <url-pattern> /travelwithfriendsbackend </url-pattern> </servlet-mapping> <servlet> <servlet-name> TravelWithFriendsBackendDeleteServlet </servlet-name> <servletclass>com.tmsoftstudio.travelwithfriendsbackend.TravelWithFriendsBackendDeleteServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name> TravelWithFriendsBackendDeleteServlet </servlet-name> <url-pattern> /travelwithfriendsdeletebackend </url-pattern> </servlet-mapping> <servlet> <servlet-name> TravelWithFriendsBackendEditServlet </servlet-name>
<servlet-class>com.tmsoftstudio.travelwithfriendsbackend.TravelWithFriendsBackendEditServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name> TravelWithFriendsBackendEditServlet </servlet-name> <url-pattern> /travelwithfriendseditbackend </url-pattern> </servlet-mapping> </web-app> import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; import org. json. JSONArray; import org. json. JSONException; import org. json. JSONObject; import com. google. appengine. api. datastore. DatastoreService; import com. google. appengine. api. datastore. DatastoreServiceFactory; import com. google. appengine. api. datastore. Entity; import com.google.appengine.api.datastore.PreparedQuery; import com. google. appengine. api. datastore. Query; @SuppressWarnings («serial») public class TravelWithFriendsBackendServlet extends HttpServlet {
private DatastoreService datastore = DatastoreServiceFactory.getDatastoreService (); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Query q = new Query («POI»); PreparedQuery pq = datastore.prepare (q); JSONArray jArr=new JSONArray (); for (Entity result: pq.asIterable ()) { String title = (String) result.getProperty («title»); String subtitle = (String) result.getProperty («subtitle»); String description = (String) result.getProperty («description»); String lat = (String) result.getProperty («lat»); String lon = (String) result.getProperty («lon»); JSONObject jOb = new JSONObject (); try { jOb. put («title», title); jOb. put («subtitle», subtitle); jOb. put («description», description); jOb. put («lat», lat); jOb. put («lon», lon); jArr. put (jOb); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace (); } }
resp.setCharacterEncoding («UTF-8»); resp.getWriter().write(jArr.toString ()); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String user = req.getParameter («user»); String lat = req.getParameter («lat»); String lon = req.getParameter («lon»); String title = req.getParameter («title»); String subtitle = req.getParameter («subtitle»); String description = req.getParameter («description»); Entity poi = new Entity («POI»); poi.setProperty («user», user); poi.setProperty («lat», lat); poi.setProperty («lon», lon); poi.setProperty («title», title); poi.setProperty («subtitle», subtitle); poi.setProperty («description», description); datastore. put (poi); }
} import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; import org. json. JSONArray; import org. json. JSONException; import org. json. JSONObject; import com. google. appengine. api. datastore. DatastoreService; import com. google. appengine. api. datastore. DatastoreServiceFactory; import com. google. appengine. api. datastore. Entity; import com.google.appengine.api.datastore.PreparedQuery; import com. google. appengine. api. datastore. Query; import com.google.appengine.api.datastore.Query.CompositeFilter; import com.google.appengine.api.datastore.Query.Filter; import com.google.appengine.api.datastore.Query.FilterOperator; @SuppressWarnings («serial») public class TravelWithFriendsBackendDeleteServlet extends HttpServlet { private DatastoreService datastore = DatastoreServiceFactory.getDatastoreService (); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = req.getParameter («user»); Filter propertyFilter = new Query.FilterPredicate («user», FilterOperator. EQUAL, user); Query q = new Query("POI").setFilter (propertyFilter); PreparedQuery pq = datastore.prepare (q); JSONArray jArr=new JSONArray (); for (Entity result: pq.asIterable ()) { String title = (String) result.getProperty («title»); JSONObject jOb = new JSONObject (); try { jOb. put («title», title); jArr. put (jOb); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace (); } } resp.setCharacterEncoding («UTF-8»); resp.getWriter().write(jArr.toString ()); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int size = Integer.parseInt(req.getParameter («size»)); String user = req.getParameter («user»);
for (int i = 0; i <size; i++) { String title = req.getParameter («title»+i); Filter filterTitle = new Query.FilterPredicate («title», FilterOperator. EQUAL, title); Filter filterUser = new Query.FilterPredicate («user», FilterOperator. EQUAL, user); CompositeFilter filter = Query.CompositeFilterOperator.and (filterTitle, filterUser); Query q = new Query("POI").setFilter (filter); PreparedQuery pq = datastore.prepare (q); for (Entity result: pq.asIterable ()) { datastore.delete(result.getKey ()); } } } } import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; import org. json. JSONArray; import org. json. JSONException; import org. json. JSONObject; import com. google. appengine. api. datastore. DatastoreService; import com. google. appengine. api. datastore. DatastoreServiceFactory; import com. google. appengine. api. datastore. Entity;
import com.google.appengine.api.datastore.PreparedQuery; import com. google. appengine. api. datastore. Query; import com.google.appengine.api.datastore.Query.CompositeFilter; import com.google.appengine.api.datastore.Query.Filter; import com.google.appengine.api.datastore.Query.FilterOperator; @SuppressWarnings («serial») public class TravelWithFriendsBackendEditServlet extends HttpServlet { private DatastoreService datastore = DatastoreServiceFactory.getDatastoreService (); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String user = req.getParameter («user»); Filter propertyFilter = new Query.FilterPredicate («user», FilterOperator. EQUAL, user); Query q = new Query("POI").setFilter (propertyFilter); PreparedQuery pq = datastore.prepare (q); JSONArray jArr=new JSONArray (); for (Entity result: pq.asIterable ()) { String title = (String) result.getProperty («title»); String subtitle = (String) result.getProperty («subtitle»); String description = (String) result.getProperty («description»); JSONObject jOb = new JSONObject (); try { jOb. put («title», title);
jOb. put («subtitle», subtitle); jOb. put («description», description); jArr. put (jOb); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace (); } } resp.setCharacterEncoding («UTF-8»); resp.getWriter().write(jArr.toString ()); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int size = Integer.parseInt(req.getParameter («size»)); String user = req.getParameter («user»); for (int i = 0; i <size; i++) { String oldtitle = req.getParameter («oldtitle»+i); Filter filterTitle = new Query.FilterPredicate («title», FilterOperator. EQUAL, oldtitle); Filter filterUser = new Query.FilterPredicate («user», FilterOperator. EQUAL, user); CompositeFilter filter = Query.CompositeFilterOperator.and (filterTitle, filterUser); Query q = new Query("POI").setFilter (filter); PreparedQuery pq = datastore.prepare (q); String title = req.getParameter («title»+i); String subtitle = req.getParameter («subtitle»+i);
String description = req.getParameter («description»+i); for (Entity result: pq.asIterable ()) { result.setProperty («title», title); result.setProperty («subtitle», subtitle); result.setProperty («description», description); datastore. put (result); } } } } Готовое приложение можно скачать в Google Play https://play.google.com/store/apps/details?id=com.tmsoftstudio.travelwithfriends. Wikitude Wikitude SDK дополненной реальности сочетает в себе технологии 3D-трекинга (на основе Simultaneous Localisation and Mapping (SLAM)), распознавания образов, слежения и геолокации AR для мобильных устройств, планшетов и смарт-очков. Доступны также расширения для Unity, Cordova, Titanium и Xamarin. После регистрации на сайте Wikitude (https://www.wikitude.com/) можно открыть веб приложение Wikitude Studio (http://studio.wikitude.com/dashboard). Панель Wikitude Studio позволяет создать проект, который будет служить контейнером для целевых изображений, используемых в качестве эталона для распознавания образов, при их появлении в камере устройства. После добавления целевого изображения в проект, выберете тип дополненной реальности: Text – отображает текст поверх целевого изображения. Image – отображает изображение поверх целевого изображения. Button – отображает кнопку поверх целевого изображения, при нажатии на которую можно перейти по URL адресу.
HTML Widget – HTML код, отображаемый поверх целевого изображения. 3D Model – отображает 3D модель поверх целевого изображения. Предварительно 3D модель конвертируется из FBX (.fbx) формата в Wikitude 3D Format (.wt3) с помощью Wikitude 3D Encoder. Video – отображает видео поверх целевого изображения. После выбора типа дополненной реальности и определения свойств объекта дополненной реальности, отображаемого поверх целевого изображения, нажмем кнопку Preview. Установим на Android устройство приложение Wikitude App из Google Play. Откроем приложение и в меню выберем Developer. Введем логин и пароль в Wikitude Studio. Выберем проект и наведем камеру устройства на целевое изображение – увидим дополненную реальность. Нажмем кнопку Export в Wikitude Studio веб браузера. Здесь можно экспортировать приложение для доступа из Wikitude App и Wikitude Hosting, загрузить приложение для хранения на собственном сервере, а также загрузить проект приложения для разработки в среде IDE на основе Wikitude SDK. При использовании Wikitude Hosting или собственного хостинга, URL адрес приложения вводится в приложении Wikitude App, в разделе Developer, в Wikitude Studio, для загрузки приложения. Для разработки приложения в среде Android Studio, скачаем проект приложения на основе шаблона Android App Template и импортируем его в Android Studio. После исправления ошибок в настройках проекта, приложение можно запустить на Android устройстве. Скачаем Wikitude SDK (https://www.wikitude.com/download/) и получим лицензионный ключ, который вставим в protected static final String WIKITUDE_SDK_KEY = «»; файла WikitudeSDKConstants чтобы убрать водяные знаки из вида камеры. В результате все равно останется водяной знак Trial. В папке libs проекта находится архивный файл Wikitude SDK. В папке assets проекта находится то самое приложение, которое скачивается при выборе Self-Hosting в Wikitude Studio веб браузера. При запуске приложения сначала запускается активность RuntimePermissionRequestActivity, в которой запрашивается разрешение android.permission.CAMERA, после этого запускается активность SampleCamActivity, расширяющая класс AbstractArchitectCamActivity. В классе SampleCamActivity в переопределенном методе getARchitectWorldPath возвращается путь к файлу index.html приложения папки assets. Если вы хотите загрузить приложение с сервера, в строке WORLD_PATH укажите URL адрес файла index.html. В классе SampleCamActivity в переопределенном методе getWikitudeSDKLicenseKey возвращается лицензионный ключ, указанный в строке WikitudeSDKConstants. WIKITUDE_SDK_KEY.
В классе AbstractArchitectCamActivity создается экземпляр класса StartupConfiguration Wikitude SDK, в конструкторе которого указывается лицензионный ключ. Также создается основной вид приложения, представленный классом ArchitectView Wikitude SDK. В классе StartupConfiguration лицензионный ключ передается в строковое поле класса. package com.wikitude.architect; import java.util.ArrayList; public class StartupConfiguration { private final String a; private String b; private final int c; private final CameraPosition d; public static final String ORIGIN_PHONEGAP = «CORDOVA»; public static final String ORIGIN_TITANIUM = «TITANIUM»; public static final String ORIGIN_XAMARIN = «XAMARIN»; public static final String ORIGIN_DEFAULT = «JAVASCRIPT_API»; protected String getOrigin () { return this. a ()? this. b: «JAVASCRIPT_API»; } private boolean a () { ArrayList <String> arrayList = new ArrayList <String> (); arrayList.add («CORDOVA»); arrayList.add («TITANIUM»); arrayList.add («XAMARIN»); arrayList.add («JAVASCRIPT_API»); return this. b!= null && arrayList.contains (this. b); } public void setOrigin (String string) {
this. b = string; } public StartupConfiguration (String string) { this (string, 3); } public StartupConfiguration (String string, int n2) { this (string, n2, CameraPosition.DEFAULT); } public StartupConfiguration (String string, int n2, CameraPosition cameraPosition) { this. a = string this. c = n2; this. d = cameraPosition; } public String getKey () { return this. a; } public int getFeatures () { return this. c; } public CameraPosition getCameraMode () { return this. d; } public static enum CameraPosition { DEFAULT, FRONT, BACK;
private CameraPosition () { } } public static interface Features { public static final int Geo = 1; public static final int Tracking2D = 2; } } В классе ArchitectView в методе onCreate лицензионный ключ передается из класса StartupConfiguration в строковое поле класса ArchitectView и в методе onPostCreate вызывается нативный метод setKey(this.getContext().getPackageName (), startupConfiguration.getKey ()). Далее имя пакета и лицензионный ключ сравниваются в нативной библиотеке папки jni Wikitude SDK. package com.wikitude.architect; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.hardware.Camera; import android.hardware.Sensor; import android.hardware.SensorManager; import android. location. LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net. Uri; import android. os. Build; import android. os. Bundle; import android.provider.Settings; import android.text.Html; import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.util.AttributeSet; import android. util. JsonWriter; import android. util. Log; import android.view. OrientationEventListener; import android.view.View; import android.view.ViewGroup;
import android.widget.FrameLayout; import android. widget. TextView; import android.widget.Toast; import com.wikitude.architect.ArchitectWebView; import com.wikitude.architect.BrowserActivity; import com.wikitude.architect.CallbackHandler; import com.wikitude.architect.Gameplay3dView; import com.wikitude.architect.HtmlDrawableInterface; import com.wikitude.architect.HtmlRenderManager; import com.wikitude.architect. IArchitectCallbackListener; import com.wikitude.architect.PlatformBridge; import com.wikitude.architect.PlatformCamera; import com.wikitude.architect.PluginManager; import com.wikitude.architect.SensorService; import com.wikitude.architect.ServiceManager; import com.wikitude.architect.StartupConfiguration; import com.wikitude.architect.plugin.Plugin; import com.wikitude.tools.device. features. MissingDeviceFeatures; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;
import java.io.StringWriter; import java.io.Writer; import java.net. URL; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Scanner; import java.util.regex.Pattern; public class ArchitectView extends FrameLayout implements e, t{ static final String a = ArchitectView.class.getSimpleName (); private static final String i = «core»; private x j = null; private PlatformCamera k = null; private static final String l = "https://sdktracking.wikitude.com"; ArchitectWebView b; private ViewState m; Activity c; static boolean d; private ServiceManager n; Gameplay3dView e; private String o = «JAVASCRIPT_API»; private boolean p = false; private AssetManager q;
private static final String r = "libarchitect.so»; private long nativePtr; private ArchitectInitializeException s = null; private static final Properties t; private PlatformBridge u; private HtmlDrawableInterface v; private HtmlRenderManager w; CallbackHandler f; private m x; private o y; private StartupConfiguration z; private StartupConfiguration.CameraPosition A; private NetworkStateReceiver B; private PluginManager C; private List <Plugin> D = new ArrayList <Plugin> (); private String E; public String g = «»; public ArchitectView (Context context, AttributeSet attributeSet, int n2) { super (context, attributeSet, n2); this. a (context); } public ArchitectView (Context context, AttributeSet attributeSet) { this (context, attributeSet, 0); } public ArchitectView (Context context) { super (context);
this. a (context); } @Override public void onCameraOpen (Camera camera) { Camera.Parameters parameters = camera.getParameters (); this. j.a (camera); this.j.setCameraParams (parameters); this. k = new PlatformCamera(this.nativePtr); this.j.setPlatformCamera (this. k); this. e.a (parameters); this. j.a ((Object) parameters); if (parameters == null) { this. m = ViewState.initFailed; throw new CamNotAccessibleException («Could not get camera parameters»); } this.f.a(parameters.getHorizontalViewAngle (), parameters.getVerticalViewAngle ()); } @Override public void onCameraReleased () { } @Override public void onCameraOpenAbort () { } private boolean d () { this. p = ArchitectView. e (); try {
if (t.getProperty («archive. type»)!= null && t.getProperty («archive. type»).equals («jar»)) { PackageManager packageManager = this.getContext().getPackageManager (); String string = packageManager.getApplicationInfo((String)this.getContext().getPackageName (), (int) 0).dataDir; String string2 = «»; String string3 = System.getProperty("os.arch»); String string4 = string3.substring (0, 3).toUpperCase (); string2 = string4.equals («X86») || string4.equals («I68»)? «x86»: (string4.equals («AAR»)? «arm64-v8a»: (this. p? «armeabi-v7a»: «armeabi»)); File file = new File (string, "libarchitect.so»); String string5 = "/libs/" + string2 + "/" + "libarchitect.so»; ArchitectView. a (file, string5); try { System.load(file.getAbsolutePath ()); } catch (UnsatisfiedLinkError var8_10) { if (string2.equals («arm64-v8a»)) { string5 = "/libs/armeabi-v7a/libarchitect.so»; file.delete (); file = new File (string, "libarchitect.so»); ArchitectView. a (file, string5); System.load(file.getAbsolutePath ()); } throw var8_10; } file.delete (); } else {
System. loadLibrary («architect»); } d = true; return true; } catch (IOException var1_2) { var1_2.printStackTrace (); } catch (PackageManager.NameNotFoundException var1_3) { var1_3.printStackTrace (); } return false; } private static void a (File file, String string) throws FileNotFoundException, IOException { InputStream inputStream = ArchitectWebView.class.getResourceAsStream (string); if (inputStream == null) { return; } FileOutputStream fileOutputStream = new FileOutputStream (file); int n2 = 0; byte [] arrby = new byte [8192]; while ((n2 = inputStream.read (arrby))> -1) { fileOutputStream. write (arrby, 0, n2); } fileOutputStream.close (); inputStream.close ();
} /* * WARNING – Removed try catching itself – possible behaviour change. */ private static boolean e () { boolean bl; bl = false; String string = System.getProperty("os.arch»); String string2 = string.substring (0, 3).toUpperCase (); if (string2.equals («X86») || string2.equals («I68») || string2.equals («AAR»)) { bl = true; } else { Scanner scanner = null; try { scanner = new Scanner (new FileInputStream (»/proc/cpuinfo»)); while (scanner. hasNextLine ()) { if (!bl && scanner.findInLine («neon»)!= null) { bl = true; } scanner.nextLine (); } } catch (Exception var4_4) {} finally { if (scanner!= null) { scanner.close ();
} } } return bl; } private void a (Context context) { if (!this. d ()) { this. m = ViewState.initFailed; this. s = new LibraryLoadFailedException («Architect native Library could not be loaded»); return; } this. c = (Activity)this.getContext (); this. e = new Gameplay3dView (context); this.e.setZOrderMediaOverlay (true); this. j = Build.VERSION.SDK_INT> = 21? new c (this, context): (this. p && ArchitectView. g ()? new c (this, context): new d (this, context)); this. n = new ServiceManager (this. c, this. j, this); this. n.a (new OrientationEventListener (context) { public void onOrientationChanged (int n2) { ArchitectView.this. e.b (); } }); this. b = new ArchitectWebView (context, this); this. f = new CallbackHandler (this. c, this); this. u = new PlatformBridge ((Context) this. c, this. b); this. v = new HtmlDrawableInterface ((Context) this. c, this. b); this.b.addJavascriptInterface ((Object) this. u, this. u.a ());
this.b.addJavascriptInterface ((Object) this. v, this. v.a ()); this. b. enableJavascript (); this. y = new o ((Activity) context, new r (), this. f); this.e.setRenderListener (this. y); this. x = new m ((Activity) context, this. f); FrameLayout frameLayout = new FrameLayout (context); frameLayout.setLayoutParams((ViewGroup.LayoutParams) new FrameLayout.LayoutParams (-1, 1)); this. w = new HtmlRenderManager ((Context) this. c, frameLayout, this. v); this. f.a (this. n); this. f.a (this. w); this. f.a (this. y); this. f.a (this. x); this.setBackgroundColor (-16777216); this.removeAllViews (); this. a ((View) frameLayout); this.a(this.j.getView ()); this.j.addViewToLayout ((View) this. e); this. a ((View) this. b); this.createNative (); } private static Properties f () { Properties properties = new Properties (); try { properties.load(ArchitectView.class.getClassLoader().getResourceAsStream("assets/sdk.properties» )); }
catch (Exception var1_1) { Log. w ((String) a, (String) «Cannot open properties file, use defaults»); } return properties; } private static boolean g () { boolean bl; try { File [] arrfile = new File("/sys/devices/system/cpu/").listFiles (new FileFilter () { @Override public boolean accept (File file) { if (Pattern.matches («cpu [0—9] +», file.getName ())) { return true; } return false; } }); bl = arrfile. length> 1; } catch (Exception var1_1) { bl = false; } return bl; } private void a (View view) { ViewGroup.LayoutParams layoutParams = view.getLayoutParams ();
if (layoutParams == null && (layoutParams = this.generateDefaultLayoutParams ()) == null) { throw new IllegalArgumentException («generateDefaultLayoutParams () cannot return null»); } super.addView (view, -1, layoutParams); } public void setLocation (double d2, double d3, double d4, float f2) { if (this. n!= null && this. n.a ()!= null && this. n.a ().h ()) { this.n.a().setLocation (d2, d3, d4, «FAKE», f2, System.currentTimeMillis ()); } } public void setLocation (double d2, double d3, float f2) { if (this. n!= null && this. n.a ()!= null && this. n.a ().h ()) { this.n.a().setLocationAltitudeUnknown (d2, d3, «FAKE», f2, System.currentTimeMillis ()); } } public void registerUrlListener (ArchitectUrlListener architectUrlListener) { if (this. b!= null) { this.b.registerUrlHandler (architectUrlListener); } } public void registerWorldLoadedListener (ArchitectWorldLoadedListener architectWorldLoadedListener) { if (this. b!= null) { this.b.registerWorldLoadedHandler (architectWorldLoadedListener); } } public void onCreate (String string) {
this. onCreate (new StartupConfiguration (string)); } public void onCreate (StartupConfiguration startupConfiguration) { this. z = startupConfiguration; this. o = startupConfiguration.getOrigin (); if (this. m == ViewState.initFailed) { Log. e ((String) a, (String) «delayed exception from constructor», (Throwable) this. s); throw this. s; } if (startupConfiguration!= null && startupConfiguration.getKey ()!= null) { this. E = startupConfiguration.getKey (); int n2 = startupConfiguration.getFeatures (); if ((ArchitectView.getSupportedFeaturesForDevice(this.getContext ()) & n2)!= n2) { throw new MissingFeatureException («Required feature set is not supported»); } this. m = ViewState.constructed; } else { Log. e ((String) a, (String) «App key not set. Switch to failsave state»); this.removeAllViews (); } this. A = this.z.getCameraMode (); } public StartupConfiguration.CameraPosition getCurrentCamera () { return this. A; } public void setCameraPositionSimple(StartupConfiguration.CameraPosition cameraPosition) {
this. A = cameraPosition; } private long getTrackingId () { String string = Settings.Secure.getString((ContentResolver)this.getContext().getContentResolver (), (String) «android_id»); if (string == null || string.trim ().equals (»»)) { string = Build.BRAND + Build.MODEL; } return («wiki» + string + «tude»).hashCode (); } private void h () { SharedPreferences sharedPreferences = this.getContext().getSharedPreferences («WTTRACKED», 0); sharedPreferences. edit ().putLong («value», this.getTrackingId()).commit (); } private boolean i () { SharedPreferences sharedPreferences = this.getContext().getSharedPreferences («WTTRACKED», 0); return sharedPreferences.getLong («value», 0) == this.getTrackingId (); } public void onPostCreate () { if (this. m!= ViewState.constructed) { throw new IllegalStateException («This method only has to be called right after the object has been constructed»); } this. m = ViewState. onPostCreate; this. q = this.getResources().getAssets (); File file = new File(a.d(this.getContext ()), «core»);
if (!file. exists ()) { file.mkdir (); } String string = ArchitectView.getBuildProperty («build. hardware»); String string2 = ArchitectView.getBuildProperty("cloud.tracker. url»); if (string!= null) { this. g = string; } this.createARchitectCore (this. f, this. q, file.getAbsolutePath (), this. g, this.z.getFeatures ()); String string3 = this.getContext().getPackageName (); this.setKey (string3, this. E); n.a(this.getContext (), this, this.getShowIcon (), this.getShowSplash ()); if (this. A == StartupConfiguration.CameraPosition.FRONT) { this.setCameraMirroring (true); } if (string2!= null) { this.setCloudTrackerURL (string2); } if (this.getDoTracking () &&!this. i ()) { String string4 = this.getSdkVersion (); String string5 = this.trackingOriginIdentifierFromString (this. o); String string6 = System.getenv («XAMARIN_BUILD_ID»); if (string6!= null) { string5 = this.trackingOriginIdentifierFromString («XAMARIN»); } String string7 = this.trackingPlatformIdentifierFromString («android»);
this.sendUsageTrackingRequest (string4, string5, string7); String string8 = this.getLicenseType (); this. a (string8); this. h (); } this. j (); this. k (); this. e.c (); } private void a (String string) { if (string. equalsIgnoreCase («invalid»)) { ((TextView) new http://www.wikitude.com/external/doc/documentation/latest/android/triallicense.html#free-triallicense\">MoretTitle ((CharSequence) «License key is missing or AlertDialog.Builder((Context)this.c).seomHtml ((String)» <p> Please add a valid license key to your app. <br> <br> <a href=\"invalid").setMessage((CharSequence)Html.fr information</a></p>")).show().findViewById(16908299)).setMovementMethod(LinkMovementM ethod.getInstance ()); Log. e ((String) a, (String) «License key is missing or invalid. Please add a valid license key to your app.»); } } private void j () { ConnectivityManager connectivityManager = (ConnectivityManager)this.getContext().getSystemService («connectivity»); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo (); if (networkInfo == null || !networkInfo.isConnectedOrConnecting ()) { this.setNetworkStatus(NetworkStatus.NONE.name ()); } else if (networkInfo.getType () == 1) { this.setNetworkStatus(NetworkStatus.WIFI.name ());
} else { this.setNetworkStatus(NetworkStatus.MOBILE.name ()); } this. B = new NetworkStateReceiver (this); this.getContext().registerReceiver ((BroadcastReceiver) this. B, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE»)); } private void k () { this.f.c(this.nativePtr); this.e.a(this.nativePtr); this.n.a(this.nativePtr); this.u.connectNative(this.nativePtr); this.v.connectNative(this.nativePtr); } public void onResume () { if (this. m!= ViewState.initFailed) { this. m = ViewState. onResume; this. j. onResume (); this. e. onResume (); this. n.c (); this.u.resume (); this. b. onResume (); this.x.resume (); this.y.resume (); } else { Log. w ((String) a, (String) «Can’t resume activity, since intialization failed», (Throwable) this. s); }
} public void onPause () { if (this. m!= ViewState. onResume) { throw new IllegalStateException («onResume () needs to be called before this method is called in the appropriate lifecycle method»); } this. j. onPause (); this. m = ViewState. onPause; this.u.pause (); this. b. onPause (); this. n.d (); this.x.pause (); this.y.pause (); this. e. onPause (); } /* * Unable to fully structure code * Enabled aggressive block sorting * Enabled unnecessary exception pruning * Enabled aggressive exception aggregation * Lifted jumps to return sites */ public void onDestroy () { if (this. m!= ViewState.initFailed) { this.getContext ().unregisterReceiver ((BroadcastReceiver) this. B); this.removeView ((View) this. b); if (this. C!= null) {
this.C.destroyNative (); } this.u.destroy (); this. v. onDestroy (); this. b. onDestroy (); this. n.e (); this.x.destroy (); this.y.destroy (); this. e.a (); if (this. k!= null) { this. k.a (); } this. n = null; var1_1 = new Thread (new Runnable () { @Override public void run () { ArchitectView.this.destroyEngine (); ArchitectView.this.destroyNative (); } }); try { var1_1.start (); if (!var1_1.isAlive ()) ** GOTO lbl25 var1_1.join (); } catch (InterruptedException var2_2) {
throw new RuntimeException («Could not join pause/destroy thread, please check life-cycle calls»); } } else { Log. w ((String) ArchitectView. a, (String) «Nothing was initialized, nothing will be destroyed», (Throwable) this. s); } lbl25: // 3 sources: this. l (); } private void l () { try { f.a (a.d ((Context) this. c)); } catch (Exception var1_1) { Log. e ((String) «Wikitude SDK onDestroy», (String)String.valueOf(var1_1.getMessage ())); } } public void callJavascript (String string) { this.b.callJavaScript (string); } public void load (String string) throws IOException { if (string.startsWith («http») || string.startsWith («file:»)) { this. b. loadArchitectFileFromUrl (string); } else { this. b. loadArchitectFileFromAssets (string); } }
public void a () { } void b () { this.u.callSync("{\"is\":\"AR.i.contextInterface.destroyAll\»}»); } public void onLowMemory () { this. f.a (); } public static String getCacheDirectoryAbsoluteFilePath (Context context) { return ArchitectWebView.getCacheDirectoryRoot(context).getAbsolutePath (); } public static int getSupportedFeaturesForDevice (Context context) { boolean bl; int n2 = 0; if (Build.VERSION.SDK_INT> = 17) { bl = context.getPackageManager().hasSystemFeature("android.hardware.camera.any»); } else { boolean bl2 = bl = context.getPackageManager().hasSystemFeature("android.hardware.camera.front») || context.getPackageManager().hasSystemFeature("android.hardware.camera»); } if (ArchitectView. b (context)> = 131072.0f && bl) { if (ArchitectView. e ()) { n2 = 2; } LocationManager locationManager = (LocationManager)context.getSystemService («location»); SensorManager sensorManager = (SensorManager)context.getSystemService («sensor»);
if (locationManager!= null && locationManager.getAllProviders ()!= null && locationManager.getAllProviders().size ()> 0 && sensorManager!= null && sensorManager.getDefaultSensor (2)!= null) { n2 |= 1; } } return n2; } public static boolean isDeviceSupported (Context context) { return (ArchitectView.getSupportedFeaturesForDevice (context) & 3) == 3; } public static MissingDeviceFeatures isDeviceSupported (Context context, int n2) { boolean bl; String string = System.getProperty("line.separator»); String string2 = «»; if (Build.VERSION.SDK_INT> = 17) { bl = context.getPackageManager().hasSystemFeature("android.hardware.camera.any»); } else { boolean bl2 = bl = context.getPackageManager().hasSystemFeature("android.hardware.camera.front») || context.getPackageManager().hasSystemFeature("android.hardware.camera»); } if (!bl) { string2 = string2 + "– Camera» + string; } if (ArchitectView. b (context) <131072.0f) { string2 = string2 + "– OpenGLES version 2.0.+" + string; }
if ((n2 & 1) == 1) { LocationManager locationManager = (LocationManager)context.getSystemService («location»); SensorManager sensorManager = (SensorManager)context.getSystemService («sensor»); if (locationManager == null || locationManager.getAllProviders () == null || locationManager.getAllProviders().size () <= 0) { string2 = string2 + "– GPS / Location Provider» + string; } if (sensorManager == null || sensorManager.getDefaultSensor (2) == null) { string2 = string2 + "– Compass» + string; } } if ((n2 & 2) == 2 &&!ArchitectView. e ()) { string2 = string2 + "– Chipset supporting NEON» + string; } return new MissingDeviceFeatures(!string2.isEmpty (), «The device is missing following features:" + string + string2); } public String getSdkVersion () { return d? this.getArchitectVersion (): «N.A.»; } public void setCullingDistance (float f2) { if (this. e!= null) { this.e.setCullingDistance (f2); } } public float getCullingDistance () { return this. e!= null? this.e.getCullingDistance (): Float.MAX_VALUE;
} public void clearAppCache () { ArchitectWebView.clearCache ((Context) this. c, 0); } private static float b (Context context) { ActivityManager activityManager = (ActivityManager)context.getSystemService («activity»); ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo (); return configurationInfo.reqGlEsVersion; } private static boolean a (ApplicationInfo applicationInfo) { return applicationInfo!= null && 2 == (2 & applicationInfo. flags); } public void registerSensorAccuracyChangeListener (SensorAccuracyChangeListener sensorAccuracyChangeListener) { this. n.a (sensorAccuracyChangeListener); } public void unregisterSensorAccuracyChangeListener (SensorAccuracyChangeListener sensorAccuracyChangeListener) { this. n.b (sensorAccuracyChangeListener); } public void captureScreen (int n2, final FileOutputStream fileOutputStream) throws IllegalArgumentException { int n3 = 100; CaptureScreenCallback captureScreenCallback = new CaptureScreenCallback () { @Override public void onScreenCaptured (Bitmap bitmap) { if (bitmap!= null && ArchitectView.this.getContext ()!= null) {
bitmap.compress(Bitmap.CompressFormat.PNG, Math.max (0, Math. min (100, 100)), (OutputStream) fileOutputStream); } } }; this.captureScreen (n2, captureScreenCallback); } public void captureScreen (int n2, final CaptureScreenCallback captureScreenCallback) throws IllegalArgumentException { switch (n2) { case 0: { this. e.a (captureScreenCallback); break; } case 1: { CaptureScreenCallback captureScreenCallback2 = new CaptureScreenCallback () { @Override public void onScreenCaptured (final Bitmap bitmap) { if (ArchitectView.this. b!= null) { ArchitectView.this.c.runOnUiThread (new Runnable () { @Override public void run () { Bitmap bitmap3; try { bitmap3 = Bitmap.createBitmap((int)bitmap.getWidth (), (int)bitmap.getHeight (), (Bitmap.Config)bitmap.getConfig ()); Canvas canvas = new Canvas (bitmap3); canvas. drawBitmap (bitmap, new Matrix (), null);
Bitmap bitmap2 = Bitmap.createBitmap((int)ArchitectView.this.b.getWidth (), (int)ArchitectView.this.b.getHeight (), (Bitmap.Config)Bitmap.Config.ARGB_8888); Canvas canvas2 = new Canvas (bitmap2); ArchitectView.this.b.layout (0, 0, ArchitectView.this.b.getWidth (), ArchitectView.this.b.getHeight ()); ArchitectView.this. b. draw (canvas2); canvas. drawBitmap (bitmap2, new Matrix (), null); } catch (Exception var2_3) { Log. w ((String) ArchitectView. a, (String) «Can’t capture screen – will return null», (Throwable) var2_3); bitmap3 = null; } try { captureScreenCallback. onScreenCaptured (bitmap3); } catch (Exception var2_4) { Log. e ((String) ArchitectView. a, (String) «Capture screen callback threw an exception», (Throwable) var2_4); } } }); } } }; this. e.a (captureScreenCallback2); break; }
default: { throw new IllegalArgumentException («captureMode must be listed in CaptureScreenCallback.CAPTURE_MODE_*»); } } } public void setDisplayMode3dInCore (boolean bl) { block4: { try { StringWriter stringWriter = new StringWriter (); JsonWriter jsonWriter = new JsonWriter ((Writer) stringWriter); jsonWriter.beginObject (); if (bl) { jsonWriter.name("3dmode").value («3d»); } else { jsonWriter.name("3dmode").value («2d»); } jsonWriter. endObject (); jsonWriter.close (); Log. i ((String) «DEBUG», (String) («JSON:" + stringWriter.toString ())); this.setHardwareConfiguration(stringWriter.toString ()); } catch (IOException var2_3) { if (h) break block4; throw new AssertionError (); } }
} public void setFlashEnabled (boolean bl) { this.j.setFlashEnabled (bl); } public static String getBuildProperty (String string) { return t.getProperty (string); } void a (String string, boolean bl) { if (string == null || string. length () <4) { Toast.makeText((Context)this.getContext (), (CharSequence) «invalid URL provided», (int)1).show (); return; } if (bl) { Uri uri; try { new URL(string).getProtocol (); uri = Uri.parse ((String) string); } catch (Exception var4_5) { uri = http://"rse ((String) (»Uri.pa + string)); } Intent intent = new Intent("android.intent.action.VIEW»); intent.setData (uri); if (string.startsWith («file»)) { Intent intent2; PackageManager packageManager = this.c.getPackageManager ();
List list = packageManager. queryIntentActivities (intent2 = new http://")),EW», Intent("android.intent.action.VIrse ((String)»Uri.pa 0); if (!list.isEmpty ()) { packageManager.getLaunchIntentForPackage(((ResolveInfo)list.get((int)0)).activityInfo.packageNa me); ComponentName componentName = new ComponentName(((ResolveInfo)list.get((int)0)).activityInfo.packageName, ((ResolveInfo)list.get((int)0)).activityInfo.name); intent.addCategory("android.intent.category.BROWSABLE»); intent.setComponent (componentName); this.getContext().startActivity (intent); } } else { this.getContext().startActivity (intent); } } else { Intent intent = new Intent(this.getContext (), BrowserActivity.class); intent. putExtra («URL», string); this.getContext().startActivity (intent); } } boolean c () { return this.b.isActivityFinishing (); } String getLastLoadedUrl () { return this.b.getLastLoadedUrl (); } void setCameraZoomLevel (float f2) {
this.j.setZoomLevel (f2); Camera.Parameters parameters = this.j.getCameraParameter (); if (parameters.isZoomSupported ()) { int n2 = (Integer)parameters.getZoomRatios().get(parameters.getZoom ()); float f3 = (float)Math.toDegrees (2.0 * Math.atan (100.0 * Math.tan(Math.toRadians(parameters.getHorizontalViewAngle ()) / 2.0) / (double) n2)); float f4 = (float)Math.toDegrees (2.0 * Math.atan (100.0 * Math.tan(Math.toRadians(parameters.getVerticalViewAngle ()) / 2.0) / (double) n2)); this. f.a (f3, f4); } else { this.f.a(parameters.getHorizontalViewAngle (), parameters.getVerticalViewAngle ()); } this. e.a (parameters); this.j.setCameraParams (parameters); } void setCameraFocusMode (CameraFocusMode cameraFocusMode) { this.j.setFocusMode (cameraFocusMode); } void setCameraPosition(StartupConfiguration.CameraPosition cameraPosition) { this. A = cameraPosition; this. j.f (); Camera.Parameters parameters = this.j.getCameraParameter (); if (parameters == null) { this. m = ViewState.initFailed; throw new CamNotAccessibleException («Could not get camera parameters»); } this.f.a(parameters.getHorizontalViewAngle (), parameters.getVerticalViewAngle ());
this. e.a (parameters); this.j.setCameraParams (parameters); } public CameraFocusMode getCameraFocusMode () { return this.j.getFocusMode (); } public float getCameraZoomLevel () { return this.j.getZoomLevel (); } public float getCameraMaxZoomLevel () { return this.j.getMaxZoomLevel (); } public String [] getAvailableCameraFocusModes () { return this.j.getAvailableCameraFocusModes (); } public String [] getAvailableCameraPositions () { return this.j.getAvailableCameraPositions (); } public boolean registerPlugin (Plugin plugin) { this. m (); if (this.C.registerPluginInCore (plugin)) { this.D.add (plugin); return true; } return false; }
public boolean registerNativePlugins (String string) { return this.registerNativePlugins (string, «»); } public boolean registerNativePlugins (String string, String string2) { if (this. m. ordinal ()> = 1 && this. m!= ViewState.initFailed) { this. m (); try { System. loadLibrary (string); } catch (UnsatisfiedLinkError var3_3) { Log. e ((String) a, (String) («Couldn’t load plugins of library «» + string + «» because the library couldn’t be loaded. Following error was thrown: " + var3_3.toString ())); return false; } long [] arrl = this.C.createNativePlugins (string2); if (!this.C.registerNativePluginsInCore (arrl)) { Log. e ((String) a, (String) («A native plugin of library „“ + string + „“ couldn’t be registered. All other plugins of this library were unregistered.»)); return false; } } else { Log. e ((String) a, (String) («Registration of plugins in library „“ + string + „“ was canceled. Wrong lifecycle state detected.»)); return false; } return true; } private void m () {
if (this. C == null) { this. C = new PluginManager (); this.C.connectNative(this.nativePtr); } } List <Plugin> getRegisteredPlugins () { return this. D; } @Override public native void createNative (); @Override public native void destroyNative (); private native void createARchitectCore (IArchitectCallbackListener var1, AssetManager var2, String var3, String var4, int var5); native void destroyEngine (); native void loadingFinished (); native String getArchitectVersion (); native void loadingStarted (); native void setNetworkStatus (String var1); native void setCameraMirroring (boolean var1); private native void setKey (String var1, String var2) private native String getLicenseType (); private native String getCustomerMail (); private native boolean getShowIcon (); private native boolean getShowSplash (); private native boolean getDoTracking (); private native void setHardwareConfiguration (String var1);
private native void setCloudTrackerURL (String var1); private native boolean sendUsageTrackingRequest (String var1, String var2, String var3); private native String trackingPlatformIdentifierFromString (String var1); private native String trackingOriginIdentifierFromString (String var1); @Override public long getNativePointer () { return this.nativePtr; } static { t = ArchitectView. f (); } public static interface CaptureScreenCallback { public static final int CAPTURE_MODE_CAM = 0; public static final int CAPTURE_MODE_CAM_AND_WEBVIEW = 1; public void onScreenCaptured (Bitmap var1); } public static interface ArchitectWorldLoadedListener { public void worldWasLoaded (String var1); public void worldLoadFailed (int var1, String var2, String var3); } public static interface ArchitectUrlListener { public boolean urlWasInvoked (String var1); } public static interface SensorAccuracyChangeListener { public void onCompassAccuracyChanged (int var1); }
static class NetworkStateReceiver extends BroadcastReceiver { private static final String a = «NetworkStateReceiver»; private final ArchitectView b; public NetworkStateReceiver (ArchitectView architectView) { this. b = architectView; } public void onReceive (Context context, Intent intent) { Log. d ((String) «NetworkStateReceiver», (String) «Network connectivity change»); if (intent.getExtras ()!= null) { ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService («connectivity»); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo (); if (networkInfo == null || !networkInfo.isConnectedOrConnecting ()) { this.b.setNetworkStatus(NetworkStatus.NONE.name ()); } else if (networkInfo.getType () == 1) { this.b.setNetworkStatus(NetworkStatus.WIFI.name ()); } else { this.b.setNetworkStatus(NetworkStatus.MOBILE.name ()); } } } } static enum NetworkStatus { NONE, WIFI, MOBILE;
private NetworkStatus () { } } public static enum CameraFocusMode { ONCE, CONTINUOUS; private CameraFocusMode () { } } static enum ViewState { constructed, onPostCreate, onResume, onPause, onDestroy, initFailed; private ViewState () { } } public static class LibraryLoadFailedException extends ArchitectInitializeException { private static final long serialVersionUID = -3436549205240611293L; public LibraryLoadFailedException (String string) { super (string); } }
public static class MissingFeatureException extends ArchitectInitializeException { private static final long serialVersionUID = -1120070352130259205L; public MissingFeatureException (Exception exception) { super (exception); } public MissingFeatureException (String string) { super (string); } } public static class CamNotAccessibleException extends ArchitectInitializeException { private static final long serialVersionUID = -2060234091280507469L; public CamNotAccessibleException (Exception exception) { super (exception); } public CamNotAccessibleException (String string) { super (string); } } public static abstract class ArchitectInitializeException extends RuntimeException { private static final long serialVersionUID = 3315635385062334407L; public ArchitectInitializeException (Exception exception) { super (exception); }
public ArchitectInitializeException (String string) { super (string); } } } OpenCV OpenCV (Open Source Computer Vision Library) это библиотека компьютерного зрения и машинного обучения с открытым исходным кодом. OpenCV обеспечивает общую инфраструктуру для приложений компьютерного зрения (http://opencv.org/). Для начала работы с OpenCV скачаем и распакуем OpenCV for Android SDK. Импортируем в Eclipse с ADT и CDT плагинами папку OpenCV-android-sdk\sdk\java. Для работы OpenCV приложений на Android устройстве нужно скачать и установить из Google Play приложение OpenCV Manager, обеспечивающее наилучшую версию библиотеки OpenCV для конкретного устройства. Импортируем в Eclipse проект OpenCV-android-sdk\samples\image-manipulations. Откорректируем свойства проекта.
Подсоединим Android устройство к компьютеру, нажмем правой кнопкой мышки на проекте и в контекстном меню выберем Run As -> Android Application. На устройстве откроется вид камеры с меню, в котором можно выбрать фильтр изображения. При выборе фильтра в меню может возникнуть ошибка «No implementation found for native…». Для ее устранения скопируем папку OpenCV-android-sdk\sdk\native\libs в проект и запустим приложение заново. Для уменьшения размера библиотеки можно оставить только файлы libopencv_imgproc. a и libopencv_java3.so. Другой способ запустить приложение это установить из Google Play приложение CPU-Z и узнать архитектуру своего процессора. Затем установить подходящий OpenCV Manager из папки OpenCV-android-sdk\apk. Переделаем это приложение в приложение с поддержкой Material Design. При создании Android проектов, работающих с Material Design, в среде Eclipse, в проект необходимо добавить библиотеки andoid design support и recyclerview. Для добавления библиотеки andoid design support в среде Eclipse импортируем с сохранением папку android-sdks\extras\android\support\design.
В свойствах библиотеки в разделе Android в Project Build Target отметим последнюю версию API и добавим зависимость от библиотеки appcompat_v7, предварительно импортировав с сохранением папку android-sdks\extras\android\support\v7\appcompat в среду Eclipse.
Для добавления библиотеки recyclerview в среде Eclipse импортируем с сохранением папку android-sdks\extras\android\support\v7\recyclerview. В свойствах библиотеки в разделе Android в Project Build Target отметим последнюю версию API и добавим зависимость от библиотеки appcompat_v7. Создадим проект Android приложения на основе шаблона Blank Activity. В свойствах проекта в разделе Android в Project Build Target добавим зависимость от библиотек appcompat_v7, andoid design support и recyclerview, а также от библиотеки OpenCV Library. В Android Studio создадим проект на основе шаблона Navigation Drawer Activity.
Скопируем содержимое папок app\src\main Android Studio проекта в Eclipse проект, скорректировав имя пакета. Android Design Support Library обеспечивает фреймворк, позволяющий создавать приложения, включающие в себя навигационное представление navigation drawer view, плавающую метку для редактируемого текста floating labels for editing text, плавающую кнопку действия floating action button, snackbar, компоновки CoordinatorLayout и AppBarLayout, панель инструментов Toolbar вместо Action Bar, RecyclerView вместо ListView и др. Класс главной активности проекта расширяет класс android.support. v7.app. AppCompatActivity и реализует интерфейс android.support.design.widget.NavigationView. OnNavigationItemSelectedListener. Класс AppCompatActivity здесь дает возможность использования методов setSupportActionBar (toolbar) определяет Toolbar как ActionBar и onBackPressed () вызывается системой при нажатии кнопки назад. Интерфейс NavigationView. OnNavigationItemSelectedListener дает метод onNavigationItemSelected (MenuItem item), вызываемый системой при выборе элемента navigation drawer view. Компоновка главной активности начинается с корневого элемента android.support. v4.widget. DrawerLayout. Это является обязательным условием для того, чтобы добавить в приложение панель Navigation Drawer, отображающую основные элементы навигации приложения по левому краю экрана. Атрибут android: fitsSystemWindows элемента DrawerLayout установлен в true, что дает достаточный отступ, чтобы не было наложения на строку состояния status bar. Атрибут tools: openDrawer=«start» не влияет на работу приложения и обеспечивает отображение навигационного ящика при открытии компоновки во вкладке Design редактора Android Studio. Первый дочерний элемент компоновки главной активности содержит компоновку основного содержимого активности, отображаемого, когда навигационный ящик скрыт. Атрибуты android: layout_width=«match_parent» и android: layout_height=«match_parent» обеспечивают заполнение всего окна основным контентом. Второй элемент android.support.design.widget.NavigationView компоновки главной активности объявляет содержимое навигационного ящика. Атрибуты android: layout_width=«wrap_content» и android: layout_height=«match_parent» обеспечивают ширину равную содержимому и высоту равную высоте окна. Ширину навигационного ящика можно сделать фиксированной, например, 320dp. Атрибут android: layout_gravity=«start» обеспечивает отображение навигационного ящика на левой стороне окна. Атрибут app: headerLayout указывает компоновку заголовка навигационного ящика, атрибут app: menu указывает XML ресурс элементов навигационного ящика. В компоновке заголовка навигационного ящика значение атрибута android: layout_height корневого элемента LinearLayout можем установить в android: layout_height=«wrap_content». Можно изменить форму и цвет фона заголовка в файле side_nav_bar. xml, например:
<shape xmlns: android="http://schemas.android.com/apk/res/android" android: shape=«oval»> <gradient android: startColor=«#B87F7F» android: centerColor=«#8C5A5A» android: endColor=«#7E4949» android: type=«linear» android: angle=«135»/> </shape> В заголовке можно убрать значок и расположить текстовый элемент по центру, например: <?xml version=«1.0» encoding=«utf-8»? > <LinearLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width=«match_parent» android: layout_height=«wrap_content» android: background="@drawable/side_nav_bar» android: paddingBottom=«5dp» android: paddingTop=«5dp» android: theme="@style/ThemeOverlay. AppCompat. Dark» android: orientation=«vertical» android: gravity=«bottom»> <TextView android: layout_width=«match_parent» android: layout_height=«wrap_content» android: text=«Image Filters» android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium» android: textAlignment=«center» />
</LinearLayout> С помощью атрибутов android: background="@color/nav_drawer_bg», app: itemTextColor="@color/item_drawer» и app: itemBackground="@drawable/navigation_drawer_background» элемента NavigationView можно установить фон навигационного ящика, цвет и фон элементов навигационного ящика. Фон элементов навигационного ящика здесь определяется файлом navigation_drawer_background. xml папки drawable: <?xml version=«1.0» encoding=«utf-8»? > <selector xmlns: android="http://schemas.android.com/apk/res/android"> <item android: state_checked=«true» android: drawable="@color/item_checked» /> </selector> Цвета определяются файлом colors. xml папки values: <?xml version=«1.0» encoding=«utf-8»? > <resources> <color name=«colorPrimary»> #8C5A5A </color> <color name=«colorPrimaryDark»> #7E4949 </color> <color name=«colorAccent»> #FF4081 </color> <color name=«nav_drawer_bg»> #C6A9A9 </color> <color name=«item_checked»> #FFFFFF </color> <color name=«item_drawer»> #3B1B1B </color> </resources> Размер текста элементов навигационного ящика NavigationView можно установить с помощью атрибута android: theme="@style/NavigationViewStyle» и стиля: <style name=«NavigationViewStyle»> <item name=«android: textSize»> 22sp </item> </style> Для установки элементов навигационного ящика нужно изменить содержимое файла activity_main_drawer. xml папки menu. <?xml version=«1.0» encoding=«utf-8»? > <menu xmlns: android="http://schemas.android.com/apk/res/android">
<group android: checkableBehavior=«single»> <item android: id="@+id/nav_preview_RGBA» android: title=«Preview RGBA» /> <item android: id="@+id/nav_histograms» android: title=«Histograms» /> <item android: id="@+id/nav_canny» android: title=«Canny» /> <item android: id="@+id/nav_sepia» android: title=«Sepia» /> <item android: id="@+id/nav_sobel» android: title=«Sobel» /> <item android: id="@+id/nav_zoom» android: title=«Zoom» /> <item android: id="@+id/nav_pixelize» android: title=«Pixelize» /> <item android: id="@+id/nav_posterize» android: title=«Posterize» />
</group> </menu> В методе onCreate класса главной активности следующий код отвечает за появление в панели инструментов значка гамбургера, при нажатии на который выскакивает навигационный ящик: DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle (this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); toggle.syncState (); Вызов метода drawer.setDrawerListener (toggle) обеспечивает анимацию значка гамбургера при движении навигационного ящика. Первый дочерний элемент компоновки главной активности указывает компоновку основного содержимого активности, отображаемого, когда навигационный ящик скрыт. Корневым элементом этой компоновки служит android.support.design.widget.CoordinatorLayout – ViewGroup, обеспечивающий такие эффекты, как перемещение кнопки действия Floating Action Button вверх и вниз, чтобы освободить место для Snackbar, отображение и скрытие панели инструментов Toolbar и кнопки действия FAB при прокрутке списка содержания вниз и вверх. Первый дочерний элемент CoordinatorLayout это элемент android.support.design. widget. AppBarLayout, обеспечивающий, чтобы вложенный элемент android.support.v7.widget.Toolbar реагировал на прокрутку. Здесь Toolbar выступает как ActionBar. Для этого в манифесте AndroidManifest. xml стиль активности объявлен как android:theme="@style/AppTheme.NoActionBar» и в методе onCreate активности вызван метод setSupportActionBar (toolbar). Атрибуты android: layout_height=»? attr/actionBarSize"и app: popupTheme="@style/AppTheme. PopupOverlay» обеспечивают режим наложения при скрытии и показе панели инструметов. Для того чтобы Toolbar реагировал на прокрутку также нужно добавить атрибут app: layout_scrollFlags=«scroll|enterAlways». Чтобы определить связь между AppBarLayout и видом, который будет прокручиваться, нужно добавить атрибут app: layout_behavior к любому виду, способному содержать прокрутку, такому как NestedScrollView. Поэтому в файле компоновки content_main. xml обернем элемент RelativeLayout в элемент NestedScrollView: <?xml version=«1.0» encoding=«utf-8»? > <android.support.v4.widget.NestedScrollView xmlns: android="http://schemas.android.com/apk/res/android"
xmlns: app="http://schemas.android.com/apk/res-auto" xmlns: tools="http://schemas.android.com/tools" android: layout_width=«match_parent» android: layout_height=«match_parent» android: paddingLeft="@dimen/activity_horizontal_margin» android: paddingRight="@dimen/activity_horizontal_margin» android: paddingTop="@dimen/activity_vertical_margin» android: paddingBottom="@dimen/activity_vertical_margin» android: fillViewport=«true» android: layout_gravity=«fill_vertical» app: layout_behavior="@string/appbar_scrolling_view_behavior» tools:context=".MainActivity» tools: showIn="@layout/app_bar_main» > <RelativeLayout android: layout_width=«match_parent» android: layout_height=«match_parent»> <TextView android: text=«Hello World!» android: layout_width=«wrap_content» android: layout_height=«wrap_content» /> </RelativeLayout> </android.support.v4.widget.NestedScrollView>
Теперь при прокрутке основного содержимого активности панель инструментов Toolbar будет отображаться и скрываться. Добавим необходимые разрешения в файл манифеста приложения: <uses-permission android:name="android.permission.CAMERA»/> <uses-feature android:name="android.hardware.camera» android: required=«false»/> <uses-feature android:name="android.hardware.camera.autofocus» android: required=«false»/> <uses-feature android:name="android.hardware.camera.front» android: required=«false»/> <uses-feature android:name="android.hardware.camera.front.autofocus» android: required=«false»/> Изменим файл компоновки content_main. xml: <?xml version=«1.0» encoding=«utf-8»? > <android.support.v4.widget.NestedScrollView xmlns: android="http://schemas.android.com/apk/res/android" xmlns: app="http://schemas.android.com/apk/res-auto" xmlns: tools="http://schemas.android.com/tools" android: layout_width=«match_parent» android: layout_height=«match_parent» android: paddingLeft="@dimen/activity_horizontal_margin» android: paddingRight="@dimen/activity_horizontal_margin» android: paddingTop="@dimen/activity_vertical_margin» android: paddingBottom="@dimen/activity_vertical_margin» android: fillViewport=«true» android: layout_gravity=«fill_vertical» app: layout_behavior="@string/appbar_scrolling_view_behavior» tools:context=".MainActivity» tools: showIn="@layout/app_bar_main»
> <RelativeLayout android: layout_width=«match_parent» android: layout_height=«match_parent»> <org.opencv.android. JavaCameraView android: layout_width=«fill_parent» android: layout_height=«fill_parent» android: id="@+id/image_manipulations_activity_surface_view» /> </RelativeLayout> </android.support.v4.widget.NestedScrollView> Изменим код класса MainActivity приложения. import java.util.Arrays; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.CameraBridgeViewBase; import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame; import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2; import org.opencv.android. LoaderCallbackInterface; import org.opencv.android. OpenCVLoader; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt; import org.opencv.core. Point; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; import android. os. Bundle; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support. v4.widget. DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support. v7.app. AppCompatActivity; import android.support.v7.widget.Toolbar; import android. util. Log; import android.view.MenuItem; public class MainActivity extends AppCompatActivity implements NavigationView. OnNavigationItemSelectedListener, CvCameraViewListener2 { private static final String TAG = «OCVSample::Activity»; private CameraBridgeViewBase mOpenCvCameraView; private Size mSize0; private Mat mIntermediateMat; private Mat mMat0; private MatOfInt mChannels [];
private MatOfInt mHistSize; private int mHistSizeNum = 25; private MatOfFloat mRanges; private Scalar mColorsRGB []; private Scalar mColorsHue []; private Scalar mWhilte; private Point mP1; private Point mP2; private float mBuff []; private Mat mSepiaKernel; public static final int VIEW_MODE_RGBA = 0; public static final int VIEW_MODE_HIST = 1; public static final int VIEW_MODE_CANNY = 2; public static final int VIEW_MODE_SEPIA = 3; public static final int VIEW_MODE_SOBEL = 4; public static final int VIEW_MODE_ZOOM = 5; public static final int VIEW_MODE_PIXELIZE = 6; public static final int VIEW_MODE_POSTERIZE = 7; public static int viewMode = VIEW_MODE_RGBA; private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback (this) { @Override public void onManagerConnected (int status) { switch (status) {
case LoaderCallbackInterface.SUCCESS: { Log. i (TAG, «OpenCV loaded successfully»); mOpenCvCameraView. enableView (); } break; default: { super. onManagerConnected (status); } break; } } }; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar (toolbar); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle ( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener (toggle); toggle.syncState ();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener (this); mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.image_manipulations_activity_surface_view); mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE); mOpenCvCameraView.setCvCameraViewListener (this); } @Override public void onBackPressed () { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super. onBackPressed (); } } @Override public void onPause () { super. onPause (); if (mOpenCvCameraView!= null) mOpenCvCameraView. disableView (); }
@Override public void onResume () { super. onResume (); if (!OpenCVLoader.initDebug ()) { Log. d (TAG, «Internal OpenCV library not found. Using OpenCV Manager for initialization»); OpenCVLoader.initAsync (OpenCVLoader. OPENCV_VERSION_3_0_0, this, mLoaderCallback); } else { Log. d (TAG, «OpenCV library found inside package. Using it!»); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } } public void onDestroy () { super. onDestroy (); if (mOpenCvCameraView!= null) mOpenCvCameraView. disableView (); } @SuppressWarnings («StatementWithEmptyBody») @Override public boolean onNavigationItemSelected (MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId (); if (id == R.id.nav_preview_RGBA) {
viewMode = VIEW_MODE_RGBA; } else if (id == R.id.nav_histograms) { viewMode = VIEW_MODE_HIST; } else if (id == R.id.nav_canny) { viewMode = VIEW_MODE_CANNY; } else if (id == R.id.nav_sepia) { viewMode = VIEW_MODE_SEPIA; } else if (id == R.id.nav_sobel) { viewMode = VIEW_MODE_SOBEL; } else if (id == R.id.nav_zoom) { viewMode = VIEW_MODE_ZOOM; } else if (id == R.id.nav_pixelize) { viewMode = VIEW_MODE_PIXELIZE; } else if (id == R.id.nav_posterize) { viewMode = VIEW_MODE_POSTERIZE; } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } @Override public void onCameraViewStarted (int width, int height) { mIntermediateMat = new Mat (); mSize0 = new Size ();
mChannels = new MatOfInt [] {new MatOfInt (0), new MatOfInt (1), new MatOfInt (2)}; mBuff = new float [mHistSizeNum]; mHistSize = new MatOfInt (mHistSizeNum); mRanges = new MatOfFloat (0f, 256f); mMat0 = new Mat (); mColorsRGB = new Scalar [] {new Scalar (200, 0, 0, 255), new Scalar (0, 200, 0, 255), new Scalar (0, 0, 200, 255)}; mColorsHue = new Scalar [] { new Scalar (255, 0, 0, 255), new Scalar (255, 60, 0, 255), new Scalar (255, 120, 0, 255), new Scalar (255, 180, 0, 255), new Scalar (255, 240, 0, 255), new Scalar (215, 213, 0, 255), new Scalar (150, 255, 0, 255), new Scalar (85, 255, 0, 255), new Scalar (20, 255, 0, 255), new Scalar (0, 255, 30, 255), new Scalar (0, 255, 85, 255), new Scalar (0, 255, 150, 255), new Scalar (0, 255, 215, 255), new Scalar (0, 234, 255, 255), new Scalar (0, 170, 255, 255), new Scalar (0, 120, 255, 255), new Scalar (0, 60, 255, 255), new Scalar (0, 0, 255, 255), new Scalar (64, 0, 255, 255), new Scalar (120, 0, 255, 255), new Scalar (180, 0, 255, 255), new Scalar (255, 0, 255, 255), new Scalar (255, 0, 215, 255), new Scalar (255, 0, 85, 255), new Scalar (255, 0, 0, 255) }; mWhilte = Scalar.all (255); mP1 = new Point (); mP2 = new Point (); // Fill sepia kernel mSepiaKernel = new Mat (4, 4, CvType.CV_32F); mSepiaKernel. put (0, 0, /* R */0.189f, 0.769f, 0.393f, 0f); mSepiaKernel. put (1, 0, /* G */0.168f, 0.686f, 0.349f, 0f); mSepiaKernel. put (2, 0, /* B */0.131f, 0.534f, 0.272f, 0f); mSepiaKernel. put (3, 0, /* A */0.000f, 0.000f, 0.000f, 1f);
} @Override public void onCameraViewStopped () { if (mIntermediateMat!= null) mIntermediateMat.release (); mIntermediateMat = null; } @Override public Mat onCameraFrame (CvCameraViewFrame inputFrame) { Mat rgba = inputFrame. rgba (); Size sizeRgba = rgba.size (); Mat rgbaInnerWindow; int rows = (int) sizeRgba. height; int cols = (int) sizeRgba. width; int left = cols / 8; int top = rows / 8; int width = cols * 3 / 4; int height = rows * 3 / 4;
switch (MainActivity.viewMode) { case MainActivity.VIEW_MODE_RGBA: break; case MainActivity.VIEW_MODE_HIST: Mat hist = new Mat (); int thikness = (int) (sizeRgba. width / (mHistSizeNum +10) / 5); if (thikness> 5) thikness = 5; int offset = (int) ((sizeRgba. width – (5*mHistSizeNum +4*10) *thikness) /2); // RGB for (int c=0; c <3; c++) { Imgproc.calcHist(Arrays.asList (rgba), mChannels [c], mMat0, hist, mHistSize, mRanges); Core.normalize (hist, hist, sizeRgba. height/2, 0, Core.NORM_INF); hist.get (0, 0, mBuff); for (int h=0; h <mHistSizeNum; h++) { mP1.x = mP2.x = offset + (c * (mHistSizeNum +10) + h) * thikness; mP1.y = sizeRgba. height-1; mP2.y = mP1.y – 2 – (int) mBuff [h]; Imgproc.line (rgba, mP1, mP2, mColorsRGB [c], thikness); } } // Value and Hue Imgproc.cvtColor (rgba, mIntermediateMat, Imgproc.COLOR_RGB2HSV_FULL); // Value Imgproc.calcHist(Arrays.asList (mIntermediateMat), mChannels [2], mMat0, hist, mHistSize, mRanges);
Core.normalize (hist, hist, sizeRgba. height/2, 0, Core.NORM_INF); hist.get (0, 0, mBuff); for (int h=0; h <mHistSizeNum; h++) { mP1.x = mP2.x = offset + (3 * (mHistSizeNum +10) + h) * thikness; mP1.y = sizeRgba. height-1; mP2.y = mP1.y – 2 – (int) mBuff [h]; Imgproc.line (rgba, mP1, mP2, mWhilte, thikness); } // Hue Imgproc.calcHist(Arrays.asList (mIntermediateMat), mChannels [0], mMat0, hist, mHistSize, mRanges); Core.normalize (hist, hist, sizeRgba. height/2, 0, Core.NORM_INF); hist.get (0, 0, mBuff); for (int h=0; h <mHistSizeNum; h++) { mP1.x = mP2.x = offset + (4 * (mHistSizeNum +10) + h) * thikness; mP1.y = sizeRgba. height-1; mP2.y = mP1.y – 2 – (int) mBuff [h]; Imgproc.line (rgba, mP1, mP2, mColorsHue [h], thikness); } break; case MainActivity.VIEW_MODE_CANNY: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Imgproc.Canny (rgbaInnerWindow, mIntermediateMat, 80, 90); Imgproc.cvtColor (mIntermediateMat, rgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA, 4); rgbaInnerWindow.release (); break;
case MainActivity.VIEW_MODE_SOBEL: Mat gray = inputFrame.gray (); Mat grayInnerWindow = gray.submat (top, top + height, left, left + width); rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Imgproc.Sobel (grayInnerWindow, mIntermediateMat, CvType.CV_8U, 1, 1); Core.convertScaleAbs (mIntermediateMat, mIntermediateMat, 10, 0); Imgproc.cvtColor (mIntermediateMat, rgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA, 4); grayInnerWindow.release (); rgbaInnerWindow.release (); break; case MainActivity.VIEW_MODE_SEPIA: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Core.transform (rgbaInnerWindow, rgbaInnerWindow, mSepiaKernel); rgbaInnerWindow.release (); break; case MainActivity.VIEW_MODE_ZOOM: Mat zoomCorner = rgba.submat (0, rows / 2 – rows / 10, 0, cols / 2 – cols / 10); Mat mZoomWindow = rgba.submat (rows / 2 – 9 * rows / 100, rows / 2 +9 * rows / 100, cols / 2 – 9 * cols / 100, cols / 2 +9 * cols / 100); Imgproc.resize (mZoomWindow, zoomCorner, zoomCorner.size ()); Size wsize = mZoomWindow.size (); Imgproc.rectangle (mZoomWindow, new Point (1, 1), new Point (wsize. width – 2, wsize. height – 2), new Scalar (255, 0, 0, 255), 2); zoomCorner.release ();
mZoomWindow.release (); break; case MainActivity.VIEW_MODE_PIXELIZE: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Imgproc.resize (rgbaInnerWindow, mIntermediateMat, mSize0, 0.1, 0.1, Imgproc.INTER_NEAREST); Imgproc.resize (mIntermediateMat, rgbaInnerWindow, rgbaInnerWindow.size (), 0., 0., Imgproc.INTER_NEAREST); rgbaInnerWindow.release (); break; case MainActivity.VIEW_MODE_POSTERIZE: /* Imgproc.cvtColor (rgbaInnerWindow, mIntermediateMat, Imgproc.COLOR_RGBA2RGB); Imgproc.pyrMeanShiftFiltering (mIntermediateMat, mIntermediateMat, 5, 50); Imgproc.cvtColor (mIntermediateMat, rgbaInnerWindow, Imgproc.COLOR_RGB2RGBA); */ rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Imgproc.Canny (rgbaInnerWindow, mIntermediateMat, 80, 90); rgbaInnerWindow.setTo (new Scalar (0, 0, 0, 255), mIntermediateMat); Core.convertScaleAbs (rgbaInnerWindow, mIntermediateMat, 1./16, 0); Core.convertScaleAbs (mIntermediateMat, rgbaInnerWindow, 16, 0); rgbaInnerWindow.release (); break; }
return rgba; } } Чтобы скорректировать ориентацию вида камеры, в файл манифеста добавим android: screenOrientation=«landscape» в тег <activity>. А в метод onCameraFrame в конце перед return rgba; добавим Core. flip (rgba, rgba, -1);. Класс BaseLoaderCallback является базовой реализацией интерфейса LoaderCallbackInterface, отвечающего за асинхронную инициализацию библиотеки OpenCV. В методе onManagerConnected интерфейса можно обработать успешную инициализацию библиотеки. Здесь в методе onManagerConnected с помощью объекта CameraBridgeViewBase включается соединение с камерой устройства. Объект CameraBridgeViewBase получается в методе onCreate активности на основе класса JavaCameraView, являющегося реализацией класса CameraBridgeViewBase. В методе onResume активности, с помощью объекта OpenCVLoader, обеспечивающего инициализацию библиотеки OpenCV, производится попытка загрузки и инициализации библиотеки OpenCV из текущего пакета приложения с помощь метода OpenCVLoader.initDebug (). В случае неудачи загрузки библиотеки OpenCV из текущего пакета приложения, производится попытка загрузки и инициализации библиотеки OpenCV с использованием OpenCV Manager с помощью метода OpenCVLoader.initAsync (). Метод onCameraViewStarted вызывается при запуске превью камеры. Здесь инициализируются объекты Mat матриц вспомогательных изображений, Scalar векторов и Point точек изображения. Метод onCameraFrame вызывается для каждого фрейма с объектом CvCameraViewFrame, представляющего фрейм камеры, в качестве параметра. Здесь методом rgba () получается изображение камеры, из него выделяется прямоугольная область rgbaInnerWindow = rgba.submat (top, top + height, left, left + width), которая обрабатывается с помощью класса Imgproc. Случаи VIEW_MODE_CANNY и VIEW_MODE_SOBEL представляют собой примеры выявления в изображении характерных особенностей, которые могут быть использованы для распознавания изображения, а именно линий изображения. Здесь наглядно видно, что алгоритм Canny Edge более эффективен по сравнению с Sobel фильтром. Дополнительно к примеру, с помощью класса Imgproc можно применить линейные фильтры к изображению: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); //Imgproc. blur (rgbaInnerWindow, rgbaInnerWindow, new Size (3,3)); //Imgproc.GaussianBlur (rgbaInnerWindow, rgbaInnerWindow, new Size (3,3), 0);
Imgproc.medianBlur (rgbaInnerWindow, rgbaInnerWindow, 3); Расширить светлые области изоражения: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Mat kernelDilate = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size (3, 3)); Imgproc. dilate (rgbaInnerWindow, rgbaInnerWindow, kernelDilate); Расширить темные области изоражения: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Mat kernelErode = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size (5, 5)); Imgproc.erode (rgbaInnerWindow, rgbaInnerWindow, kernelErode); С помощью класса org.opencv.android. Utils можно конвертировать Bitmap изображение в Mat объект и наоборот и обрабатывать уже не текущий вид камеры, а Bitmap изображение. Изменим приложение таким образом, чтобы пользователь мог добавлять изображения для отслеживания, а камера при распознавании изображения показывала текст, предварительно добавленный пользователем. Для этого изменим главную активность приложения. import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.CameraBridgeViewBase; import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame; import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2; import org.opencv.android. LoaderCallbackInterface; import org.opencv.android. OpenCVLoader; import org.opencv.android. Utils; import org.opencv.core.Core; import org.opencv.core.DMatch; import org.opencv.core.KeyPoint;
import org.opencv.core.Mat; import org.opencv.core.MatOfDMatch; import org.opencv.core.MatOfKeyPoint; import org.opencv.core. Point; import org.opencv.core.Size; import org.opencv.features2d.DescriptorExtractor; import org.opencv.features2d.DescriptorMatcher; import org. opencv. features2d. FeatureDetector; import org.opencv.imgproc.Imgproc; import android.app.AlertDialog; import android. app. Dialog; import android.content.Context; import android.content. DialogInterface; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android. os. Bundle; import android.support.design. widget. FloatingActionButton; import android.support.design.widget.NavigationView; import android.support. v4.app. DialogFragment; import android.support.v4.view.GravityCompat; import android.support. v4.widget. DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle; import android.support. v7.app. AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.StaticLayout; import android. text. TextPaint; import android.util.Base64; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.View. OnFocusChangeListener; import android.view. WindowManager; import android.widget.CheckBox; import android. widget. EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; public class MainActivity extends AppCompatActivity implements NavigationView. OnNavigationItemSelectedListener, CvCameraViewListener2 { private CameraBridgeViewBase mOpenCvCameraView; public static final int VIEW_MODE_RGBA = 0; public static final int VIEW_MODE_TAKE = 1; public static int viewMode = VIEW_MODE_RGBA; private ImageView headerImage;
private AppCompatActivity activity; private Bitmap bmp; private static SharedPreferences mSettings; private FeatureDetector mFeatureDetector; private DescriptorExtractor mDescriptorExtractor; private DescriptorMatcher mDescriptorMatcher; private Mat referenceImageGray; private MatOfKeyPoint mReferenceKeypoints; private Mat mReferenceDescriptors; private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback (this) { @Override public void onManagerConnected (int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: { mOpenCvCameraView. enableView (); mFeatureDetector = FeatureDetector.create(FeatureDetector.BRISK); mDescriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.BRISK); mDescriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING); mSettings = getSharedPreferences («APP_PREFERENCES», Context.MODE_PRIVATE); if (mSettings.contains («APP_IMAGE»)) { String stringImage = mSettings.getString («APP_IMAGE», «»); byte [] decodedString = Base64.decode (stringImage, Base64.DEFAULT); Bitmap decodedByte = BitmapFactory.decodeByteArray (decodedString, 0, decodedString. length);
bmp=decodedByte; Mat mReferenceImage = new Mat (); Utils.bitmapToMat (bmp, mReferenceImage); referenceImageGray = new Mat (); Imgproc.cvtColor (mReferenceImage, referenceImageGray, Imgproc.COLOR_BGR2GRAY); mReferenceKeypoints = new MatOfKeyPoint (); mReferenceDescriptors = new Mat (); mFeatureDetector.detect (referenceImageGray, mReferenceKeypoints); mDescriptorExtractor.compute (referenceImageGray, mReferenceKeypoints, mReferenceDescriptors); } } break; default: { super. onManagerConnected (status); } break; } } }; @Override protected void onCreate (Bundle savedInstanceState) { super. onCreate (savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar (toolbar);
activity=this; DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle ( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener (toggle); toggle.syncState (); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener (this); View header = navigationView.getHeaderView (0); headerImage = (ImageView)header.findViewById(R.id.header_imageView); headerImage.setVisibility (View. GONE); mSettings = getSharedPreferences («APP_PREFERENCES», Context.MODE_PRIVATE); if (!mSettings.contains («CAMERA_ROTATE»)) { SharedPreferences. Editor editor = mSettings. edit (); editor. putInt («CAMERA_ROTATE», 1); editor.commit (); } if (mSettings.contains («APP_IMAGE»)) { String stringImage = mSettings.getString («APP_IMAGE», «»); byte [] decodedString = Base64.decode (stringImage, Base64.DEFAULT); Bitmap decodedByte = BitmapFactory.decodeByteArray (decodedString, 0, decodedString. length);
bmp=decodedByte; headerImage.setImageBitmap (bmp); headerImage.setVisibility(View.VISIBLE); } if (!mSettings.contains («MARKERS_SIZE»)) { SharedPreferences. Editor editor = mSettings. edit (); editor. putInt («MARKERS_SIZE», 0); editor.commit (); } mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.image_manipulations_activity_surface_view); mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE); mOpenCvCameraView.setCvCameraViewListener (this); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener (new View. OnClickListener () { @Override public void onClick (View view) { viewMode = VIEW_MODE_TAKE; } }); } @Override public void onBackPressed () {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super. onBackPressed (); } } @Override public void onPause () { super. onPause (); if (mOpenCvCameraView!= null) mOpenCvCameraView. disableView (); } @Override public void onResume () { super. onResume (); if (!OpenCVLoader.initDebug ()) { OpenCVLoader.initAsync (OpenCVLoader. OPENCV_VERSION_3_2_0, this, mLoaderCallback); } else { mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } }
public void onDestroy () { super. onDestroy (); if (mOpenCvCameraView!= null) mOpenCvCameraView. disableView (); } @SuppressWarnings («StatementWithEmptyBody») @Override public boolean onNavigationItemSelected (MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId (); if (id == R.id.nav_preview_RGBA) { viewMode = VIEW_MODE_TAKE; } if (id == R.id.nav_preview_Rotate) { SharedPreferences. Editor editor = mSettings. edit (); if(mSettings.getInt («CAMERA_ROTATE», 1) ==1) { editor. putInt («CAMERA_ROTATE», -1); } else { editor. putInt («CAMERA_ROTATE», 1); } editor.commit (); }
if (id == R.id.nav_preview_Select) { SelectDialogFragment dialog = new SelectDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «SelectDialogFragment»); } if (id == R.id.nav_preview_Edit) { EditDialogFragment dialog = new EditDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «EditDialogFragment»); } if (id == R.id.nav_preview_Delete) { DeleteDialogFragment dialog = new DeleteDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «DeleteDialogFragment»); } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } @Override public void onCameraViewStarted (int width, int height) { } @Override
public void onCameraViewStopped () { } @Override public Mat onCameraFrame (CvCameraViewFrame inputFrame) { Mat rgba = inputFrame. rgba (); Core. flip (rgba, rgba, mSettings.getInt («CAMERA_ROTATE», 1)); Size sizeRgba = rgba.size (); Mat rgbaInnerWindow; int rows = (int) sizeRgba. height; int cols = (int) sizeRgba. width; int left = cols / 8; int top = rows / 8; int width = cols * 3 / 4; int height = rows * 3 / 4; switch (MainActivity.viewMode) { case MainActivity.VIEW_MODE_RGBA: if (mSettings.contains («APP_IMAGE»)) { Mat mGraySrc = new Mat ();
Imgproc.cvtColor (rgba, mGraySrc, Imgproc.COLOR_RGBA2GRAY); MatOfKeyPoint mSceneKeypoints = new MatOfKeyPoint (); Mat mSceneDescriptors = new Mat (); mFeatureDetector.detect (mGraySrc, mSceneKeypoints); mDescriptorExtractor.compute (mGraySrc, mSceneKeypoints, mSceneDescriptors); MatOfDMatch mMatches = new MatOfDMatch (); mDescriptorMatcher.match (mSceneDescriptors, mReferenceDescriptors, mMatches); List <DMatch> matchesList = mMatches.toList (); if (matchesList.size () <4) { break; } double maxDist = 0.0; double minDist = Double.MAX_VALUE; for (DMatch match: matchesList) { double dist = match. distance; if (dist <minDist) { minDist = dist; } if (dist> maxDist) { maxDist = dist; } } if (minDist> 50.0) { break;
} List <KeyPoint> referenceKeypointsList = mReferenceKeypoints.toList (); List <KeyPoint> sceneKeypointsList = mSceneKeypoints.toList (); ArrayList <Point> goodReferencePointsList = new ArrayList <Point> (); ArrayList <Point> goodScenePointsList = new ArrayList <Point> (); double maxGoodMatchDist = 1.75 * minDist; for (DMatch match: matchesList) { if (match. distance <maxGoodMatchDist) { goodReferencePointsList.add(referenceKeypointsList.get(match.trainIdx).pt); goodScenePointsList.add(sceneKeypointsList.get(match.queryIdx).pt); } } if (goodReferencePointsList.size () <4 || goodScenePointsList.size () <4) { break; } /* MatOfPoint2f goodReferencePoints = new MatOfPoint2f (); goodReferencePoints.fromList (goodReferencePointsList); MatOfPoint2f goodScenePoints = new MatOfPoint2f (); goodScenePoints.fromList (goodScenePointsList); Mat homography = Calib3d.findHomography (goodReferencePoints, goodScenePoints); Mat mReferenceCorners = new Mat (4, 1, CvType.CV_32FC2); Mat mCandidateSceneCorners = new Mat (4, 1, CvType.CV_32FC2);
mReferenceCorners. put (0, 0, new double [] {0.0, 0.0}); mReferenceCorners. put (1, 0, new double [] {referenceImageGray.cols (), 0.0}); mReferenceCorners. put (2, 0, new double [] {referenceImageGray.cols (), referenceImageGray.rows ()}); mReferenceCorners. put (3, 0, new double [] {0.0, referenceImageGray.rows ()}); Core.perspectiveTransform (mReferenceCorners, mCandidateSceneCorners, homography); MatOfPoint mIntSceneCorners = new MatOfPoint (); mCandidateSceneCorners.convertTo (mIntSceneCorners, CvType.CV_32S); Mat mSceneCorners = new Mat (4, 1, CvType.CV_32FC2); if (Imgproc.isContourConvex (mIntSceneCorners)) { mCandidateSceneCorners.copyTo (mSceneCorners); } if (mSceneCorners. height () <4) { break; } */ String text = mSettings.getString («APP_IMAGE_LABEL», «»); rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); Bitmap innerBtm = Bitmap.createBitmap(rgbaInnerWindow.cols (), rgbaInnerWindow.rows (), Bitmap.Config.ARGB_8888); Utils.matToBitmap (rgbaInnerWindow, innerBtm); Canvas canvas = new Canvas (innerBtm); TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG); paint.setColor (Color. BLUE); paint.setTextSize (50); StaticLayout textLayout = new StaticLayout (text, paint, canvas.getWidth (), android.text.Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); float x = (innerBtm.getWidth () – canvas.getWidth ()) /2;
float y = (innerBtm.getHeight () – textLayout.getHeight ()) /2; canvas.save (); canvas.translate (x, y); textLayout. draw (canvas); canvas.restore (); Mat newrgbaInnerWindow = new Mat (); Utils.bitmapToMat (innerBtm, newrgbaInnerWindow); newrgbaInnerWindow.copyTo (rgbaInnerWindow); newrgbaInnerWindow.release (); rgbaInnerWindow.release (); //Imgproc. putText (rgba, mSettings.getString («APP_IMAGE_LABEL», «»), new Point (cols/2, rows/2), Core.FONT_ITALIC, 2, new Scalar (0, 0, 255), 5); } break; case MainActivity.VIEW_MODE_TAKE: rgbaInnerWindow = rgba.submat (top, top + height, left, left + width); bmp = Bitmap.createBitmap(rgbaInnerWindow.cols (), rgbaInnerWindow.rows (), Bitmap.Config.ARGB_8888); Utils.matToBitmap (rgbaInnerWindow, bmp); rgbaInnerWindow.release (); activity.runOnUiThread (new Runnable () { public void run () { headerImage.setImageBitmap (bmp); headerImage.setVisibility(View.VISIBLE);
} }); Mat mReferenceImage = new Mat (); Utils.bitmapToMat (bmp, mReferenceImage); referenceImageGray = new Mat (); Imgproc.cvtColor (mReferenceImage, referenceImageGray, Imgproc.COLOR_BGR2GRAY); mReferenceKeypoints = new MatOfKeyPoint (); mReferenceDescriptors = new Mat (); mFeatureDetector.detect (referenceImageGray, mReferenceKeypoints); mDescriptorExtractor.compute (referenceImageGray, mReferenceKeypoints, mReferenceDescriptors); Bitmap tmpBtm=bmp; ByteArrayOutputStream outputStream = new ByteArrayOutputStream (); tmpBtm.compress(Bitmap.CompressFormat.JPEG, 10, outputStream); byte [] bitmapByte = outputStream.toByteArray (); outputStream = null; final String stringEncodedImage = Base64.encodeToString (bitmapByte, Base64.DEFAULT); bitmapByte = null; SharedPreferences. Editor editor = mSettings. edit (); editor. putString («APP_IMAGE», stringEncodedImage); int size = mSettings.getInt («MARKERS_SIZE», 0) +1; editor. putInt («MARKERS_SIZE», size);
editor. putString («MARKER»+size, stringEncodedImage); editor.commit (); AddDialogFragment dialog = new AddDialogFragment (); dialog.show(activity.getSupportFragmentManager (), «AddDialogFragment»); viewMode = VIEW_MODE_RGBA; break; } return rgba; } public static class AddDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private EditText label; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ()); LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout.add_label, null); builder.setView (view) .setPositiveButton(R.string. button_add, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) {
} }) .setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { AddDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); label=(EditText)view.findViewById(R.id.add_label); return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) {
SharedPreferences. Editor editor = mSettings. edit (); editor. putString («APP_IMAGE_LABEL», label.getText().toString ()); int size = mSettings.getInt («MARKERS_SIZE», 0); editor. putString («LABEL»+size, label.getText().toString ()); editor.commit (); alert. dismiss (); } }); } } public static class DeleteDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private LinearLayout list; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ()); LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout.delete_marker, null); builder.setView (view) .setPositiveButton(R.string. button_delete, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) {
} }) .setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { DeleteDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); list=(LinearLayout)view.findViewById(R.id.list_delete_marker); int size = mSettings.getInt («MARKERS_SIZE», 0); for (int i=1; i <=size; i++) { CheckBox cb = new CheckBox (getActivity ()); String text = mSettings.getString («LABEL»+i, «»); cb.setText (text); ImageView imv = new ImageView (getActivity ()); String stringImage = mSettings.getString («MARKER»+i, «»); byte [] decodedString = Base64.decode (stringImage, Base64.DEFAULT); Bitmap decodedByte = BitmapFactory.decodeByteArray (decodedString, 0, decodedString. length); imv.setImageBitmap (decodedByte);
imv.setLayoutParams (new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams. WRAP_CONTENT)); list.addView (cb); list.addView (imv); if (stringImage. equals (»»)) { cb.setVisibility (View. GONE); } } return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { CheckBox cb; int j=1; for (int i = 0; i<list.getChildCount (); i++) { cb = (CheckBox)list.getChildAt (i); if(cb.isChecked ()) { SharedPreferences. Editor editor = mSettings. edit (); editor.remove («LABEL»+j); editor.remove («MARKER»+j);
editor.commit (); } i=i+1; j++; } alert. dismiss (); } }); } } public static class SelectDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private LinearLayout list; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ()); LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout.select_marker, null); builder.setView (view) .setPositiveButton(R.string. button_select, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) {
} }) .setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { SelectDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); list=(LinearLayout)view.findViewById(R.id.list_select_marker); int size = mSettings.getInt («MARKERS_SIZE», 0); for (int i=1; i <=size; i++) { CheckBox cb = new CheckBox (getActivity ()); String text = mSettings.getString («LABEL»+i, «»); cb.setText (text); ImageView imv = new ImageView (getActivity ()); String stringImage = mSettings.getString («MARKER»+i, «»); byte [] decodedString = Base64.decode (stringImage, Base64.DEFAULT); Bitmap decodedByte = BitmapFactory.decodeByteArray (decodedString, 0, decodedString. length); imv.setImageBitmap (decodedByte);
imv.setLayoutParams (new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams. WRAP_CONTENT)); list.addView (cb); list.addView (imv); if (stringImage. equals (»»)) { cb.setVisibility (View. GONE); } } return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { CheckBox cb; int j=1; for (int i = 0; i<list.getChildCount (); i++) { cb = (CheckBox)list.getChildAt (i); if(cb.isChecked ()) { String stringImage = mSettings.getString («MARKER»+j, «»); String label = mSettings.getString («LABEL»+j, «»); byte [] decodedString = Base64.decode (stringImage, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray (decodedString, 0, decodedString. length); ((MainActivity)getActivity()).bmp=decodedByte; ((MainActivity)getActivity()).headerImage.setImageBitmap(((MainActivity)getActivity()).bmp); SharedPreferences. Editor editor = mSettings. edit (); editor. putString («APP_IMAGE_LABEL», label); editor. putString («APP_IMAGE», stringImage); editor.commit (); } i=i+1; j++; } alert. dismiss (); } }); } } public static class EditDialogFragment extends DialogFragment { private View view; private AlertDialog alert; private LinearLayout list; @Override public Dialog onCreateDialog (Bundle savedInstanceState) { AlertDialog. Builder builder = new AlertDialog. Builder (getActivity ());
LayoutInflater inflater = getActivity().getLayoutInflater (); view = (View) inflater.inflate(R.layout. edit_marker, null); builder.setView (view) .setPositiveButton(R.string. button_edit, new DialogInterface. OnClickListener () { @Override public void onClick (DialogInterface dialog, int id) { } }) .setNegativeButton(R.string. button_cancel, new DialogInterface. OnClickListener () { public void onClick (DialogInterface dialog, int id) { EditDialogFragment.this.getDialog().cancel (); } }); alert = builder.create (); alert.setOnShowListener (new DialogInterface. OnShowListener () { @Override public void onShow (DialogInterface dialog) { } }); list=(LinearLayout)view.findViewById(R.id.list_edit_marker); int size = mSettings.getInt («MARKERS_SIZE», 0); for (int i=1; i <=size; i++) { CheckBox cb = new CheckBox (getActivity ());
String text = mSettings.getString («LABEL»+i, «»); cb.setText(R.string.label_edit); EditText ed = new EditText (getActivity ()); ed.setText (text); ed.setOnFocusChangeListener (new OnFocusChangeListener () { @Override public void onFocusChange (View v, boolean hasFocus) { if (hasFocus) { alert.getWindow().clearFlags(WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams. FLAG_ALT_FOCUSABLE_IM); alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_AL WAYS_VISIBLE); } } }); ImageView imv = new ImageView (getActivity ()); String stringImage = mSettings.getString («MARKER»+i, «»); byte [] decodedString = Base64.decode (stringImage, Base64.DEFAULT); Bitmap decodedByte = BitmapFactory.decodeByteArray (decodedString, 0, decodedString. length); imv.setImageBitmap (decodedByte); imv.setLayoutParams (new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams. WRAP_CONTENT)); list.addView (cb); list.addView (ed);
list.addView (imv); if (stringImage. equals (»»)) { cb.setVisibility (View. GONE); ed.setVisibility (View. GONE); } } return alert; } @Override public void onStart () { super. onStart (); alert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener (new View. OnClickListener () { @Override public void onClick (View v) { CheckBox cb; int j=1; for (int i = 0; i<list.getChildCount (); i++) { cb = (CheckBox)list.getChildAt (i); if(cb.isChecked ()) { EditText ed = (EditText)list.getChildAt (i+1); SharedPreferences. Editor editor = mSettings. edit (); editor. putString («LABEL»+j, ed.getText().toString ()); editor.commit ();
} i=i+2; j++; } alert. dismiss (); } }); } } } Здесь плавающей кнопкой или с помощью меню устанавливается режим viewMode = VIEW_MODE_TAKE, который обрабатывается в методе onCameraFrame и при котором производится захват камерой изображения с его последующим сохранением в настройках приложения. При этом также добавляется текст описания для изображения. В конце режим изменяется на viewMode = VIEW_MODE_RGBA, при котором производится отслеживание изображения с использованием классов org.opencv.features2d.DescriptorExtractor, org.opencv.features2d.DescriptorMatcher и org. opencv. features2d. FeatureDetector. При обнаружении изображения на основе его описания создается изображение, которое накладывается на вид камеры. Делается это из-за того, что класс Imgproc не работает с текстом в кодировке UTF-8. Остальные пункты меню приложения позволяют удалять добавленные изображения, редактировать их описание и выбирать из них изображение для отслеживания. Готовое приложение можно скачать в Google Play https://play.google.com/store/apps/details?id=com.tmsoftstudio.opencvimage.