Дополненная реальность (Augmented Reality) не является какой-то новой технологией, но ее применение было замечено широкой публикой с появлением игры Pokemon GO, которая показала, что технология AR имеет большой потенциал.В книге рассмотрены различные способы разработки приложений с дополненной реальностью, от нативной разработки в Android Studio до использования таких движков, как Unity.
Приведённый ознакомительный фрагмент книги Разработка Android-приложений с Augmented Reality предоставлен нашим книжным партнёром — компанией ЛитРес.
Купить и скачать полную версию книги в форматах FB2, ePub, MOBI, TXT, HTML, RTF и других
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-radar-plugin-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»);
Конец ознакомительного фрагмента.
Приведённый ознакомительный фрагмент книги Разработка Android-приложений с Augmented Reality предоставлен нашим книжным партнёром — компанией ЛитРес.
Купить и скачать полную версию книги в форматах FB2, ePub, MOBI, TXT, HTML, RTF и других