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»
}
}
В файл манифеста:
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 фреймворка в файле манифеста приложения декларируются необходимые разрешения и наличие сенсоров устройства. android: layout_width=«match_parent» android: layout_height=«match_parent» android: id="@+id/parentFrameLayout»> android: id="@+id/beyondarFragment» android:name="com.beyondar.android.fragment.BeyondarFragmentSupport» android: layout_width=«match_parent» android: layout_height=«match_parent» /> Далее создается объект 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» } } В файл манифеста добавим: 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 Map добавим Google API Key в файл манифеста. Для того получим ключ в Google Developers Console и добавим в тег android:name="com.google.android.geo. API_KEY» android: value=«AIzaSyBcRu9Vvb7…» /> Изменим файл компоновки content_main. xml. 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» > android: layout_width=«match_parent» android: layout_height=«match_parent»> android: id="@+id/beyondarFragment» android:name="com.beyondar.android.fragment.BeyondarFragmentSupport» android: layout_width=«match_parent» android: layout_height=«match_parent» /> Изменим код класса главной активности. 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 private Set 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»);