Centered zooming a Google Map through a wrapper?

2019-08-18 18:03发布

问题:

How can i set a listener on an transparent ImageView that is above the map, to only react to pinching (zooming) events on it, while everything else like moving the map gets handled by the Google map?

My goal is to create centered zooming on the map which can be done like so:

 mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMap.getCameraPosition().target, amountOfZoomNeeded));

but i need to catch that pinch event first, and only that event since onTouch blocks using the map below.

EDIT:

I managed to create centered zooming using the ScaleGestureDetectors onScale() method:

public class PinchListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

    private GoogleMap googleMap;

    public PinchListener(GoogleMap googleMap){
        this.googleMap = googleMap;
    }


    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        double zoom = googleMap.getCameraPosition().zoom;
        zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(1.5d);
        CameraUpdate update =  CameraUpdateFactory.newLatLngZoom(googleMap.getCameraPosition().target, (float) zoom);

        googleMap.moveCamera(update);
        return true;
    }
}

And in my MainActivity i initialize it, where mMapWrapper is the transparent ImageView wrapper i mentioned earlier.

ScaleGestureDetector mScaleGestureDetector = new ScaleGestureDetector(this, new PinchListener(mGoogleMap));

mMapWrapper.setOnTouchListener(new View.OnTouchListener(){
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mScaleGestureDetector.onTouchEvent(event);
        return true;
    }
});

But now I'm facing a problem where I can't move the map since all touch events are getting eaten by the onTouch() method of the wrapper. How can i prevent this from happening?

回答1:

So this is how i managed to do this.

I created two classes for detecting certain events, like a fling, double tap, scroll(drag) and scale(zoom):

public class PinchListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

    private GoogleMap mGoogleMap;

    public PinchListener(GoogleMap googleMap) {
        this.mGoogleMap = googleMap;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        double zoom = mGoogleMap.getCameraPosition().zoom;
        zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(1.5d);
        CameraUpdate update = CameraUpdateFactory.newLatLngZoom(mGoogleMap.getCameraPosition().target, (float) zoom);
        mGoogleMap.moveCamera(update);
        return true;
    }
}

public class GestureListener extends GestureDetector.SimpleOnGestureListener {

    private GoogleMap mGoogleMap;
    private boolean mIsInAnimation;

    public GestureListener(GoogleMap googleMap) {
        this.mGoogleMap = googleMap;
        mIsInAnimation = false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        LatLng target = mGoogleMap.getCameraPosition().target;
        Point screenPoint = mGoogleMap.getProjection().toScreenLocation(target);
        Point newPoint = new Point(screenPoint.x + (int) distanceX, screenPoint.y + (int) distanceY);
        LatLng mapNewTarget = mGoogleMap.getProjection().fromScreenLocation(newPoint);

        CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
                mapNewTarget, mGoogleMap.getCameraPosition().zoom);
        mGoogleMap.moveCamera(update);
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        mGoogleMap.animateCamera(CameraUpdateFactory.zoomIn(), 400, null);
        return true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if (mIsInAnimation) return false;
        int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
        if (velocity < 500) return false;
        double k1 = 0.002d; /*exipemental*/
        double k2 = 0.002d;/*exipemental*/

        LatLng target = mGoogleMap.getCameraPosition().target;
        Point screenPoint = mGoogleMap.getProjection().toScreenLocation(target);

        Point newPoint = new Point(screenPoint.x - (int) (velocityX * k1),
                screenPoint.y - (int) (velocityY * k1));
        LatLng mapNewTarget = mGoogleMap.getProjection().fromScreenLocation(newPoint);

        CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
                mapNewTarget, mGoogleMap.getCameraPosition().zoom);

        tryUpdateCamera(update, (int) (velocity * k2));
        return true;
    }

    private void tryUpdateCamera(CameraUpdate update, int animateTime) {
        mIsInAnimation = true;
        mGoogleMap.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
            @Override
            public void onFinish() {
                mIsInAnimation = false;
            }

            @Override
            public void onCancel() {
                mIsInAnimation = false;
            }
        });
    }
}

I then initialized a GestureDetector and ScaleGestureDetector in my MainActivity:

ScaleGestureDetector mScaleGestureDetector = new ScaleGestureDetector(this, new PinchListener(mMap));
GestureDetector mGestureDetector = new GestureDetector(this, new GestureListener(mMap));

I had already created a transparent image wrapper in my activity_main to detect all these events and then simulate them on the Google Map below, like this:

<fragment 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"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:id="@+id/map"/>

<ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/mapWrapper"
/>

And then finally in my MainActivity i created an OnTouchListener for the wrapper:

mMapWrapper = findViewById(R.id.mapWrapper);

mMapWrapper.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    mMode = Mode.DRAG;
                    mGestureDetector.onTouchEvent(event);
                    mScaleGestureDetector.onTouchEvent(event);
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    mMode = Mode.ZOOM;
                    mScaleGestureDetector.onTouchEvent(event);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    mMode = Mode.NONE;
                    mGestureDetector.onTouchEvent(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mMode == Mode.DRAG) {
                        mGestureDetector.onTouchEvent(event);
                    } else if (mMode == Mode.ZOOM) {
                        mScaleGestureDetector.onTouchEvent(event);
                    }
                    break;
            }

            return true;
        }
    });

In the last step i "feed" the right detector the events that i want. If i had not done that, the zooming would not be completely centered since sometimes onScroll(Drag) events would be called and that would move the map.