保持地图居中无论你在哪里捏缩放安卓

Posted

技术标签:

【中文标题】保持地图居中无论你在哪里捏缩放安卓【英文标题】:Keep map centered regardless of where you pinch zoom on android 【发布时间】:2015-06-03 19:31:08 【问题描述】:

我希望做一些类似于 Uber 处理捏缩放事件的方式。无论您在屏幕上的哪个位置捏合,它都会使地图居中并放大中心位置。有没有办法做到这一点,而无需在地图片段上进行某种覆盖?或者我应该禁用地图的事件,在地图片段上创建一个覆盖,并处理来自覆盖的所有缩放/其他事件?

【问题讨论】:

请对此功能请求加注星标,让 Google 知道我们需要它:issuetracker.google.com/issues/69795937 【参考方案1】:

我花了大约 3 天时间在 google 上搜索后建立了完整的解决方案。我的答案是从https://***.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_
    android:layout_/>
...

我已经在 Android 4.1 (API 16) 和更高版本上进行了测试,它运行良好且流畅。 (关于 API

【讨论】:

Math.log(1.55d) 是什么意思?以及所有的缩放计算(Math.log(currentSpan / lastSpan) / Math.log(1.55d))? 我用过这个 .. 但它在“双击保持滑动向上或向下”缩放时出现问题。 在 DispatchTouchEvent 中的 CustomMapFragment 中的第一行必须以其他方式处理极端情况(例如,如果播放服务版本低于使用的更新按钮,则没有此代码将无法工作)。 if(gestureDetector==null) return super.dispatchTouchEvent(ev); 双击放大还是有问题 你在名为 fragment_map 的布局中做了什么,你能把它放在这里吗【参考方案2】:

这是 MechEthan 所想的代码。

    首先,您必须检测覆盖视图上的双击。

    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);
        
    
    

    无论您在哪里抓取您的 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();
        
    
    

    在我的 Map_Fragment 内部(最终将位于支持导航抽屉和片段事务以切换视图的 Activity 中的 FrameLayout 内)

    mMapFragment = CustomMap_Fragment.newInstance();
    getChildFragmentManager().beginTransaction().replace(R.id.map_container, mMapFragment).commit();
    

    现在终于在我刚刚获得地图的同一个 Fragment 中,EventBus 接收器在收到“double_tapped_map”时将执行以下操作:

    @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);
    
    

注意:要完美地实现这一点,您可以禁用地图上的 zoomGestures(意味着您要执行 myMap.getUiSettings().setZoomGesturesEnabled(false);。如果您不这样做,您将能够非常双击在地图上快速,您会看到它会远离中心,因为双击的实现与我在发布的第一个答案中完全相同,即它们从以前的点击时间中减去当前时间,因此在该窗口中您可以滑动第三次点击,它不会触发事件总线事件,而谷歌地图会捕获它;因此禁用缩放手势。

但是,你会看到捏入/捏出不再起作用,你还必须处理捏,我也做过,但还需要 1 个小时,我还没有时间去做,但是100% 我会在这样做时更新答案。

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

【讨论】:

你能补充一些细节吗?我在我的活动中添加了地图。使用this,我想在其中添加上述功能。 我最近以更好的方式解决了这个问题,我将编辑我的答案以反映所有这些变化,但不幸的是我今天不能 好的。如果你能帮忙,那就太好了。我现在被困在这一点上:) 谢谢。 @Odaym。如果我会得到任何新的更新,我会将其与描述一起放入您的编辑中。 它现在可以工作了 :) 谢谢你的帮助,myMap.getUiSettings().setZoomGesturesEnabled(false);-tip,一个帮助了我【参考方案3】:

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

google-maps v2 API 没有任何明确的自定义缩放处理。尽管我确信您可以注入一些东西,但使用覆盖方法可以使您免受 google-maps 更改的影响,并让您在需要时更轻松地支持其他地图提供者。

(为了完整起见:您还可以处理相机后更改事件和重新居中,但这将是一个笨拙、糟糕的用户体验。)

【讨论】:

感谢您的回复。是的,这就是我认为我需要做的事情。我尝试了一些像你说的那样重新居中的hacky方法。你是完全正确的,超级笨拙。哈哈【参考方案4】:

我也有同样的要求。我必须了解事件在 android 中是如何处理的才能解决这个问题,因为我们必须拦截触摸事件以进行缩放并将滚动事件传递给地图。 为了实现这一点,我们需要一个自定义视图而不是谷歌地图视图。我们的自定义视图拦截了触摸事件,并通过不给底层地图处理的机会来决定是处理后续事件还是让底层地图自己处理。

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

    自定义片段

    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);
    
    

    自定义视图

    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_
    android:layout_
    tools:context=".MainActivity" />

【讨论】:

【参考方案5】:

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

请查看以下链接。

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

【讨论】:

这篇文章是关于以编程方式缩放而不是捏到缩放

以上是关于保持地图居中无论你在哪里捏缩放安卓的主要内容,如果未能解决你的问题,请参考以下文章

谷歌地图 - 缩放时保持中心

像 Snapseed 安卓应用一样的画布捏缩放

防止 UIWebview 上的捏缩放和双击缩放

使用 Android 内置的手势监听器和缩放监听器实现捏缩放和拖动

iOS UIScrollView捏缩放添加空白

带有捏缩放的 Android 图库