问题描述
我在我的项目中使用google maps
.如果标记未显示在屏幕上,我想在屏幕上放置箭头.如何控制屏幕?
I am using google maps
on my project. I want to put arrow to screen if marker not showing on the screen. How can I control screen?
推荐答案
实际上,您的问题的答案在 lakshman中. pasala 评论,但其实现有点复杂(TLDR).
Actually, the answer for your question is in lakshman.pasala comment, but its implementation is a little bit complex (TLDR).
因此,获取Google Maps Screen Control的最好方法是实现自定义视图扩展了 MapView
类.在这种情况下,您可以完全控制视图画布上的绘图.为此,您应该覆盖 dispatchDraw()
方法(因为MapView
扩展了FrameLayout
(即ViewGroup
))并在其中实现了箭头图.像这样的东西:
So, best way to get Google Maps Screen Control - is implement custom view which extends MapView
class. In that case you can get full control of drawing on view canvas. To do that you should override dispatchDraw()
method (because MapView
extends FrameLayout
which is ViewGroup
) and implement within it arrow drawing. Something like that:
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawArrowToMarker(canvas);
canvas.restore();
}
,并且您需要在每次地图移动/缩放/旋转时通过invalidate()
调用它.要检测地图的移动/缩放/旋转,您需要 (恰好 GoogleMap.setOnCameraMoveListener()
方法).您可以在自定义MapView类中声明GoogleMap
对象并通过setter进行设置,但如果自定义MapView类将实现 OnMapReadyCallback
接口,并在onMapReady()
回调中获取它.另外,您还需要几种实用的方法来添加/删除标记,确定线段的相交,方向等.自定义视图的完整源代码(例如EnhanchedMapView
)可以是:
and you need to call it via invalidate()
on every map move/zoom/rotate. For detect map move/zoom/rotate you need GoogleMap
(exactly GoogleMap.setOnCameraMoveListener()
method). You can declare GoogleMap
object inside your custom MapView class and set it via setter, but better if custom MapView class will implement OnMapReadyCallback
interface and get it in onMapReady()
callback. Also your need several utility methods for adding/removing marker, determine segments intersections, directions etc. Full source code of custom view (e.g. EnhanchedMapView
) can be like:
public class EnhanchedMapView extends MapView implements OnMapReadyCallback {
private final static int ARROW_PADDING = 50;
private final static double ARROW_ANGLE = Math.PI / 6;
private final static double ARROW_LENGTH = 100;
private final static double ARROW_SIZE = 50;
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private Marker mMarker;
private Paint mPaintArrow;
public EnhanchedMapView(@NonNull Context context) {
super(context);
init();
}
public EnhanchedMapView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public EnhanchedMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public EnhanchedMapView(@NonNull Context context, @Nullable GoogleMapOptions options) {
super(context, options);
init();
}
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawArrowToMarker(canvas);
canvas.restore();
}
private void drawArrowToMarker(Canvas canvas) {
if (mGoogleMap == null || mMarker == null) {
return;
}
VisibleRegion visibleRegion = mGoogleMap.getProjection().getVisibleRegion();
LatLngBounds screenBounds = visibleRegion.latLngBounds;
LatLng mapCenter = screenBounds.getCenter();
Projection mapProjection = mGoogleMap.getProjection();
final Point pointMapCenter = mGoogleMap.getProjection().toScreenLocation(mapCenter);
final Point pointTopLeft = mapProjection.toScreenLocation(visibleRegion.farLeft);
final Point pointTopRight = mapProjection.toScreenLocation(visibleRegion.farRight);
final Point pointBottomLeft = mapProjection.toScreenLocation(visibleRegion.nearLeft);
final Point pointBottomRight = mapProjection.toScreenLocation(visibleRegion.nearRight);
final Point pointMarker = mapProjection.toScreenLocation(mMarker.getPosition());
final Point tl = new Point(pointTopLeft.x + ARROW_PADDING, pointTopLeft.y + ARROW_PADDING);
final Point tr = new Point(pointTopRight.x - ARROW_PADDING, pointTopRight.y + ARROW_PADDING);
final Point br = new Point(pointBottomRight.x - ARROW_PADDING, pointBottomRight.y - ARROW_PADDING);
final Point bl = new Point(pointBottomLeft.x + ARROW_PADDING, pointBottomLeft.y - ARROW_PADDING);
final Point pointIntersection = getBoundsIntersection(tl, tr, br, bl, pointMapCenter, pointMarker);
if (pointIntersection != null) {
double angle = Math.atan2(pointMarker.y - pointMapCenter.y, pointMarker.x - pointMapCenter.x);
int arrowX, arrowY;
arrowX = (int) (pointIntersection.x - ARROW_LENGTH * Math.cos(angle));
arrowY = (int) (pointIntersection.y - ARROW_LENGTH * Math.sin(angle));
canvas.drawLine(pointIntersection.x, pointIntersection.y, arrowX, arrowY, mPaintArrow);
arrowX = (int) (pointIntersection.x - ARROW_SIZE * Math.cos(angle + ARROW_ANGLE));
arrowY = (int) (pointIntersection.y - ARROW_SIZE * Math.sin(angle + ARROW_ANGLE));
canvas.drawLine(pointIntersection.x, pointIntersection.y, arrowX, arrowY, mPaintArrow);
arrowX = (int) (pointIntersection.x - ARROW_SIZE * Math.cos(angle - ARROW_ANGLE));
arrowY = (int) (pointIntersection.y - ARROW_SIZE * Math.sin(angle - ARROW_ANGLE));
canvas.drawLine(pointIntersection.x, pointIntersection.y, arrowX, arrowY, mPaintArrow);
}
}
private void init() {
setWillNotDraw(false);
mPaintArrow = new Paint();
mPaintArrow.setColor(Color.BLUE);
mPaintArrow.setStrokeWidth(15);
}
@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);
}
}
public void addMarker(MarkerOptions markerOptions) {
removeMarker();
mMarker = mGoogleMap.addMarker(markerOptions);
}
public void removeMarker() {
mGoogleMap.clear();
}
private static boolean floatEquals(float f1, float f2) {
final double EPS = 1e-6;
return (Math.abs(f1 - f2) < EPS);
}
private static Point getBoundIntersection(Point p11, Point p12, Point p21, Point p22) {
double x, y;
Point intersectionPoint = null;
// test intersection with vertical bound
if (floatEquals(p12.x, p11.x) || floatEquals(p22.x, p21.x)) {
if (floatEquals(p12.x, p11.x) && floatEquals(p22.x, p21.x) && !floatEquals(p11.x, p21.x)) {
return null;
} else {
if (floatEquals(p12.x, p11.x)) {
x = p11.x;
y = (x - p21.x) / (p22.x - p21.x) * (p22.y - p21.y) + p21.y;
if (x >= Math.min(p21.x, p22.x) && x <= Math.max(p21.x, p22.x)
&& y >= Math.min(p11.y, p12.y) && y <= Math.max(p11.y, p12.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
} else {
x = p21.x;
y = (x - p11.x) / (p12.x - p11.x) * (p12.y - p11.y) + p11.y;
if (x >= Math.min(p11.x, p12.x) && x <= Math.max(p11.x, p12.x)
&& y >= Math.min(p21.y, p22.y) && y <= Math.max(p21.y, p22.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
}
}
} else {
// test intersection with horizontal bound
if (floatEquals(p12.y, p11.y) || floatEquals(p22.y, p21.y)) {
if (floatEquals(p12.y, p11.y) && floatEquals(p22.y, p21.y) && !floatEquals(p11.y, p21.y)) {
return null;
} else {
if (floatEquals(p12.y, p11.y)) {
y = p12.y;
x = (y - p21.y) / (p22.y - p21.y) * (p22.x - p21.x) + p21.x;
if (x >= Math.min(p11.x, p12.x) && x <= Math.max(p11.x, p12.x)
&& y >= Math.min(p21.y, p22.y) && y <= Math.max(p21.y, p22.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
} else {
y = p21.y;
x = (y - p11.y) / (p12.y - p11.y) * (p12.x - p11.x) + p11.x;
if (x >= Math.min(p21.x, p22.x) && x <= Math.max(p21.x, p22.x)
&& y >= Math.min(p11.y, p12.y) && y <= Math.max(p11.y, p12.y)) {
intersectionPoint = new Point((int) x, (int) y);
}
}
}
}
}
return intersectionPoint;
}
private static Point getBoundsIntersection(Point tl, Point tr, Point br, Point bl, Point p1, Point p2) {
Point intersectionPoint = null;
if ((intersectionPoint = getBoundIntersection(tl, tr, p1, p2)) != null) {
return intersectionPoint;
} else if ((intersectionPoint = getBoundIntersection(tr, br, p1, p2)) != null) {
return intersectionPoint;
} else if ((intersectionPoint = getBoundIntersection(br, bl, p1, p2)) != null) {
return intersectionPoint;
} else if ((intersectionPoint = getBoundIntersection(bl, tl, p1, p2)) != null) {
return intersectionPoint;
}
return null;
}
}
您可以通过以下方式在MainActivity
中使用它:
And you can use it from MainActivity
this way:
public class MainActivity extends AppCompatActivity {
private static final String MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey";
static final LatLng KYIV = new LatLng(50.450311, 30.523730);
private EnhanchedMapView 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 = (EnhanchedMapView) findViewById(R.id.mapview);
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
mMapView.addMarker(new MarkerOptions().position(KYIV).title("Kyiv"));
}
});
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Bundle mapViewBundle = outState.getBundle(MAP_VIEW_BUNDLE_KEY);
if (mapViewBundle == null) {
mapViewBundle = new Bundle();
outState.putBundle(MAP_VIEW_BUNDLE_KEY, mapViewBundle);
}
mMapView.onSaveInstanceState(mapViewBundle);
}
@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="{YOUR_PACKAGE_NAME}.MainActivity">
<{YOUR_PACKAGE_NAME}.EnhanchedMapView
android:id="@+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</RelativeLayout>
因此,您应该得到类似的内容:
As a result you should get something like that:
这篇关于Google Maps屏幕控制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!