В очередном выпуске «Как создать андроид-приложение» мы рассмотрим приложение с эффектом дополненной реальности, как игра 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 градусов. И получается, если мы смотрим в одну точку но в разном положении, у нас будут разные азимуты. Как это можно исправить?