如何捕捉地图平移和缩放真正完成的情况?

发布于 2024-09-15 19:27:15 字数 357 浏览 11 评论 0原文

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

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

有一些方法,例如创建一个空的覆盖层来控制 onTouch 事件等,但地图平移可能会在用户完成触摸后持续很长时间,因为 GMap 使用某种惯性来使平移更加平滑。

我尝试子类化MapView,但它的draw()方法是final,因此它不能被覆盖。

有什么想法可以精确处理平移和缩放整理吗?

I am trying to write an application that will dynamically load data to map while user pans or zooms it.

I need to track when the map is finished to change its view state (stops panning or zooming) and then load a new portion of data for creating markers. But in fact Google Maps API doesn't have any events to handle this.

There are some methods like creating an empty overlay to control onTouch events and so on, but map panning could last long after user finished his touch because GMaps use some kind of inertia to make the pan smoother.

I tried to subclass MapView but its draw() method is final thus it cannot be overridden.

Any ideas how to make precise handling of pan and zoom finishing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

梦中的蝴蝶 2024-09-22 19:27:15

经过数小时的研究并做出了一些决定。它有一些缺点和优点,我将进一步描述。

我们应该做的主要事情是重写一些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() 方法)。
- 如果用户快速单击缩放按钮多次,则能够跳过不必要的数据加载。仅当点击停止后才会触发事件。
缺点:
- 取消缩放或平移操作是完全不可能的(也许我将来会添加此功能)

希望这个小课程能够帮助您。

Hours of researching and some decision was found. It has some cons and pros which I'll describe further.

The main thing we should do is to override some MapView's methods to handle its drawing behavior. In case we cannot override draw() method we should find another way in. There is another one derivative from View which may be overridden - computeScroll() method. It is called repeatedly as map continues padding. All we have to do is to set some time threshold to catch if computeScroll is not called anymore this time.
Here is what I did:

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

}

Then you should register event handlers in your MapActivity like this:

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

Now what about advantages and disadvantages of this approach?
Advantages:
- Events handles either way map was panned or zoomed. Touch event, hardware keys used, even programmatically fired events are handled (like setZoom() or animate() methods).
- Ability to skip unnecessary loading of data if user clicks zoom button several times quickly. Event will fire only after clicks will stop.
Disadvantages:
- It is quite not possible to cancel zooming or panning action (maybe I'll add this ability in the future)

Hope this little class will help you.

偏闹i 2024-09-22 19:27:15

MapChange 项目,最初发布在 这里有类似的问题,帮助我完成您要求的相同任务。

The MapChange project, originally posted on a similar question here, helped me to fullfil the same task you asked for.

眼眸里的快感 2024-09-22 19:27:15

现在可以使用:

googleMap.setOnCameraIdleListener {
                //Do your thing
            }

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

Now this can be used:

googleMap.setOnCameraIdleListener {
                //Do your thing
            }

Callback interface for when camera movement has ended

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文