如何判断地图的平移和缩放已经完成?

10

我正在尝试编写一个应用程序,在用户平移或缩放地图时动态加载数据到地图上。

我需要跟踪地图何时完成平移或缩放以更改其视图状态(停止平移或缩放),然后加载新的数据部分以创建标记。但实际上,Google Maps API 没有任何事件来处理这个问题。

有一些方法,如创建一个空覆盖层来控制 onTouch 事件等,但是地图平移可能会持续很长时间,即使用户已经松开手指,因为 GMaps 使用某种惯性使平移更加平滑。

我尝试子类化 MapView,但它的 draw() 方法是 final,因此无法重写。

有什么办法可以精确地处理平移和缩放的结束吗?

3个回答

12

经过数小时的研究,我找到了一些决定方案。它有一些优点和缺点,接下来我会进一步描述。

我们需要做的主要事情是覆盖一些MapView的方法来处理其绘制行为。如果我们无法覆盖draw()方法,我们应该寻找另一种方法。还有另一种从View派生出来的方法可以被重写 - computeScroll()方法。当地图继续填充时,它会被重复调用。我们所要做的就是设置一些时间阈值,以捕获是否再次调用computeScroll这个方法。
这是我的做法:

import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;

public class EnhancedMapView extends MapView {
public interface OnZoomChangeListener {
    public void onZoomChange(MapView view, int newZoom, int oldZoom);
}

public interface OnPanChangeListener {
    public void onPanChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter);
}

private EnhancedMapView _this;

    // Set this variable to your preferred timeout
private long events_timeout = 500L;
private boolean is_touched = false;
private GeoPoint last_center_pos;
private int last_zoom;
private Timer zoom_event_delay_timer = new Timer();
private Timer pan_event_delay_timer = new Timer();

private EnhancedMapView.OnZoomChangeListener zoom_change_listener;
private EnhancedMapView.OnPanChangeListener pan_change_listener;


public EnhancedMapView(Context context, String apiKey) {
    super(context, apiKey);
    _this = this;
    last_center_pos = this.getMapCenter();
    last_zoom = this.getZoomLevel();
}

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

public EnhancedMapView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public void setOnZoomChangeListener(EnhancedMapView.OnZoomChangeListener l) {
    zoom_change_listener = l;
}

public void setOnPanChangeListener(EnhancedMapView.OnPanChangeListener l) {
    pan_change_listener = l;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (ev.getAction() == 1) {
        is_touched = false;
    } else {
        is_touched = true;
    }

    return super.onTouchEvent(ev);
}

@Override
public void computeScroll() {
    super.computeScroll();

    if (getZoomLevel() != last_zoom) {
                    // if computeScroll called before timer counts down we should drop it and start it over again
        zoom_event_delay_timer.cancel();
        zoom_event_delay_timer = new Timer();
        zoom_event_delay_timer.schedule(new TimerTask() {
            @Override
            public void run() {
                zoom_change_listener.onZoomChange(_this, getZoomLevel(), last_zoom);
                last_zoom = getZoomLevel();
            }
        }, events_timeout);
    }

    // Send event only when map's center has changed and user stopped touching the screen
    if (!last_center_pos.equals(getMapCenter()) || !is_touched) {
        pan_event_delay_timer.cancel();
        pan_event_delay_timer = new Timer();
        pan_event_delay_timer.schedule(new TimerTask() {
            @Override
            public void run() {
                pan_change_listener.onPanChange(_this, getMapCenter(), last_center_pos);
                last_center_pos = getMapCenter();
            }
        }, events_timeout);
    }
}

}

那么你应该像这样在MapActivity中注册事件处理程序:

public class YourMapActivity extends MapActivity {

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

    mv = new EnhancedMapView(this, "<your Maps API key here>");

    mv.setClickable(true);
    mv.setBuiltInZoomControls(true);

    mv.setOnZoomChangeListener(new EnhancedMapView.OnZoomChangeListener() {
        @Override
        public void onZoomChange(MapView view, int newZoom, int oldZoom) {
            Log.d("test", "zoom changed from " + oldZoom + " to " + newZoom);
        }
    }
    mv.setOnPanChangeListener(new EnhancedMapView.OnPanChangeListener() {
        public void onPanChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter) {
            Log.d("test", "center changed from " + oldCenter.getLatitudeE6() + "," + oldCenter.getLongitudeE6() + " to " + newCenter.getLatitudeE6() + "," + newCenter.getLongitudeE6());
        }
    }
}

现在来谈一下这种方法的优缺点:
优点:
- 无论是地图平移还是缩放,都可以处理事件。触摸事件、硬件按键使用,甚至程序上的事件也可以处理(如setZoom()或animate()方法)。
- 如果用户快速点击缩放按钮,可以跳过不必要的数据加载。事件仅在点击停止后触发。
缺点:
- 比较难取消缩放或平移操作(也许我将来会添加这个功能)。

希望这个小类对您有所帮助。


我在使用这个解决方案时注意到的另一个小问题是,当更改我的OverlayItem之一的可绘制对象或更改旋转时,computeScroll()也会被触发,从而使onPanChangeListener被激活。 - rogerkk
并不是你的所有类构造函数都初始化了内部变量_this、last_center_pos和last_zoom。据我所知,它们应该被初始化。 - Theo
应该将(!last_center_pos.equals(getMapCenter()) || !is_touched)改为(!last_center_pos.equals(getMapCenter()) && !is_touched),否则当用户不触摸屏幕时,平移监听器会持续触发。在上面那行代码的注释中,您已经正确地使用了“and”来描述条件。 - Theo
我创建了一个小的MapView类,其中包含三个事件:onClick,onPan和onZoom 这里 - mpcabd

0

现在可以使用:

googleMap.setOnCameraIdleListener {
                //Do your thing
            }

相机移动结束时的回调接口

0

我看到他们使用了我的技巧。也许他们甚至受到了我上面展示的答案的启发 :) 无论如何,我很感激有人将其制作成一个独立的库。 - Dmitriy Rybakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接