偏离MapFragment的中心以获取移动目标lat/lng和缩放级别的动画

Dan*_*Lew 49 android android-maps-v2

我有一个UI,它有一个MapFragment,透明视图覆盖在它上面.地图占据整个屏幕,而视图只占屏幕的左三分之一.结果,地图的默认"中心"关闭.当有人点击标记时,我想将该标记居中在MapFragment的整个可见区域(而不是MapFragment本身的中心).

由于这很难用文字描述,让我用几张图片.假设这是我的UI看起来的样子:

默认地图

当用户点击一个标记时,我想要将它居中放大以便更接近它.没有任何调整,这是你会得到的:

使用错误的居中标记映射

我想要的是标记居中,但在右边的空间,如下所示:

使用正确居中的标记映射

如果您不使用地图投影更改缩放级别,则很容易实现此专长:

// Offset the target latitude/longitude by preset x/y ints
LatLng target = new LatLng(latitude, longitude);
Projection projection = getMap().getProjection();
Point screenLocation = projection.toScreenLocation(target);
screenLocation.x += offsetX;
screenLocation.y += offsetY;
LatLng offsetTarget = projection.fromScreenLocation(screenLocation);

// Animate to the calculated lat/lng
getMap().animateCamera(CameraUpdateFactory.newLatLng(offsetTarget));
Run Code Online (Sandbox Code Playgroud)

但是,如果您同时更改缩放级别,则上述计算不起作用(因为lat/lng偏移在不同的缩放级别发生变化).

让我来看一下尝试修复的列表:

  • 快速更改缩放级别,进行计算,缩放回原始摄像机位置,然后设置动画.不幸的是,突然的相机改变(即使它只是一瞬间)很遗憾非常明显,我想避免闪烁.

  • 将两个MapFragments叠加在一起,让一个执行计算,而另一个显示.我发现MapFragments并不是真正构建成彼此叠加的(这条路线上有不可避免的错误).

  • 通过缩放级别平方的差异修改屏幕位置的x/y.从理论上讲,它应该可以工作,但它总是相当长一段时间(〜.1纬度/经度,足够远离它).

有没有办法计算offsetTarget即使缩放级别改变?

Kyl*_*vey 37

最新版本的播放服务库添加了一个setPadding()功能GoogleMap.此填充将所有相机移动投影到偏移地图中心.本文档进一步描述了地图填充的行为方式.

这里最初提出的问题现在可以通过添加相当于左侧布局宽度的左边距填充来解决.

mMap.setPadding(leftPx, topPx, rightPx, bottomPx);
Run Code Online (Sandbox Code Playgroud)

  • 这对我有用.一行代码解决方案. (4认同)

小智 16

我找到了一个非常适合这个问题的解决方案.解决方案是从原始偏移量计算所需缩放的偏移中心,然后为地图的相机设置动画.为此,首先将地图的相机移动到所需的缩放,计算该缩放级别的偏移,然后恢复原始缩放.计算新中心后,我们可以使用CameraUpdateFactory.newLatLngZoom制作动画.

我希望这有帮助!

private void animateLatLngZoom(LatLng latlng, int reqZoom, int offsetX, int offsetY) {

    // Save current zoom
    float originalZoom = mMap.getCameraPosition().zoom;

    // Move temporarily camera zoom
    mMap.moveCamera(CameraUpdateFactory.zoomTo(reqZoom));

    Point pointInScreen = mMap.getProjection().toScreenLocation(latlng);

    Point newPoint = new Point();
    newPoint.x = pointInScreen.x + offsetX;
    newPoint.y = pointInScreen.y + offsetY;

    LatLng newCenterLatLng = mMap.getProjection().fromScreenLocation(newPoint);

    // Restore original zoom
    mMap.moveCamera(CameraUpdateFactory.zoomTo(originalZoom));

    // Animate a camera with new latlng center and required zoom.
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(newCenterLatLng, reqZoom));

}
Run Code Online (Sandbox Code Playgroud)


mun*_*324 11

编辑:以下代码适用于已弃用的v1映射.这个SO答案说明了如何在v2中执行类似的操作:如何在Google Map V2 for Android中获取纬度/经度范围

您需要的关键方法应该使用long/lat和百分比而不是像素.现在,mapview将围绕地图的中心进行缩放,然后移动到"重新定位"曝光区域中的图钉.您可以在放大度数后获得宽度,考虑某些屏幕偏差,然后重新定位地图.以下是按下"+"之后的以下代码的示例屏幕截图:之前 在此输入图像描述在此输入图像描述 主要代码:

@Override
            public void onZoom(boolean zoomIn) {
                // TODO Auto-generated method stub
                controller.setZoom(zoomIn ? map.getZoomLevel()+1 : map.getZoomLevel()-1);
                int bias = (int) (map.getLatitudeSpan()* 1.0/3.0); // a fraction of your choosing
                map.getLongitudeSpan();
                controller.animateTo(new GeoPoint(yourLocation.getLatitudeE6(),yourLocation.getLongitudeE6()-bias));

            }
Run Code Online (Sandbox Code Playgroud)

所有代码:

    package com.example.testmapview;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.view.Menu;
import android.widget.ZoomButtonsController;
import android.widget.ZoomButtonsController.OnZoomListener;

public class MainActivity extends MapActivity {
    MapView map;
    GeoPoint yourLocation;
    MapController controller;
    ZoomButtonsController zoomButtons;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        map=(MapView)findViewById(R.id.mapview);
        map.setBuiltInZoomControls(true);
        controller = map.getController();
        zoomButtons = map.getZoomButtonsController();

        zoomButtons.setOnZoomListener(new OnZoomListener(){

            @Override
            public void onVisibilityChanged(boolean arg0) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onZoom(boolean zoomIn) {
                // TODO Auto-generated method stub
                controller.setZoom(zoomIn ? map.getZoomLevel()+1 : map.getZoomLevel()-1);
                int bias = (int) (map.getLatitudeSpan()* 1.0/3.0); // a fraction of your choosing
                map.getLongitudeSpan();
                controller.animateTo(new GeoPoint(yourLocation.getLatitudeE6(),yourLocation.getLongitudeE6()-bias));

            }
        });

        //Dropping a pin, setting center
        Drawable marker=getResources().getDrawable(android.R.drawable.star_big_on);
        MyItemizedOverlay myItemizedOverlay = new MyItemizedOverlay(marker);
        map.getOverlays().add(myItemizedOverlay);
        yourLocation = new GeoPoint(0, 0);
        controller.setCenter(yourLocation);
        myItemizedOverlay.addItem(yourLocation, "myPoint1", "myPoint1");
        controller.setZoom(5);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    @Override
    protected boolean isRouteDisplayed() {
        // TODO Auto-generated method stub
        return false;
    }

}
Run Code Online (Sandbox Code Playgroud)

和基本布局:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testmapview"
    android:versionCode="1"
    android:versionName="1.0" >
<meta-data
    android:name="com.google.android.maps.v2.API_KEY"
    android:value="YOURKEYHERE:)"/>
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

<uses-permission android:name="android.permission.INTERNET" /> 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <uses-library android:name="com.google.android.maps"/>

        <activity
            android:name="com.example.testmapview.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
Run Code Online (Sandbox Code Playgroud)

如果这还没有完全回答你的问题,请告诉我,因为这是一个有趣的实验.


Mic*_*Fry 5

API 的投影内容并不是那么有用,我遇到了同样的问题并自己解决了问题。

public class SphericalMercatorProjection {

    public static PointD latLngToWorldXY(LatLng latLng, double zoomLevel) {
        final double worldWidthPixels = toWorldWidthPixels(zoomLevel);
        final double x = latLng.longitude / 360 + .5;
        final double siny = Math.sin(Math.toRadians(latLng.latitude));
        final double y = 0.5 * Math.log((1 + siny) / (1 - siny)) / -(2 * Math.PI) + .5;
        return new PointD(x * worldWidthPixels, y * worldWidthPixels);
    }

    public static LatLng worldXYToLatLng(PointD point, double zoomLevel) {
        final double worldWidthPixels = toWorldWidthPixels(zoomLevel);
        final double x = point.x / worldWidthPixels - 0.5;
        final double lng = x * 360;

        double y = .5 - (point.y / worldWidthPixels);
        final double lat = 90 - Math.toDegrees(Math.atan(Math.exp(-y * 2 * Math.PI)) * 2);

        return new LatLng(lat, lng);
    }

    private static double toWorldWidthPixels(double zoomLevel) {
        return 256 * Math.pow(2, zoomLevel);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用此代码,您可以围绕新目标(即不是中心)转换地图。我选择使用锚点系统,其中 (0.5, 0.5) 是默认值,代表屏幕的中间。(0,0) 是左上角,(1, 1) 是左下角。

private LatLng getOffsetLocation(LatLng location, double zoom) {
    Point size = getSize();
    PointD anchorOffset = new PointD(size.x * (0.5 - anchor.x), size.y * (0.5 - anchor.y));
    PointD screenCenterWorldXY = SphericalMercatorProjection.latLngToWorldXY(location, zoom);
    PointD newScreenCenterWorldXY = new PointD(screenCenterWorldXY.x + anchorOffset.x, screenCenterWorldXY.y + anchorOffset.y);
    newScreenCenterWorldXY.rotate(screenCenterWorldXY, cameraPosition.bearing);
    return SphericalMercatorProjection.worldXYToLatLng(newScreenCenterWorldXY, zoom);
}
Run Code Online (Sandbox Code Playgroud)

基本上,您使用投影来获得世界 XY 坐标,然后偏移该点并将其转回 LatLng。然后,您可以将其传递给您的地图。PointD 是简单类型,它包含 x,y 作为双精度值并且还进行旋转。

public class PointD {

    public double x;
    public double y;

    public PointD(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public void rotate(PointD origin, float angleDeg) {
        double rotationRadians = Math.toRadians(angleDeg);
        this.x -= origin.x;
        this.y -= origin.y;
        double rotatedX = this.x * Math.cos(rotationRadians) - this.y * Math.sin(rotationRadians);
        double rotatedY = this.x * Math.sin(rotationRadians) + this.y * Math.cos(rotationRadians);
        this.x = rotatedX;
        this.y = rotatedY;
        this.x += origin.x;
        this.y += origin.y;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您同时更新地图方位角和位置,请务必在 getOffsetLocation 中使用新方位角。