В очередном выпуске “Как создать андроид-приложение” мы рассмотрим приложение с эффектом дополненной реальности, как игра PokemonGo. Да, да, мы тоже будем с вами ловить покемонов, привязанных к определенным координатам на местности, используя датчики android устройства.
Итак, что такое «Дополненная реальность»? Этот термин стал довольно популярным в последние несколько лет благодаря Google glass, но идея старше, чем первый телефон Android. Вы помните фильм Терминатор? Наш герой имел зрение, которое отображало расстояние до ближайших объектов и дополнительную информацию о них.
Существует несколько определений дополненной реальности: исследователь Рональд Азума (англ. Ronald Azuma) в 1997 году определил её как систему, которая:
- совмещает виртуальное и реальное;
- взаимодействует в реальном времени;
- работает в 3D.
Не путайте дополненную реальность с виртуальной реальностью – это разные технологии. Отличие заключается в том, что дополненная реальность – это наложение оцифрованной информации на реальный мир. Давайте рассмотрим простое приложение, которое отображает привязанную к координатам картинку на CameraView. Картинка отображается поверх изображения с камеры, если устройство находится поблизости и повернуто в сторону координат расположения картинки. С координатами местоположения понятно, но каким образом устройство будет определять направление расположения устройства относительно точки привязки картинки? В этом нам поможет теория геодезии и такое понятие, как азимут.
Немного теории
Кто не знает, азимут – это угол, образуемый заданным направлением движения и направлением на север.
Приложение будет определять точку назначения, сравнивая азимут, вычисленный из основных свойств прямоугольного треугольника и реальный азимут, который указывает устройство. Вот примерный алгоритм для достижения этой цели:
- получить местоположение устройства
- получить местоположение точки назначения
- вычислить расстояние до точки назначения
- рассчитать теоретический азимут
- получить реальный азимут устройства
- сравнить оба азимута
- вызвать событие при совпадении значений азимута и расстояния в пределах допустимой погрешности
Теперь вопрос заключается в том, как вычислить азимут и расстояние. Это довольно просто, потому что мы будем игнорировать кривизну Земли и рассматривать ее как плоскую поверхность:
Как вы можете видеть, у нас есть прямоугольный треугольник, и мы можем вычислить угол ᵠ между точками с помощью простого уравнения:
В приведенной таблице представлены отношения между уголом в градах и азимутом A(AB):
А чтобы определить расстояние до точки назначения, воспользуемся этой формулой:
Рассмотрим реализацию нашего приложения
Ниже код проекта в Андроид Студио, который мы сейчас подробно разберем.
Для начала посмотрим ресурсы. Нам понадобится изображение покемона.
Вот ссылка на ресурсы: https://yadi.sk/d/Y06QyQ6juJibC
В нашем приложении 2 активити, поэтому нам нужны 2 макета компоновки для них. В одном будет отображаться превью камеры с наложением текста и картинки, а также кнопка для перехода на карту.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <SurfaceView android:id="@+id/cameraview" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/icon" android:layout_width="96dp" android:layout_height="96dp" android:src="@drawable/pikachu" android:visibility="invisible" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> <TextView android:id="@+id/cameraTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:layout_alignParentTop="true" android:layout_alignParentStart="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Map" android:id="@+id/btnMap" android:layout_marginBottom="23dp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" /> <ImageView android:layout_width="224dp" android:layout_height="224dp" android:id="@+id/imageView" android:src="@drawable/reticle" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> </RelativeLayout>
В другом макете будет отображаться карта Google Maps.
<?xml version="1.0" encoding="utf-8"?> <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" tools:context="info.fandroid.example.augmentedreality.MapActivity"> <fragment android:id="@+id/mapView" android:name="com.google.android.gms.maps.MapFragment" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
Я не буду подробно останавливаться на том, как встроить карту Google Maps в приложение, урок об этом смотрите на нашем сайте: ссылка
Теперь перейдем к коду. Нам понадобятся два интерфейса. Первый – это слушатель изменения местоположения.
import android.location.Location; public interface OnLocationChangedListener { void onLocationChanged(Location currentLocation); }
Второй – слушатель изменения азимута.
public interface OnAzimuthChangedListener { void onAzimuthChanged( float azimuthFrom, float azimuthTo); }
В классе MyCurrentLocation будем определять местоположение устройства. Этот класс реализует такие интерфейсы: GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener.
import android.content.Context; import android.location.Location; import android.os.Bundle; import android.util.Log; 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 MyCurrentLocation implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { private GoogleApiClient mGoogleApiClient; private Location mLastLocation; private LocationRequest mLocationRequest; private OnLocationChangedListener onLocationChangedListener; //передаем интерфейс OnLocationChangedListener в конструкторе для организации //прослушивания события смены местоположения public MyCurrentLocation(OnLocationChangedListener onLocationChangedListener) { this. onLocationChangedListener = onLocationChangedListener; } /** * Создает GoogleApiClient. Использует метод { @code #addApi} для запроса * LocationServices API. */ protected synchronized void buildGoogleApiClient(Context context) { mGoogleApiClient = new GoogleApiClient.Builder(context) .addConnectionCallbacks( this) .addOnConnectionFailedListener( this ) .addApi(LocationServices. API) .build(); //создаем запрос и устанавливаем интервал для его отправки mLocationRequest = LocationRequest. create() .setPriority(LocationRequest. PRIORITY_HIGH_ACCURACY ) .setInterval( 10 * 1000) // 10 seconds, in milliseconds .setFastestInterval(1 * 1000 ); // 1 second, in milliseconds } public void start(){ //Подключает клиента к службам Google Play. mGoogleApiClient.connect(); } public void stop(){ //Закрывает подключение к службам Google Play. mGoogleApiClient.disconnect(); } //После вызова connect(), этот метод будет вызываться асинхронно после успешного завершения запроса подключения. @Override public void onConnected(Bundle bundle) { LocationServices.FusedLocationApi .requestLocationUpdates( mGoogleApiClient, mLocationRequest , this ); mLastLocation = LocationServices.FusedLocationApi .getLastLocation( mGoogleApiClient); if ( mLastLocation != null ) { onLocationChangedListener.onLocationChanged( mLastLocation ); } } //Вызывается, когда клиент временно в отключенном состоянии. @Override public void onConnectionSuspended( int i) { } //Вызывается, когда произошла ошибка при подключении клиента к службе. @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.e( "MyApp" , "Location services connection failed with code " + connectionResult.getErrorCode()); } /* * Реализуем метод onLocationChanged интерфейса LocationListener. Обратный вызов, который возникает, когда изменяется местоположение. * Здесь создаем объект mLastLocation, который хранит последнее местоположение и передаем его в методе интерфейса. */ @Override public void onLocationChanged(Location location) { mLastLocation = LocationServices.FusedLocationApi .getLastLocation( mGoogleApiClient); if ( mLastLocation != null ) { onLocationChangedListener.onLocationChanged( mLastLocation ); } } }
Это местоположение мы будем получать через конструктор этого класса в главном классе приложения, который будет реализовать интерфейс nLocationChangedListener.
А теперь смотрим класс для определения азимута по методу, о котором я говорил в начале урока. Класс MyCurrentAzimuth имплементирует интерфейс SensorEventListener.
import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; public class MyCurrentAzimuth implements SensorEventListener { private SensorManager sensorManager; private Sensor sensor; private int azimuthFrom = 0 ; private int azimuthTo = 0 ; private OnAzimuthChangedListener mAzimuthListener; Context mContext ; //в конструкторе передаем интерфейс OnAzimuthChangedListener и контекст public MyCurrentAzimuth(OnAzimuthChangedListener azimuthListener, Context context) { mAzimuthListener = azimuthListener; mContext = context; } //подключаемся к сенсору и регистрируем слушатель для данного датчика с заданной периодичностью //SENSOR_DELAY_UI - частота обновления пользовательского интерфейса. //TYPE_ROTATION_VECTOR - Возвращает положение устройства в пространстве в виде угла //относительно оси Z, указывающей на север. // Виртуальный датчик, берущий показания от акселерометра, гироскопа и датчика магнитного поля. public void start(){ sensorManager = (SensorManager) mContext .getSystemService( mContext. SENSOR_SERVICE); sensor = sensorManager .getDefaultSensor(Sensor. TYPE_ROTATION_VECTOR); sensorManager.registerListener( this, sensor , SensorManager. SENSOR_DELAY_UI); } //Отменяет регистрацию слушателя для всех датчиков. public void stop(){ sensorManager.unregisterListener( this ); } //вызывается при новом событии датчика //получаем матрицу вращения устройства // в переменную azimuthTo сохраняем градусную меру угла поворота в радианах @Override public void onSensorChanged(SensorEvent event) { azimuthFrom = azimuthTo; float[] orientation = new float[ 3]; float[] rMat = new float[ 9]; SensorManager.getRotationMatrixFromVector (rMat, event. values); azimuthTo = ( int) ( Math. toDegrees( SensorManager.getOrientation( rMat, orientation )[ 0] ) + 360 ) % 360 ; mAzimuthListener.onAzimuthChanged( azimuthFrom , azimuthTo ); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }
Как видите, здесь используется виртуальный датчик, TYPE_ROTATION_VECTOR, который берет информацию сразу с нескольких сенсоров – акселерометра, гироскопа и датчика магнитного поля. И если акселерометр есть в каждом устройстве, то датчик магнитного поля в старых бюджетных телефонах зачастую отсутствует. Следовательно, на таком смартфоне азимут определяться не будет, так как датчик TYPE_ROTATION_VECTOR будет всегда возвращать 0. Но позже я покажу, как мы обойдем это ограничение, чтобы наше приложение работало.
А как получить список сенсоров в вашем устройстве – смотрите по ссылке на экране и в описании видео. На нашем сайте есть небольшая инструкция об этом с перечнем и назначением основных сенсоров: ссылка
А мы рассмотрим следующий класс – MapActivity. Его задача – отображение карты с меткой в месте привязки покемона.
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.Toast; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapFragment; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; public class MapActivity extends AppCompatActivity { GoogleMap googleMap ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout. activity_map); createMapView(); addMarker(); setTitle(getString(R.string. p_name)); } private void createMapView(){ try { if( null == googleMap ){ googleMap = ((MapFragment) getFragmentManager().findFragmentById( R.id. mapView )).getMap(); if( null == googleMap ) { Toast. makeText(getApplicationContext(), "Error creating map",Toast. LENGTH_SHORT).show(); } } } catch (NullPointerException exception){ Log.e( "mapApp" , exception.toString()); } } private void addMarker(){ double lat = CameraViewActivity.TARGET_LATITUDE; double lng = CameraViewActivity.TARGET_LONGITUDE; CameraPosition cameraPosition = new CameraPosition.Builder() .target( new LatLng(lat, lng)) .zoom( 15) .build(); CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition); googleMap.animateCamera(cameraUpdate); if( null != googleMap ){ googleMap.addMarker( new MarkerOptions() .position( new LatLng(lat, lng)) .title(getString(R.string. p_name )) .draggable( false ) ); } } }
Здесь мы просто создаем карту Google Map и добавляем маркер в точку с заранее определенными координатами, для простоты здесь мы их берем из переменных в классе главного активити. В методе добавления маркера также устанавливаем позицию камеры и масштаб карты. Для работы с Google Maps нужно получить apikey в консоли разработчика Google и прописать его в манифесте приложения. Подробнее о получении ключа и работе с картами Google смотрите тут и тут.
Еще нам понадобится класс Pikachu – здесь просто набор переменных: имя и координаты, а также геттеры для них.
public class Pikachu { private String mName; private double mLatitude ; private double mLongitude ; public Pikachu(String newName, double newLatitude, double newLongitude) { this. mName = newName; this. mLatitude = newLatitude; this. mLongitude = newLongitude; } public String getPoiName() { return mName; } public double getPoiLatitude() { return mLatitude; } public double getPoiLongitude() { return mLongitude; } }
После того как вы подготовили данные от датчиков, пришло время для реализации главного класса CameraViewActivity. Первая и наиболее важная вещь заключается в реализации инетрфейса SurfaceHolder.Callback который позволяет отправить изображение с камеры в наш макет и обработать связанные с этим различные события. Интерфейс реализует три метода, ответственных за это: surfaceChanged() surfaceCreated() surfaceDestroyed(). Позже мы рассмотрим реализыцию этих методов более подробно.
Класс CameraViewActivity имплементирует также интерфейсы OnLocationChangedListener, OnAzimuthChangedListener, с их методами onLocationChanged и onAzimuthChanged, а также интерфейс View.OnClickListener – слушатель нажатия кнопки с методом onClick.
import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; import android.hardware.Camera; import android.location.Location; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class CameraViewActivity extends Activity implements SurfaceHolder.Callback, OnLocationChangedListener, OnAzimuthChangedListener, View.OnClickListener { //объявляем необходимые переменные private Camera mCamera; private SurfaceHolder mSurfaceHolder; private boolean isCameraviewOn = false ; private Pikachu mPoi; private double mAzimuthReal = 0 ; private double mAzimuthTeoretical = 0 ; /*нам понадобятся, помимо прочего, две константы для хранения допустимых отклонений дистанции и азимута устройства от целевых. Значения подобраны практически, вы можете их менять, чтобы облегчить, или наоборот, усложнить задачу поиска покемона. Точность дистанции указана в условных единицах, равных примерно 0.9м, а точность азимута - в градусах*/ private static final double DISTANCE_ACCURACY = 20 ; private static final double AZIMUTH_ACCURACY = 10 ; private double mMyLatitude = 0 ; private double mMyLongitude = 0 ; /*также создаем константы с координатами цели, это будет местоположение покемона. Здесь укажите широту и долготу любого места, которое находится недалеко от вас - например, координаты соседнего двора или ближайшего магазина - чтобы далеко не бегать. Особо ленивые могут указать свое текущее местоположение. Получить координаты любого места можно, например, через приложение Google карты. */ public static final double TARGET_LATITUDE = 27.590377 ; public static final double TARGET_LONGITUDE = 14.425153 ; private MyCurrentAzimuth myCurrentAzimuth; private MyCurrentLocation myCurrentLocation; TextView descriptionTextView; ImageView pointerIcon; Button btnMap; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout. activity_camera_view); setRequestedOrientation(ActivityInfo. SCREEN_ORIENTATION_PORTRAIT); setupListeners(); setupLayout(); setAugmentedRealityPoint(); } //создаем экземпляр покемона с указанием координат его местоположения private void setAugmentedRealityPoint() { mPoi = new Pikachu( getString(R.string. p_name ), TARGET_LATITUDE, TARGET_LONGITUDE ); } /*вычисляем дистанцию между устройством и покемоном по формуле, о которой я говорил в начале урока. Результат приходит в десятичных градусах, умножение его на 100000 дает некую условную единицу, приблизительно равную 0.9м. Чтобы перевести результат в метрическую систему, нужно применять сложные расчеты, и я решил не усложнять приложение.*/ public double calculateDistance() { double dX = mPoi .getPoiLatitude() - mMyLatitude; double dY = mPoi .getPoiLongitude() - mMyLongitude; double distance = (Math. sqrt(Math.pow (dX, 2 ) + Math.pow(dY, 2 )) * 100000 ); return distance; } /*вычисляем теоретический азимут по формуле, о которой я говорил в начале урока. Вычисление азимута для разных четвертей производим на основе таблицы. */ public double calculateTeoreticalAzimuth() { double dX = mPoi .getPoiLatitude() - mMyLatitude; double dY = mPoi .getPoiLongitude() - mMyLongitude ; double phiAngle; double tanPhi; double azimuth = 0; tanPhi = Math.abs (dY / dX); phiAngle = Math.atan (tanPhi); phiAngle = Math.toDegrees (phiAngle); if (dX > 0 && dY > 0) { // I quater return azimuth = phiAngle; } else if (dX < 0 && dY > 0) { // II return azimuth = 180 - phiAngle; } else if (dX < 0 && dY < 0) { // III return azimuth = 180 + phiAngle; } else if (dX > 0 && dY < 0) { // IV return azimuth = 360 - phiAngle; } return phiAngle; } //расчитываем точность азимута, необходимую для отображения покемона private List<Double> calculateAzimuthAccuracy( double azimuth) { double minAngle = azimuth - AZIMUTH_ACCURACY ; double maxAngle = azimuth + AZIMUTH_ACCURACY ; List<Double> minMax = new ArrayList<Double>(); if (minAngle < 0) minAngle += 360; if (maxAngle >= 360) maxAngle -= 360; minMax.clear(); minMax.add(minAngle); minMax.add(maxAngle); return minMax; } //Метод isBetween определяет, находится ли азимут в целевом диапазоне с учетом допустимых отклонений private boolean isBetween( double minAngle, double maxAngle, double azimuth) { if (minAngle > maxAngle) { if (isBetween( 0, maxAngle, azimuth) && isBetween(minAngle, 360 , azimuth)) return true ; } else { if (azimuth > minAngle && azimuth < maxAngle) return true ; } return false; } // выводим на экран основную информацию о местоположении цели и нашего устройства private void updateDescription() { long distance = ( long ) calculateDistance(); int tAzimut = ( int ) mAzimuthTeoretical ; int rAzimut = ( int ) mAzimuthReal ; String text = mPoi.getPoiName() + " location:" + "\n latitude: " + TARGET_LATITUDE + " longitude: " + TARGET_LONGITUDE + "\n Current location:" + "\n Latitude: " + mMyLatitude + " Longitude: " + mMyLongitude + "\n " + "\n Target azimuth: " + tAzimut + " \n Current azimuth: " + rAzimut + " \n Distance: " + distance; descriptionTextView.setText(text); } /*переопределяем метод слушателя OnAzimuthChangeListener, который вызывается при изменении азимута устройства, расчитанного на основании показаний датчиков, получаемых в параметрах этого метода из класса MyCurrentAsimuth. Получаем данные азимута устройства, сравниваем их с целевыми параметрами - проверяем, если азимуты реальный и теоретический, а также дистанция до цели совпадают в пределах допустимых значений, отображаем картинку покемона на экране. Также вызываем метод обновления информации о местоположении на экране.*/ @Override public void onAzimuthChanged( float azimuthChangedFrom, float azimuthChangedTo) { mAzimuthReal = azimuthChangedTo; mAzimuthTeoretical = calculateTeoreticalAzimuth(); int distance = ( int ) calculateDistance(); pointerIcon = (ImageView) findViewById(R.id. icon ); double minAngle = calculateAzimuthAccuracy(mAzimuthTeoretical ).get( 0); double maxAngle = calculateAzimuthAccuracy(mAzimuthTeoretical ).get( 1); if ((isBetween(minAngle, maxAngle, mAzimuthReal )) && distance <= DISTANCE_ACCURACY ) { pointerIcon.setVisibility(View. VISIBLE ); } else { pointerIcon.setVisibility(View. INVISIBLE ); } updateDescription(); } /*переопределяем метод onLocationChanged интерфейса слушателя OnLocationChangedListener, здесь при изменении местоположения отображаем тост с новыми координатами и вызываем метод, который выводит основную информацию на экран.*/ @Override public void onLocationChanged(Location location) { mMyLatitude = location.getLatitude(); mMyLongitude = location.getLongitude(); mAzimuthTeoretical = calculateTeoreticalAzimuth(); Toast.makeText (this , "latitude: "+location.getLatitude()+ " longitude: "+location.getLongitude(), Toast. LENGTH_SHORT ).show(); //если устройство возвращает азимут = 0 отображаем картинку на основе значения дистанции if (mAzimuthReal == 0){ if ( distance <= DISTANCE_ACCURACY) { pointerIcon.setVisibility(View.VISIBLE); } else { pointerIcon.setVisibility(View.INVISIBLE); } } updateDescription(); } /*в методе жизненного цикла onStop мы вызываем методы отмены регистрации датчика азимута и закрытия подключения к службам Google Play*/ @Override protected void onStop() { myCurrentAzimuth.stop(); myCurrentLocation.stop(); super.onStop(); } //в методе onResume соответственно открываем подключение и регистрируем слушатель датчиков @Override protected void onResume() { super.onResume(); myCurrentAzimuth.start(); myCurrentLocation.start(); } /*метод setupListeners служит для инициализации слушателей местоположения и азимута - здесь мы вызываем конструкторы классов MyCurrentLocation и MyCurrentAzimuth и выполняем их методы start*/ private void setupListeners() { myCurrentLocation = new MyCurrentLocation( this); myCurrentLocation.buildGoogleApiClient( this ); myCurrentLocation.start(); myCurrentAzimuth = new MyCurrentAzimuth( this, this); myCurrentAzimuth.start(); } //метод setupLayout инициализирует все элементы экрана и создает surfaceView для отображения превью камеры private void setupLayout() { descriptionTextView = (TextView) findViewById(R.id.cameraTextView ); btnMap = (Button) findViewById(R.id. btnMap ); btnMap.setVisibility(View. VISIBLE ); btnMap.setOnClickListener( this ); getWindow().setFormat(PixelFormat. UNKNOWN); SurfaceView surfaceView = (SurfaceView) findViewById(R.id.cameraview ); mSurfaceHolder = surfaceView.getHolder(); mSurfaceHolder.addCallback( this ); mSurfaceHolder.setType(SurfaceHolder. SURFACE_TYPE_PUSH_BUFFERS ); } /*вызывается сразу же после того, как были внесены любые структурные изменения (формат или размер) surfaceView. Здесь , в зависимости от условий, стартуем или останавливаем превью камеры*/ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if ( isCameraviewOn ) { mCamera.stopPreview(); isCameraviewOn = false ; } if ( mCamera != null ) { try { mCamera .setPreviewDisplay( mSurfaceHolder); mCamera .startPreview(); isCameraviewOn = true ; } catch (IOException e) { e.printStackTrace(); } } } /*вызывается при первом создании surfaceView, здесь получаем доступ к камере и устанавливаем ориентацию дисплея превью*/ @Override public void surfaceCreated(SurfaceHolder holder) { mCamera = Camera. open(); mCamera.setDisplayOrientation( 90); } //вызывается перед уничтожением surfaceView, останавливаем превью и освобождаем камеру @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); mCamera = null ; isCameraviewOn = false ; } //и последний метод - обработчик нажатия кнопки, здесь по нажатию открываем карту @Override public void onClick(View v) { Intent intent = new Intent( this , MapActivity. class); startActivity(intent); } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="info.fandroid.example.augmentedreality" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="18" android:targetSdkVersion="22" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/Theme.AppCompat.Light"> <activity android:name="info.fandroid.example.augmentedreality.CameraViewActivity" android:label="@string/title_activity_camera_view"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="info.fandroid.example.augmentedreality.MapActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="info.fandroid.example.augmentedreality.CameraViewActivity" /> </activity> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="AIzaSyCGGfjXTiou9cLPOoQ3kFBpMpf3eMU1-5w" /> </application> </manifest>
android.permission.ACCESS_FINE_LOCATION
– позволяет максимально точно определять местоположение, используя все доступные способы: GPS, Wi-Fi и сеть сотовой связи- ACCESS_COARSE_LOCATION – позволяет приложению получить доступ к приблизительному местоположению.
- com.google.android.providers.gsf.permission.READ_GSERVICES позволяет работать с гулокартами
Приветствую, ребят поделитесь рабочим исходником или может есть уже готовый на гитхабе?
Посмотрите источник (ссылка в конце) – он обновлен, там есть новые исходники.
Добрый день. Для андроид 6 и выше нужно заменить: “mCamera = Camera. open();” на “if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
//ask for authorisation
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 50);
else
{
mCamera = Camera. open();}” т.к. для подключения к камере требуется интерактивное подтверждение пользователя.
Добрый вечер. Интересует вопрос определения текущего азимута. При повороте телефона горизонтально на 90 градусов. Текущий азимут изменяется на 90 градусов. И получается, если мы смотрим в одну точку но в разном положении, у нас будут разные азимуты. Как это можно исправить?