2010-08-25 15 views
10

J'essaie d'écrire une application qui va charger dynamiquement des données à cartographier pendant que l'utilisateur effectue un panoramique ou un zoom.Comment attraper cette carte panoramique et zoom sont vraiment finis?

J'ai besoin de faire un suivi lorsque la carte est finie pour changer son état d'affichage (arrête le panoramique ou le zoom), puis charger une nouvelle partie de données pour créer des marqueurs. Mais en fait, l'API Google Maps n'a aucun événement à gérer. Il existe des méthodes telles que la création d'une superposition vide pour contrôler les événements onTouch, etc., mais le mappage de carte peut durer longtemps après que l'utilisateur a terminé son contact car GMaps utilise une certaine inertie pour rendre le panoramique plus fluide.

J'ai essayé de sous-classer MapView mais sa méthode draw() est final ainsi elle ne peut pas être substituée.

Des idées sur la façon de gérer avec précision la finition panoramique et zoom?

Répondre

12

Des heures de recherche et une décision ont été trouvées. Il a quelques inconvénients et avantages que je décrirai plus loin.

La principale chose que nous devrions faire est de remplacer certaines méthodes de MapView pour gérer son comportement de dessin. Dans le cas où nous ne pouvons pas surcharger la méthode draw(), nous devrions trouver une autre méthode. Il existe une autre dérivée de View qui peut être surchargée - la méthode computeScroll(). Il est appelé à plusieurs reprises alors que la carte continue de remplir. Tout ce que nous avons à faire est de définir un seuil de temps à attraper si computeScroll n'est plus appelé cette fois.
Voici ce que je faisais:

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

} 

Ensuite, vous devez vous inscrire gestionnaires d'événements dans votre MapActivity comme ceci:

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

Maintenant que sur les avantages et les inconvénients de cette approche?
Avantages:
- Les poignées d'événements dans l'une ou l'autre des directions ont été panoramisées ou agrandies. L'événement tactile, les clés matérielles utilisées, même les événements déclenchés par programmation sont gérés (comme les méthodes setZoom() ou animate()).
- Possibilité d'ignorer le chargement inutile de données si l'utilisateur clique plusieurs fois sur le bouton de zoom rapidement. L'événement se déclenchera seulement après que les clics s'arrêteront.
Inconvénients:
- Il est tout à fait impossible d'annuler le zoom ou une action panoramique (peut-être je vais ajouter cette capacité à l'avenir)

espère que cette petite classe vous aidera.

+0

Un autre petit problème que j'ai remarqué lors de l'utilisation de cette solution, est que le fait de changer le drawable pour un de mes 'OverlayItem's ou de changer la rotation, computeScroll() serait également déclenché, provoquant le' onPanChangeListener'. – rogerkk

+0

Tous les constructeurs de classe n'initialisent pas les internes _this, last_center_pos, last_zoom. Autant que je sache, ils devraient. – Theo

+0

(! Last_center_pos.equals (getMapCenter()) ||! Is_touched) doit être (! Last_center_pos.equals (getMapCenter()) &&! Is_touched) sinon l'écouteur pan se déclenche en continu lorsque l'utilisateur ne touche pas l'écran. Vous l'aviez bien dans le commentaire au-dessus de cette ligne où vous avez décrit les conditions en utilisant 'et'. – Theo

0

Le projet MapChange, initialement affiché sur un similar question here, m'a aidé à remplir la même tâche que vous avez demandée.

+0

Comme je le vois ils utilisent ma technique. Peut-être qu'ils ont même été inspirés par ma réponse ci-dessus :) De toute façon j'ai apprécié que quelqu'un l'ait fait comme une bibliothèque séparée. –