Search code examples
androidgoogle-mapslocationzooming

Keep map centered regardless of where you pinch zoom on android


I'm looking to do something similar to the way Uber handles pinch zoom events. No matter where you pinch on the screen, it keeps the map centered and zooms in on the center location. Is there a way to do this without having some sort of overlay over the map fragment? Or should I just disable maps' events, create an overlay over the map fragment, and handle all zoom / other events from the overlay?


Solution

  • I've founded complete solution after spending about 3 days to search on google. My answer is edited from 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);
            }
        }
    }
    

    and customize 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();
            }
        }
    

    Finally, in your activity:

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

    I've already tested on Android 4.1 (API 16) and latter, it work fine and smooth. (About API < 16, I haven't any device to test).