Reputation: 1039
In my application I add a set number of markers to my map like this:
private fun addMarker(googleMap: GoogleMap, location: Location) {
val options = MarkerOptions()
options.position(LatLng(location.latitude, location.longitude))
options.rotation(location.bearing)
options.anchor(0.5f, 0.5f)
options.flat(true)
val drawable = ContextCompat.getDrawable(context, R.drawable.background_vehicle) as LayerDrawable
val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
options.icon(BitmapDescriptorFactory.fromBitmap(bitmap))
googleMap.addMarker(options)
}
And this is my drawable:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/icon_vehicle_marker" />
<item android:id="@+id/vehicle_image" android:bottom="5dp"
android:drawable="@drawable/icon_car" android:left="5dp"
android:right="5dp" android:top="10dp" />
</layer-list>
Which makes something like this:
My problem is that making the icon flat and setting a rotation, makes the car icon inside the drawable rotate to. I just want for the first layer to rotate. Ideally, I just want the first layer (the blue arrow) to be flat and rotate and the second layer (the car icon) to not be flat and not rotate.
Is there any way to make a two-layer marker icon with different options or something like that?
Upvotes: 2
Views: 665
Reputation: 13343
To do that you need two points-centers of rotation (see Fig. 1):
Fig.1 - Rotation center points
P1 - rotation center for "flat" part of marker;
P2 - rotation center for "non-flat" part of marker.
So, rotate inner "non flat" part is impossible via default markers - they have only one rotation center point - P1. Also it's hard to determine P2 coordinates for composite drawable: that needs exactly inner part of drawable pathData
coordinates reading, bounding box and center point calculation etc.
But if you have separately placeholder and car drawables then no need to make a two-layer marker icon: you can determine rotation center of inner icon (P2 on fig 1) as offset between placeholder (outer "flat") and inner icons and rotation can be implemented in MapView
-based custom view via custom drawing each drawable over map canvas (placeholder need to be drawn with rotation).
TLDR;
For example, with placeholder drawable (icon_vehicle_marker.xml
) like:
<vector android:height="24dp" android:viewportHeight="511.999"
android:viewportWidth="511.999" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#006DF0" android:pathData="M405.961,62.116C365.906,22.06 312.649,0 256,0c-56.648,0 -109.905,22.06 -149.962,62.116C64.694,103.46 44.023,157.77 44.023,212.077s20.672,108.617 62.016,149.961L256,511.999L405.96,362.037c41.345,-41.345 62.016,-95.653 62.016,-149.961C467.976,157.77 447.306,103.461 405.961,62.116zM384.751,340.828L256,469.579L127.249,340.828c-35.497,-35.497 -53.244,-82.124 -53.244,-128.751s17.748,-93.255 53.244,-128.751C161.64,48.936 207.365,29.996 256,29.996c48.636,0 94.36,18.94 128.751,53.33c35.497,35.497 53.245,82.124 53.245,128.751S420.247,305.331 384.751,340.828z"/>
</vector>
and inner car drawable (icon_car.xml
) like:
<vector android:height="24dp" android:viewportHeight="459"
android:viewportWidth="459" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#006DF0" android:pathData="M405.45,51c-5.101,-15.3 -20.4,-25.5 -35.7,-25.5H89.25c-17.85,0 -30.6,10.2 -35.7,25.5L0,204v204c0,15.3 10.2,25.5 25.5,25.5H51c15.3,0 25.5,-10.2 25.5,-25.5v-25.5h306V408c0,15.3 10.2,25.5 25.5,25.5h25.5c15.3,0 25.5,-10.2 25.5,-25.5V204L405.45,51zM89.25,306C68.85,306 51,288.15 51,267.75s17.85,-38.25 38.25,-38.25s38.25,17.85 38.25,38.25S109.65,306 89.25,306zM369.75,306c-20.4,0 -38.25,-17.85 -38.25,-38.25s17.85,-38.25 38.25,-38.25S408,247.35 408,267.75S390.15,306 369.75,306zM51,178.5L89.25,63.75h280.5L408,178.5H51z"/>
</vector>
With MarkersMapView
custom view like:
public class MarkersMapView extends MapView implements OnMapReadyCallback {
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private Marker mMarker;
private int mPlaceholderWidth = 150;
private int mPlaceholderHeight = 150;
private int mCarWidth = 75;
private int mCarHeight = 75;
private int mCarOffset = 90;
private Drawable mPlaceholderDrawable;
private Drawable mCarDrawable;
public MarkersMapView(@NonNull Context context) {
super(context);
init(context);
}
public MarkersMapView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MarkersMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public MarkersMapView(@NonNull Context context, @Nullable GoogleMapOptions options) {
super(context, options);
init(context);
}
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawMarker(canvas);
canvas.restore();
}
private void init(Context context) {
setWillNotDraw(false);
mPlaceholderDrawable = ContextCompat.getDrawable(context, R.drawable.icon_vehicle_marker);
mPlaceholderDrawable.setBounds(0, 0 , mPlaceholderWidth, mPlaceholderHeight);
mCarDrawable = ContextCompat.getDrawable(context, R.drawable.icon_car);
mCarDrawable.setBounds(0, 0 , mCarWidth, mCarHeight);
postInvalidate();
}
@Override
public void getMapAsync(OnMapReadyCallback callback) {
mMapReadyCallback = callback;
super.getMapAsync(this);
}
@Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
@Override
public void onCameraMove() {
invalidate();
}
});
if (mMapReadyCallback != null) {
mMapReadyCallback.onMapReady(googleMap);
}
}
private void drawMarker(Canvas canvas) {
if (mGoogleMap == null || mMarker == null) {
return;
}
Projection mapProjection = mGoogleMap.getProjection();
// get screen coordinates of marker
final Point pointMarker = mapProjection.toScreenLocation(mMarker.getPosition());
canvas.save();
// move origin to screen coordinates of marker shifted by placeholder icon sizes
canvas.translate(pointMarker.x - mPlaceholderWidth / 2, pointMarker.y - mPlaceholderHeight);
// rotate canvas according bearing of GoogleMap camera view
canvas.rotate(-mGoogleMap.getCameraPosition().bearing, mPlaceholderWidth / 2, mPlaceholderHeight);
mPlaceholderDrawable.draw(canvas);
// revert origin back
canvas.restore();
// calculate position of inner icon center point
float dx = (float) (mCarOffset * Math.sin(Math.toRadians(-mGoogleMap.getCameraPosition().bearing))) - mCarWidth / 2;
float dy = (float) (-mCarOffset * Math.cos(Math.toRadians(-mGoogleMap.getCameraPosition().bearing))) - mCarHeight / 2;
// move origin to screen coordinates of inner icon center point shifted by placeholder icon size
canvas.translate(pointMarker.x + dx, pointMarker.y + dy);
mCarDrawable.draw(canvas);
}
public void addMarker(MarkerOptions markerOptions) {
removeMarker();
mMarker = mGoogleMap.addMarker(markerOptions.visible(false));
}
public void removeMarker() {
mGoogleMap.clear();
mMarker = null;
}
}
MainActivity.java
like:
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey";
private static final LatLng CAR = new LatLng(50.450311, 30.523730);
private GoogleMap mGoogleMap;
private MarkersMapView mMapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY);
}
mMapView = (MarkersMapView) findViewById(R.id.mapview);
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mMapView.addMarker(new MarkerOptions()
.position(CAR)
.flat(true)
.draggable(false));
}
});
}
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
protected void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
}
and activity_main.xml
like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="activities.MainActivity">
<{YOUR_PACKAGE_NAME}.MarkersMapView
android:id="@+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
you'll got something like that:
mCarOffset
- P1-P2 distance measured on "placeholder" and "inner" icons of marker and hardcoded.
NB! This is only demo for one marker. If you have, for example, many (hundreds of) markers you should determine which markers exactly should be drawn etc. for increasing performance.
Upvotes: 2