Setup
To use the Favendo Map in your Android application you have to add our Maven repository
to your project’s build.gradle
file.
allprojects {
repositories {
maven {
url "${favendo_artifactory_url}/backspin-android-sdk"
credentials {
username = "${favendo_artifactory_user}"
password = "${favendo_artifactory_password}"
}
name = "plugins-favendo"
}
}
}
The minimum API level of the SDK is 15. But in order to use any of the BLE related features you need at least API level 18. |
In order to use our repository you need to authenticate with a valid
username and password. It is a good practice to declare those as variables
by adding the following lines to your project’s gradle.properties
file.
favendo_artifactory_user = your_name
favendo_artifactory_password = your_password
favendo_artifactory_url = http://maven-repository.favendo.de/artifactory
Now you have to add the dependency to your module’s build.gradle
file.
Map Implementations
Favendo offers two map implementations from which you can choose.
-
Google Maps
-
Favendo MapView
dependencies {
compile 'com.favendo.android:backspin-sdk-googlemap:3.1.0'
-- or --
compile 'com.favendo.android:backspin-sdk-favendomap:3.1.0'
}
No matter if you imported the googlemap
or the favendomap
dependency
it includes the Backspin SDK of the same version.
If your app already uses the Backspin SDK you can simply remove it from your
build.gradle
file.
The Favendo Map uses the Backspin SDK and will only work if the SDK is already initialized and a root venue is loaded. For more information on how to work with the Backspin SDK take a look here. |
If you work with the Google Maps implementation don’t forget to add the Google API Key to your app’s manifest. Look here for further instructions.
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_GOOGLE_MAPS_API_KEY"/>
This library uses Vector Drawables. In order to support Vector Drawables
on devices running Android versions prior to version 5.0 (API level 21) you have
to add vectorDrawables.useSupportLibrary = true to your app’s build.gradle file.
Look
here
for further information.
|
Map Fragment
The Favendo Map SDK uses a fragment to show an indoor map of the currently
loaded root venue.
In order to do this you first have to subclass from the according MapFragment
(FavendoMapFragment
or GoogleMapFragment
)
and override its abstract onMapReady()
method.
public class MyMapFragment extends FavendoMapFragment {
@Override
public void onMapReady() {
// interact with the map
// add markers, move camera, etc.
}
}
When the map is initialized and the first level plan loaded, onMapReady()
gets
called. At this point the map is ready to be used. You can add markers, move the
camera, switch levels and do various other supported operations on the map fragment.
This fragment retains its instance and can therefore not be added via
xml - use a FragmentTransaction instead. It also results in onMapReady() being
called only once, regardless of any configuration changes (like rotating the device).
|
The following code snippet shows an example of an activity that adds the map fragment
by a FragmentTransaction
. R.id.fragmentContainer
refers to an empty FrameLayout
that
exists inside the activity’s layout xml.
public class MyMapActivity extends AppCompatActivity {
private static final String TAG_FRAGMENT = "MyMapFragment";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT) == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer, new MyMapFragment(), TAG_FRAGMENT)
.commit();
}
}
}
Configuration
There are several settings that adjust the behavior of the map to your liking.
Just override the onConfiguration(MapConfig)
method in your map fragment and
modify the MapConfig
according to your needs.
public class MyMapFragment extends FavendoMapFragment {
[...]
@Override
protected void onConfiguration(MapConfig mapConfig) {
super.onConfiguration(mapConfig);
mapConfig.setRenderWorldTiles(true);
mapConfig.setInitialZoomLevel(17);
[...]
}
}
RenderWorldTiles |
Sets whether the map should render any world tiles that don’t belong to the indoor location. |
InitialLevelNumber |
Sets the initial level which is shown when the map is opened. |
InitialZoomLevel |
Sets the initial zoom level of the camera. If there is an active position, this value is ignored. |
InitialZoomLevelOnPosition |
Sets the initial zoom level of the camera. If there is no active position, this value is ignored. |
MinZoomLevel |
Sets the minimum zoom level of the camera. It is not possible to zoom out further than the zoom level set here. |
MaxZoomLevel |
Sets the maximum zoom level of the camera. It is not possible to zoom in further than the zoom level set here. |
RestoreCamera |
Sets whether the camera state should be restored after the map fragment is recreated. |
RestoreCameraTimeout |
Sets the time in milliseconds that has to pass before the saved camera state is no longer relevant. |
PositionPathSnapping |
Sets whether the position should be snapped to the navigation graph before showing it on the map. |
NavigationEnabled |
Sets whether the marker detail will show a navigation action button or not. This button can be used to start a navigation to the selected marker. |
PositionMarkerCompassBearing |
Sets whether the position marker should be rotated by the devices orientation (compass and gyroscope). |
AllGesturesEnabled |
Sets whether the user can use gestures to control the camera. |
ScrollGesturesEnabled |
Sets whether the user can use gestures to scroll the camera. |
RotateGesturesEnabled |
Sets whether the user can use gestures to rotate the camera. |
ZoomGesturesEnabled |
Sets whether the user can use gestures to zoom the camera. |
TiltGesturesEnabled |
Sets whether the user can use gestures to tilt the camera. |
Image loading
Some of the UI elements like VenueMarker
and MarkerDetailDefault
depend on the loading of images from the web.
There are several libraries available like Universal Image Loader,
Picasso or Glide.
To connect the MapFragment
with the image loader from your app,
you need to override the createImageLoader
method,
implement the MapImageLoader
interface and return that instance.
This interface provides methods for asynchronous and synchronous image requests.
The following code snippet shows the method which needs to be overridden in the
MapFragment
and how the methods should be used:
@Override
protected MapImageLoader createImageLoader() {
return new MapImageLoader() {
@Override
public void loadImageAsync(String url, int width, int height, BitmapLoadedListener loadedListener) {
// use your custom image loader library and start a asynchronous request to get the image
// call onBitmapLoaded on the listener to pass the loaded bitmap back to the map or call onBitmapFailed in case of an error
}
@Override
public Bitmap loadImageSync(String url, int width, int height) {
// use your custom image loader library and start synchronous request
// finally return the loaded bitmap directly
return bitmap;
}
};
}
Indoor Markers
IndoorMarker
is the base class of all markers used by the map.
It holds information about the geo coordinate and the level it belongs to.
Use the following methods of the map fragment to manipulate the markers
on the map:
getMarkers()
|
Returns all previously added |
addMarker(IndoorMarker)
|
Add an |
removeMarker(IndoorMarker)
|
Removes a previously added |
removeMarkers()
|
Removes all previously added |
refreshMarker(IndoorMarker)
|
Forces an |
showMarkers()
|
Sets the visibility of all markers to true and refreshes them. |
hideMarkers()
|
Sets the visibility of all markers to false and refreshes them. |
hideMarkersExcept(IndoorMarker)
|
Sets the visibility of all markers except the specified ones to false and refreshes them. |
showMarkers(VisibilityPredicate)
|
Control the visibility of each marker separately by passing a custom visibility predicate. |
selectMarker(IndoorMarker)
|
Changes the state of the given marker to
|
addVenueMarkers()
|
Adds a |
An IndoorMarker
can have one of the following states:
IndoorMarker.STATE_DEFAULT
|
This is the default state.
The marker can be selected by the user or by calling
|
IndoorMarker.STATE_SELECTED
|
This is the selected state. It usually results in a highlighted visual representation. |
IndoorMarker.STATE_DISABLED
|
This is the disabled state. Markers of this state can not be selected and are usually grayed out. |
You can create your own markers by sub-classing IndoorMarker
and implementing
the loadBitmap(Context, BitmapLoadedListener)
method. Pass a
bitmap to the listener according to the markers state. This method is called
whenever you add or refresh the marker.
There is also an already implemented VenueCategoryMarker
which can be used
for a convenient visual representation of a venue.
It renders the logo of the first VenueCategory
that is associated with
the venue, together with its name below.
Marker Detail View
Whenever a marker is selected, the SDK will show a UI element that provides further information about the selection.
The method createMarkerDetail()
of the map fragment determines which
marker detail view is used by the map. If you want to customize the existing
implementation or inject a custom one you have to override it.
There are several constructor overloads which can be used to customize
the colors of the MarkerDetailDefault
.
The default implementation looks like this:
protected MarkerDetail createMarkerDetail() {
return new MarkerDetailDefault(this, this);
}
The MarkerDetailDefault
has the ability to show information about VenueMarker
instances, like the VenueCategoryMarker
. If you want to use it to cover other
markers in a similar way you have to subclass MarkerDetailDefault
and override
the apply(Position, IndoorMarker, boolean)
method like this:
@Override
public void apply(Position position, IndoorMarker marker, boolean navigationEnabled) {
if (marker instanceof MyCustomMarker) {
mTitle.setText(...);
mSubTitle.setText(...);
mImageContainer.setVisibility(GONE);
return;
}
super.apply(marker);
}
If you don’t want to use the MarkerDetailDefault
you can create
a custom class which implements the MarkerDetail
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
apply(Position, IndoorMarker, boolean)
|
This method gets called
when a marker is selected initially and every time the position gets updated.
The third parameter comes from the |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. |
Camera
Use the following methods to control the camera:
moveCamera(CameraPosition)
|
Sets the camera to the given camera position. |
animateCamera(CameraPosition, int)
|
Animates the camera to the given camera position. Specify the animation time in milliseconds. |
getCameraLocation()
|
Returns the current camera position. |
getCameraZoomLevel()
|
Returns the current zoom level of the camera. |
moveCameraToPosition()
|
Sets the camera position to the current user’s indoor location, if there is one. |
animateCameraToPosition()
|
Animates the camera position to the current user’s indoor location. |
moveCameraAndSelectMarker(IndoorMarker)
|
Sets the camera position to the given marker’s indoor location and performs a marker selection. |
animateCameraAndSelectMarker(IndoorMarker)
|
Animates the camera position to the given marker’s indoor location and performs a marker selection. |
Position Follow Mode
The map has a position follow mode that can be enabled by using
enablePositionFollowMode()
or disabled by using disablePositionFollowMode()
.
While the position follow mode is enabled, the camera always animates to center
the latest user position. The map will also perform a level switch, if needed.
Any user interaction like gestures, a level switch or a marker selection will
disable the position follow mode.
The position follow view allows the user to enable the position follow mode. The default implementation is a button that changes its color when the follow mode is enabled or disabled. It will only show itself when a user position is available.
The method createPositionFollow()
of the map fragment determines which
position follow view is used by the map. If you want to customize the existing
position follow or inject a custom one you have to override it.
There is a constructor overload which can be used to customize
the icon and colors of the PositionFollowDefault
.
The default implementation looks like this:
protected PositionFollow createPositionFollow() {
return new PositionFollowDefault(getContext(), this);
}
If you don’t want to use the PositionFollowDefault
you can create
a custom class which implements the PositionFollow
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
enable()
|
This method gets called when the position follow mode is enabled. Update your view to represent this accordingly. |
disable()
|
This method gets called when the position follow mode is disabled. Update your view to represent this accordingly. It will also be called initially. |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. It will also be called initially. |
Levels
The Favendo Map needs at least one level. You can override the defineLevels()
method to decide which levels are available to the map.
By default the system gets all levels from the current root venue.
Level Switcher
This SDK also comes with a default level switcher UI element.
It tells the user which level is currently displayed and where the user
is currently located. When switching through the different levels all
IndoorMarker
instances will automatically be shown or hidden just as the
level plan overlay.
The method createLevelSwitcher()
of the map fragment determines which
level switcher is used by the map. If you want to customize the existing level
switcher or inject a custom one you have to override it.
There are several constructor overloads which can be used to customize
the size and colors of the LevelSwitcherDefault
.
The default implementation looks like this:
protected LevelSwitcher createLevelSwitcher() {
return new LevelSwitcherDefault(
getContext(),
ThemeColorUtil.fetchPrimaryColor(getContext()),
ContextCompat.getColor(getContext(), R.color.level_switcher_foreground_selected));
}
If you don’t want to use the LevelSwitcherDefault
you can create
a custom class which implements the LevelSwitcher
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
initialize(List<Level>, LevelSwitchListener)
|
This method gets called when the map initializes. Set up your custom switcher according to the given levels. Use the listener to start a level switch. |
requestLevelSwitch(Level)
|
This method gets called when the map fragment is
about to perform a level switch, either through your call on the listener or a
|
finishedLevelSwitch(Level)
|
This method gets called when the map fragment finished a level switch. Update your UI accordingly. |
addIndicator(LevelIndicator)
|
This method gets called whenever an indicator
should be shown within the |
removeIndicator(LevelIndicator)
|
This method gets called whenever an indicator
should be removed from the |
Level Indicators
Indicators help the user to keep track of several information on different levels,
like their position, the navigation destination or associated assets.
The LevelSwitcherDefault
uses small icons to the left and to the right
of the level number, but if you implemented a custom LevelSwitcher
you are
completely free in their visual representation.
There are two system indicators which are always added by default. One is for the current users position and one for its final navigation destination. If you want to add more indicators you can use the following builder:
LevelIndicator myIndicator = new LevelIndicator.Builder(this)
.setLevel(1)
.setAlignment(IndicatorAlignment.RIGHT)
.setDrawable(R.drawable.custom_icon)
.setZIndex(1f)
.add();
The add()
method returns a new instance of type LevelIndicator
.
Keep this instance if you want to remove it later.
To achieve this you simply have to call the
LevelSwitcher.removeIndicator(LevelIndicator)
method.
Every Indicator also has an z-Index which specifies its visual priority.
This is needed whenever multiple indicators are added on the same level and
the same side.
The system indicators for position and the navigation destination
have a value of 0.5
.
If you choose an higher value the system indicator gets hidden and your icon
will be shown instead of the system one.
A new LevelIndicator
will have a z-Index of 0 and therefore the system icons
will have a higher priority by default.
Position Marker
The Favendo Map SDK automatically registers for position updates from the Backspin SDK and renders a position marker on the map.
If you want to customize what PositionMarker
is used you can simply override
the createPositionMarker(Position)
method of the map fragment.
This method gets called when the first position update is received.
The default implementation looks like this:
protected PositionMarker createPositionMarker(Position position) {
return new PositionMarkerDefault(
position,
mMapConfig.isPositionMarkerCompassBearing(),
GuiUtil.dipToPixel(getActivity(), 38),
ThemeColorUtil.fetchAccentColor(getContext()));
}
Use PositionMarkerBitmap
if you want to use a position marker with a custom bitmap.
Remember to start scanning and positioning of the Backspin SDK in order to receive position updates. |
Navigation
One of the core features of the map is indoor routing / turn by turn navigation.
It uses the navigation API provided by the Backspin SDK and displays the resulting
NavigationRoute
accordingly. The MarkerDetailDefault
view already comes with
an action button that triggers turn by turn navigation to the selected venue -
if NavigationEnabled
of the MapConfig
is true. There is no further implementation
effort needed if this behaviour suits your use case.
In any case, these methods of the map fragment are used to control the indoor navigation:
startNavigation(NavigationTask)
|
Starts a new turn by turn navigation using
the provided |
stopNavigation()
|
Stops the currently running navigation. |
isNavigating()
|
Returns true if there is an active navigation. |
IndoorPath
There is also an object to manage polylines on the map. An IndoorPath
has many indoor locations which are connected via a simple line. This line can
be customized in color and width.
IndoorPath myIndoorPath = new IndoorPath(locations, Color.BLACK);
Every path segment will automatically be shown or hidden according
to the current level which is selected. You can also attach IndoorMarkers
which are added and removed together with the whole IndoorPath
object.
To do so use the addPath(IndoorPath)
method of your map fragment.
You can also remove and refresh previously added paths via the according methods.
There are several methods which are involved when showing the path
for a navigation route.
By default the system creates one IndoorPath
for an navigation which is
started via the startNavigation(NavigationTask)
method.
A new path will be created and added inside the onRouteCalculated
callback.
To customize the navigation path you can simply override
the createNavigationPath()
method. The default implementation looks like this:
protected IndoorPath createNavigationPath() {
return new IndoorPath(
GuiUtil.dipToPixel(getContext(), 4),
ThemeColorUtil.fetchAccentColor(getContext()));
}
If you want to manually add an IndoorPath
for any NavigationRoute
you can
simply call the addRoute(NavigationRoute)
which then returns
the created an added path.
I you changed locations or other fields of the IndoorPath you
have to call refreshPaths() afterwards.
|
Navigation Info
The navigation info view provides the user information about the currently active navigation. The default implementation displays the name of the navigation target together with distance and time to destination. The view also includes a button that is used to stop the current navigation.
The method createNavigationInfo()
of the map fragment determines which
navigation info view is used by the map. If you want to customize the existing
navigation info or inject a custom one you have to override it.
There is a constructor overload which can be used to customize
the foreground and background color of the NavigationInfoDefault
.
The default implementation looks like this:
protected NavigationInfo createNavigationInfo() {
return new NavigationInfoDefault(getContext(), this);
}
If you don’t want to use the NavigationInfoDefault
you can create
a custom class which implements the NavigationInfo
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
apply(Position, String, NavigationRoute)
|
This method gets called when a new
route has been calculated. Update the view with the information about the
latest |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. |
Turn By Turn
The turn by turn view provides the user information about the next directions of the currently active navigation. The default implementation displays an arrow icon together with a short text to describe the direction.
The method createTurnByTurn()
of the map fragment determines which
turn by turn view is used by the map. If you want to customize the existing
turn by turn or inject a custom one you have to override it.
There are several constructor overloads which can be used to customize
the colors of the TurnByTurnDefault
.
The default implementation looks like this:
protected TurnByTurn createTurnByTurn() {
return new TurnByTurnDefault(
getContext(),
ContextCompat.getColor(getContext(), R.color.turn_by_turn_background),
ContextCompat.getColor(getContext(), R.color.turn_by_turn_background_second),
ContextCompat.getColor(getContext(), R.color.turn_by_turn_foreground),
ContextCompat.getColor(getContext(), R.color.turn_by_turn_foreground),
ThemeColorUtil.fetchAccentColor(getContext()),
ThemeColorUtil.fetchAccentColor(getContext()));
}
The following strings are used by the turn by turn UI. Provide your own translations if needed.
<string name="direction_straight">Straight ahead</string>
<string name="direction_bear_left">Slightly Left</string>
<string name="direction_left">Turn Left</string>
<string name="direction_bear_right">Slightly Right</string>
<string name="direction_right">Turn Right</string>
<string name="direction_level_up">Level Up</string>
<string name="direction_level_down">Level Down</string>
<string name="direction_destination_reached">Destination Reached</string>
If you don’t want to use the TurnByTurnDefault
you can create
a custom class which implements the TurnByTurn
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
apply(NavigationDirection, NavigationDirection)
|
This method gets called when a new route has been calculated. Update the view with the information about the next two navigation directions. |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. |