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

Тимур Машнин

Дополненная реальность (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 и других

Смотрите также

а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ э ю я