无论你在哪里捏缩放android,都要保持地图居中

时间:2015-03-30 17:03:40

标签: android google-maps location zoom

我希望做一些类似于优步处理捏缩放事件的方式。无论您在屏幕上捏什么,它都会使地图居中并放大中心位置。有没有办法在地图片段上没有某种叠加的情况下做到这一点?或者我应该只禁用地图的事件,在地图片段上创建叠加层,并处理叠加层中的所有缩放/其他事件?

6 个答案:

答案 0 :(得分:21)

我花了大约3天的时间在谷歌搜索后建立了完整的解决方案。我的回答是从https://stackoverflow.com/a/32734436/3693334编辑的。

public class CustomMapView extends MapView {

    private int fingers = 0;
    private GoogleMap googleMap;
    private long lastZoomTime = 0;
    private float lastSpan = -1;
    private Handler handler = new Handler();

    private ScaleGestureDetector scaleGestureDetector;
    private GestureDetector gestureDetector;

    public CustomMapView(Context context) {
        super(context);
    }

    public CustomMapView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomMapView(Context context, AttributeSet attrs, int style) {
        super(context, attrs, style);
    }

    public CustomMapView(Context context, GoogleMapOptions options) {
        super(context, options);
    }

    public void init(GoogleMap map) {
        scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.OnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                if (lastSpan == -1) {
                    lastSpan = detector.getCurrentSpan();
                } else if (detector.getEventTime() - lastZoomTime >= 50) {
                    lastZoomTime = detector.getEventTime();
                    googleMap.animateCamera(CameraUpdateFactory.zoomBy(getZoomValue(detector.getCurrentSpan(), lastSpan)), 50, null);
                    lastSpan = detector.getCurrentSpan();
                }
                return false;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                lastSpan = -1;
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                lastSpan = -1;

            }
        });
        gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {

                disableScrolling();
                googleMap.animateCamera(CameraUpdateFactory.zoomIn(), 400, null);

                return true;
            }
        });
        googleMap = map;
    }

    private float getZoomValue(float currentSpan, float lastSpan) {
        double value = (Math.log(currentSpan / lastSpan) / Math.log(1.55d));
        return (float) value;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        gestureDetector.onTouchEvent(ev);
        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_POINTER_DOWN:
                fingers = fingers + 1;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                fingers = fingers - 1;
                break;
            case MotionEvent.ACTION_UP:
                fingers = 0;
                break;
            case MotionEvent.ACTION_DOWN:
                fingers = 1;
                break;
        }
        if (fingers > 1) {
            disableScrolling();
        } else if (fingers < 1) {
            enableScrolling();
        }
        if (fingers > 1) {
            return scaleGestureDetector.onTouchEvent(ev);
        } else {
            return super.dispatchTouchEvent(ev);
        }
    }

    private void enableScrolling() {
        if (googleMap != null && !googleMap.getUiSettings().isScrollGesturesEnabled()) {
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    googleMap.getUiSettings().setAllGesturesEnabled(true);
                }
            }, 50);
        }
    }

    private void disableScrolling() {
        handler.removeCallbacksAndMessages(null);
        if (googleMap != null && googleMap.getUiSettings().isScrollGesturesEnabled()) {
            googleMap.getUiSettings().setAllGesturesEnabled(false);
        }
    }
}

并自定义MapFragment

public class CustomMapFragment extends Fragment {

        CustomMapView view;
        Bundle bundle;
        GoogleMap map;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            bundle = savedInstanceState;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.fragment_map, container, false);

            view = (CustomMapView) v.findViewById(R.id.mapView);
            view.onCreate(bundle);
            view.onResume();

            map = view.getMap();
            view.init(map);

            MapsInitializer.initialize(getActivity());

            return v;
        }

        public GoogleMap getMap() {
            return map;
        }

        @Override
        public void onResume() {
            super.onResume();
            view.onResume();
        }

        @Override
        public void onPause() {
            super.onPause();
            view.onPause();
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            view.onDestroy();
        }

        @Override
        public void onLowMemory() {
            super.onLowMemory();
            view.onLowMemory();
        }
    }

最后,在您的活动中:

....
<fragment
    android:id="@+id/map"
    class="yourpackage.CustomMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
...

我已经在Android 4.1(API 16)和后者上进行了测试,它运行良好且流畅。 (关于API&lt; 16,我没有任何设备可以测试)。

答案 1 :(得分:6)

这是MechEthan想到的代码。

  1. 首先,您必须检测叠加视图上的双击。

    public class TouchableWrapper extends FrameLayout {
        private final GestureDetector.SimpleOnGestureListener mGestureListener
                = new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {
    
                //Notify the event bus (I am using Otto eventbus of course) that you have just received a double-tap event on the map, inside the event bus event listener
                EventBus_Singleton.getInstance().post(new EventBus_Poster("double_tapped_map"));
    
                return true;
            }
        };
    
        public TouchableWrapper(Context context) {
            super(context);
            mGestureDetector = new GestureDetectorCompat(context, mGestureListener);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            mGestureDetector.onTouchEvent(ev);
    
            return super.onInterceptTouchEvent(ev);
        }
    }
    
  2. 无论您何时抓住mapView,请将mapView包装在上面创建的TouchableWrapper中。这就是我这样做的原因,因为我有一个问题需要将mapFragment添加到另一个片段中,所以我需要一个自定义的SupportMapFragment来执行此操作

    public class CustomMap_Fragment extends SupportMapFragment {
    
        TouchableWrapper mTouchView;
    
        public CustomMap_Fragment() {
            super();
        }
    
        public static CustomMap_Fragment newInstance() {
            return new CustomMap_Fragment();
        }
    
        @Override
        public View onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) {
            View mapView = super.onCreateView(arg0, arg1, arg2);
    
            Fragment fragment = getParentFragment();
            if (fragment != null && fragment instanceof OnMapReadyListener) {
                ((OnMapReadyListener) fragment).onMapReady();
            }
    
            mTouchView = new TouchableWrapper(getActivity());
            mTouchView.addView(mapView);
    
            return mTouchView;
        }
    
        public static interface OnMapReadyListener {
            void onMapReady();
        }
    }
    
  3. 在我的Map_Fragment中(最终将位于支持导航抽屉和片段事务以切换视图的活动中的FrameLayout内)

    mMapFragment = CustomMap_Fragment.newInstance();
    getChildFragmentManager().beginTransaction().replace(R.id.map_container, mMapFragment).commit();
    
  4. 现在终于在我刚拿到地图的同一个片段中,EventBus接收器在收到&#34; double_tapped_map&#34;

    时会执行以下操作
    @Subscribe public void eventBus_ListenerMethod(AnswerAvailableEvent event) {
        //Construct a CameraUpdate object that will zoom into the exact middle of the map, with a zoom of currentCameraZoom + 1 unit
       zoomInUpdate = CameraUpdateFactory.zoomIn();
       //Run that with a speed of 400 ms.
       map.animateCamera(zoomInUpdate, 400, null);
    }
    
  5. 注意:为了完美实现这一点,您需要在地图上停用zoomGestures(意味着您执行myMap.getUiSettings().setZoomGesturesEnabled(false);。如果您不这样做,您将可以双击很快在地图上你会看到它会从中心缩放,因为双击的实现与我在第一个答案中的实现完全一样,即它们减去当前时间之前的点击时间,所以在那个窗口你可以滑入第三个点击它不会触发事件总线事件,谷歌地图将捕捉它;所以禁用缩放手势。

    然而,你会看到捏合/熄灭将不再起作用,你也必须处理捏,我也已经完成但需要再花1个小时而且我没有时间去做那个但是,当我这样做时,我会更新答案。

    提示:优步已禁用地图上的旋转手势。 map.getUiSettings().setRotateGesturesEnabled(false);

答案 2 :(得分:2)

就个人而言,我只会在地图上禁用缩放手势,检测叠加上的捏合,然后将其他所有内容传递到地图。

google-maps v2 API没有任何明确的自定义缩放处理功能。虽然我确信你可以注入一些东西,但是叠加方法可以使你免受google-maps更改的影响,并且可以让你在需要时更轻松地支持其他地图提供者。

(只是为了完整性:您还可以处理相机后更改事件并重新居中,但这将是一种愚蠢,糟糕的用户体验。)

答案 3 :(得分:1)

您可以使用LatLngBounds限制地图从您想要的位置移动。 (您可以将绑定的东北角和西南角设置为相同的点)。

请查看以下链接。

https://developers.google.com/maps/documentation/android-api/views

答案 4 :(得分:1)

我也有相同的要求。我必须了解如何在android中处理事件以解决此问题,因为我们必须拦截触摸事件以进行缩放并将滚动事件传递给地图。 为此,我们需要在Google Map View上自定义View。我们的自定义视图拦截触摸事件,并通过不给基础地图提供处理机会的机会来决定是否处理后续事件,或者只是让基础地图独自处理所有事件。

现在的代码时间- 这里我们需要两件事-一个自定义片段,一个自定义视图。

  1. 自定义片段

    public class CustomMapFragment extends SupportMapFragment implements OnMapReadyCallback {
    
    public View mapView = null;
    
    public WrapperView wrapperView = null;
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        mapView = super.onCreateView(inflater, parent, savedInstanceState);
        wrapperView = new WrapperView(getActivity());
        wrapperView.addView(mapView);
        SupportMapFragment mapFragment = (SupportMapFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
        return wrapperView;
    }
    
    @Override
    public View getView() {
        return mapView;
    }
    
    @Override
    public void onMapReady(GoogleMap googleMap) {
        wrapperView.setGoogleMap(googleMap);
    }
    
  2. 自定义视图

    public class WrapperView extends FrameLayout {
    
    private GoogleMap googleMap;
    
    Activity activity = null;
    
    ScaleGestureDetector scaleGestureDetector;
    
    public WrapperView(Activity activity) {
        super(activity);
        this.activity=activity;
        scaleGestureDetector = new ScaleGestureDetector(activity ,new MyOnScaleGestureListener());
    }
    
    public void setGoogleMap(GoogleMap map){
        googleMap = map;
    }
    
    private boolean isZoomInProgress(MotionEvent event){
        if(event.getPointerCount()>1){
            return true;
        }
        return false;
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event){
        return isZoomInProgress(event);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event){
        return scaleGestureDetector.onTouchEvent(event);
    }
    
    public class MyOnScaleGestureListener extends
            ScaleGestureDetector.SimpleOnScaleGestureListener {
    
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float previousSpan = detector.getPreviousSpan();
            float currentSpan = detector.getCurrentSpan();
            float targetSpan;
            if(previousSpan>currentSpan){
                targetSpan = previousSpan-currentSpan;
            }else{
                targetSpan = currentSpan-previousSpan;
            }
            float scaleFactor = detector.getScaleFactor();
            if (scaleFactor > 1) {
                if(googleMap.getCameraPosition().zoom!=googleMap.getMaxZoomLevel()) {
                    for(int j=0;j<(targetSpan*2);j++){
                        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(googleMap.getCameraPosition().target, googleMap.getCameraPosition().zoom + 0.002f));
                    }
                }
            } else {
                if (googleMap.getCameraPosition().zoom != googleMap.getMinZoomLevel()) {
                    for(int j=0;j<(targetSpan*2);j++){
                        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(googleMap.getCameraPosition().target, googleMap.getCameraPosition().zoom - 0.002f));
                    }
                }
            }
            return true;
        }
    
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }
    
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {}
    }
    

在您的视图中使用新的自定义片段,如下所示-

 <fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="yourpackage.CustomMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

答案 5 :(得分:0)

对于您的setLatLngBoundsForCameraTarget()对象,可以使用GoogleMap方法轻松实现。在Android版Google Maps SDK的“相机和视图”页面上:

private GoogleMap mMap;
// Create a LatLngBounds that includes the city of Adelaide in Australia.
private LatLngBounds ADELAIDE = new LatLngBounds(
  new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));
// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);`

请相信我,如果您将LatLngBounds放置在标记的确切坐标上,则可以使相机保持在标记的中心。容易!