在谷歌地图中旋转两层标记图标
Rotate a two layer marker icon in GoogleMap
在我的应用程序中,我将一组标记添加到我的地图中,如下所示:
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)
}
这是我的可绘制对象:
<?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>
这样的东西:
我的问题是使图标变平并设置旋转,使可绘制对象内的汽车图标旋转到。我只想让第一层旋转。理想情况下,我只希望第一层(蓝色箭头)平整且旋转,第二层(汽车图标)不平整且不旋转。
有什么方法可以制作具有不同选项或类似内容的 two-layer 标记图标吗?
为此,您需要两个旋转中心点(见图 1):
图 1 - 旋转中心点
P1 - "flat" 部分标记的旋转中心;
P2 - "non-flat" 部分标记的旋转中心。
因此,通过默认标记旋转内部 "non flat" 部分是不可能的 - 它们只有一个旋转中心点 - P1。也很难确定复合绘图的 P2 坐标:需要精确的绘图内部 pathData
坐标读取、边界框和中心点计算等。
但是如果你有单独的占位符和汽车绘图那么就不需要制作一个双层标记图标:你可以确定内部图标的旋转中心(图 1 中的 P2)作为占位符(外部 "flat") 和内部图标和旋转可以在 MapView
-based custom view 中通过在地图 canvas 上自定义绘制每个可绘制对象来实现(占位符需要旋转绘制)。
TLDR;
例如,占位符可绘制 (icon_vehicle_marker.xml
) 如:
<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>
和车内可绘制对象 (icon_car.xml
) 如:
<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>
使用 MarkersMapView
自定义视图,例如:
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
喜欢:
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();
}
}
和activity_main.xml
喜欢:
<?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>
你会得到类似的东西:
mCarOffset
- P1-P2 距离在 "placeholder" 和 "inner" 标记图标上测量并硬编码。
NB! 这只是一个标记的演示。例如,如果您有许多(数百个)标记,您应该确定应该准确绘制哪些标记等以提高性能。
在我的应用程序中,我将一组标记添加到我的地图中,如下所示:
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)
}
这是我的可绘制对象:
<?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>
这样的东西:
我的问题是使图标变平并设置旋转,使可绘制对象内的汽车图标旋转到。我只想让第一层旋转。理想情况下,我只希望第一层(蓝色箭头)平整且旋转,第二层(汽车图标)不平整且不旋转。
有什么方法可以制作具有不同选项或类似内容的 two-layer 标记图标吗?
为此,您需要两个旋转中心点(见图 1):
图 1 - 旋转中心点
P1 - "flat" 部分标记的旋转中心;
P2 - "non-flat" 部分标记的旋转中心。
因此,通过默认标记旋转内部 "non flat" 部分是不可能的 - 它们只有一个旋转中心点 - P1。也很难确定复合绘图的 P2 坐标:需要精确的绘图内部 pathData
坐标读取、边界框和中心点计算等。
但是如果你有单独的占位符和汽车绘图那么就不需要制作一个双层标记图标:你可以确定内部图标的旋转中心(图 1 中的 P2)作为占位符(外部 "flat") 和内部图标和旋转可以在 MapView
-based custom view 中通过在地图 canvas 上自定义绘制每个可绘制对象来实现(占位符需要旋转绘制)。
TLDR;
例如,占位符可绘制 (icon_vehicle_marker.xml
) 如:
<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>
和车内可绘制对象 (icon_car.xml
) 如:
<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>
使用 MarkersMapView
自定义视图,例如:
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
喜欢:
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();
}
}
和activity_main.xml
喜欢:
<?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>
你会得到类似的东西:
mCarOffset
- P1-P2 距离在 "placeholder" 和 "inner" 标记图标上测量并硬编码。
NB! 这只是一个标记的演示。例如,如果您有许多(数百个)标记,您应该确定应该准确绘制哪些标记等以提高性能。