I need to detect when a MapView has been scrolled or zoomed, like the "moveend" event in the javascript API. I'd like to wait until the view has stopped moving, so I can then detect if I need to query my server for items withing the viewing rectangle, and if so send out a request. (actually I send a request for a slightly larger area than the viewing rectangle)
Obviously, I'd rather not send out a request for data if the view is still moving. But even worse is that I don't know that I need to send another request, leaving areas of the map missing markers.
Currently I am subclassing MapView and handling the onTouchEvent as follows:
public boolean onTouchEvent(android.view.Motion开发者_如何学PythonEvent ev) {
super.onTouchEvent (ev);
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint center = getMapCenter();
int latSpan = getLatitudeSpan(), lngSpan = getLongitudeSpan();
/* (check if it has moved enough to need a new set of data) */
}
return true;
}
Problem is, I don't know if the view has stopped, since scrolling tends to have inertia and can keep going past the "ACTION_UP" event.
Is there some event I can tap into that will alert me when a mapview is done moving (or zooming)? If not, has anyone written logic to detect this? In theory I could make a guess by looking at all the actions, and set something to come along bit later and check it...but...that seems messy and a PITA. But if someone has already written it.... :)
This is the method I am using at the moment, I have used this and tested it, works well.
Just make sure you make your draw()
method efficient. (Avoid GC in it).
//In map activity
class MyMapActivity extends MapActivity {
protected void onCreate(Bundle savedState){
setContent(R.layout.activity_map);
super.onCreate(savedSate);
OnMapMoveListener mapListener = new OnMapMoveListener(){
public void mapMovingFinishedEvent(){
Log.d("MapActivity", "Hey look! I stopped scrolling!");
}
}
// Create overlay
OnMoveOverlay mOnMoveOverlay = new OnMoveOverlay(mapListener);
// Add overlay to view.
MapView mapView = (MapView)findViewById(R.id.map_view);
// Make sure you add as the last overlay so its on the top.
// Otherwise other overlays could steal the touchEvent;
mapView.getOverlays().add(mOnMoveOverlay);
}
}
This is your OnMoveOverlay class
//OnMoveOverlay
class OnMoveOverlay extends Overlay
{
private static GeoPoint lastLatLon = new GeoPoint(0, 0);
private static GeoPoint currLatLon;
// Event listener to listen for map finished moving events
private OnMapMoveListener eventListener = null;
protected boolean isMapMoving = false;
public OnMoveOverlay(OnMapMoveListener eventLis){
//Set event listener
eventListener = eventLis;
}
@Override
public boolean onTouchEvent(android.view.MotionEvent ev)
{
super.onTouchEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_UP)
{
// Added to example to make more complete
isMapMoving = true;
}
//Fix: changed to false as it would handle the touch event and not pass back.
return false;
}
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow)
{
if (!shadow)
{
if (isMapMoving)
{
currLatLon = mapView.getProjection().fromPixels(0, 0);
if (currLatLon.equals(lastLatLon))
{
isMapMoving = false;
eventListener.mapMovingFinishedEvent();
}
else
{
lastLatLon = currLatLon;
}
}
}
}
public interface OnMapMoveListener{
public void mapMovingFinishedEvent();
}
}
Just implement your own listener eventListener.mapMovingFinishedEvent();
and fire the map moving bool by another method like above and your sorted.
The idea is when the map is moving the pixel projection to the coords will be changing, once they are the same, you have finished moving.
I have updated this with newer more complete code, there was an issue with it double drawing.
We don't do anything on the shadow pass as we would just double calculate per draw pass which is a waste.
Feel Free to ask any questions :)
Thanks, Chris
I had the same problem and "solved" it in a similar way, but I think less complicated: As overriding computeScroll() didn't work for me, I overrode onTouchEvent, too. Then I used a Handler, that invokes a method call after 50ms, if the map center changed, the same happens again, if the map center didn't change, the listener is called. The method I invoke in onTouchEvent looks like this:
private void refreshMapPosition() {
GeoPoint currentMapCenter = getMapCenter();
if (oldMapCenter==null || !oldMapCenter.equals(currentMapCenter)) {
oldMapCenter = currentMapCenter;
handler.postDelayed(new Runnable() {
@Override
public void run() {
refreshMapPosition();
}
}, 50);
}
else {
if (onScrollEndListener!=null)
onScrollEndListener.onScrollEnd(currentMapCenter);
}
}
But I'm waiting for a real solution for this, too ...
I don't really have a satisfactory solution to this problem, but I can tell what I did to partially solve it.
- I subclassed
MapView
and overrode thecomputeScroll()
method, which gets the current centre-point of the map and compares it with the last-known centre-point (stored as avolatile
field in the subclass). If the centre-point has changed, it fires an event to the listener of the map (I defined a custom listener interface for this). - The listener is an activity that instantiates a subclass of
AsyncTask
and executes it. This task pauses for 100ms in itsdoInBackGround()
method, before performing the server data fetch. - When the listener activity receives a second map-move event (which it will do because of the stepping effect of the map movement), it checks the status of the just-executed
AsyncTask
. If that task is still running, it willcancel()
it. It then creates a new task, and executes that.
The overall effect is that when the listeners get the flurry of map-moved events a few milliseconds apart, the only one that actually triggers the task to perform the server-fetch is the last one in the sequence. The downside is that it introduces a slight delay between the map movement happening, and the server fetch occurring.
I'm not happy with it, it's ugly, but it mitigates the problem. I would love to see a better solution to this.
I solved it using a thread and it seems to work quite good. It not only detects center changes but also zoom changes. Well, the detection is done after zooming and scrolling ends. If you need to detect zooming changes when you move up the first finger then you can modify my code a bit to detect different pointers. But I didn't need it, so didn't include it and left some homework for you :D
public class CustomMapView extends MapView {
private GeoPoint pressGP;
private GeoPoint lastGP;
private int pressZoom;
private int lastZoom;
public boolean onTouchEvent( MotionEvent event ) {
switch( event.getAction() ) {
case MotionEvent.ACTION_DOWN:
pressGP = getMapCenter();
pressZoom = getZoomLevel();
break;
case MotionEvent.ACTION_UP:
lastGP = getMapCenter();
pressZoom = getZoomLevel();
if( !pressGP.equals( lastGP ) ) {
Thread thread = new Thread() {
public void run() {
while( true ) {
try {
Thread.sleep( 100 );
} catch (InterruptedException e) {}
GeoPoint gp = getMapCenter();
int zl = getZoomLevel();
if( gp.equals( lastGP ) && zl == lastZoom)
break;
lastGP = gp;
lastZoom = zl;
}
onMapStop( lastGP );
}
};
thread.start();
}
break;
}
return super.onTouchEvent( event );
}
public void onMapStop( GeoPoint point , int zoom ){
// PUT YOUR CODE HERE
}
}
With the latest version of google maps API (V2) there is a listener to do this, i.e. GoogleMap.OnCameraChangeListener
.
mGoogleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener()
{
@Override
public void onCameraChange(CameraPosition cameraPosition)
{
Toast.makeText(mActivity, "Longitude : "+cameraPosition.target.longitude
+", Latitude : "+cameraPosition.target.latitude, Toast.LENGTH_SHORT).show();
}
});
精彩评论