Google Maps Android API v2 - Interactive InfoWindow (comme dans les google maps Android d'origine)


191

J'essaie de personnaliser InfoWindowaprès un clic sur un marqueur avec la nouvelle API Google Maps v2. Je veux que cela ressemble à l'application de cartes d'origine de Google. Comme ça:

Exemple d'image

Quand j'ai à l' ImageButtonintérieur, cela ne fonctionne pas - l'ensemble InfoWindowest sélectionné et pas seulement le ImageButton. J'ai lu que c'est parce qu'il n'y a pas de Viewlui - même mais que c'est un instantané, de sorte que les éléments individuels ne peuvent pas être distingués les uns des autres.

EDIT: Dans la documentation (grâce à Disco S2 ):

Comme mentionné dans la section précédente sur les fenêtres d'informations, une fenêtre d'informations n'est pas une vue en direct, mais la vue est rendue sous forme d'image sur la carte. Par conséquent, tous les écouteurs que vous définissez dans la vue sont ignorés et vous ne pouvez pas faire la distinction entre les événements de clic sur différentes parties de la vue. Il est conseillé de ne pas placer de composants interactifs - tels que des boutons, des cases à cocher ou des entrées de texte - dans votre fenêtre d'informations personnalisée.

Mais si Google l'utilise, il doit y avoir un moyen de le faire. Est-ce que quelqu'un a une idée?


«ça ne marche pas» n'est pas une description particulièrement utile de vos symptômes. Voici un exemple de projet qui montre comportant une fenêtre d'information personnalisée avec une image: github.com/commonsguy/cw-omnibus/tree/master/MapsV2/Popups
CommonsWare

8
@CommonsWare J'en ai écrit la raison. À partir de la documentation originale Remarque: la fenêtre d'informations qui est dessinée n'est pas une vue en direct. La vue est rendue sous forme d'image (en utilisant View.draw(Canvas)) ... Cela signifie que les modifications ultérieures de la vue ne seront pas reflétées par la fenêtre d'informations sur la carte. Pour mettre à jour la fenêtre d'informations ultérieurement En outre, la fenêtre d'informations ne respectera aucune des interactivités typiques d'une vue normale comme les événements tactiles ou gestuels. Cependant, vous pouvez écouter un événement de clic générique sur toute la fenêtre d'informations comme décrit dans la section ci-dessous. ... dans votre exemple n'est que textview
user1943012

Bonjour, comment avez-vous fait apparaître le bouton ma position sous la barre d'action en mode plein écran? Merci!
tkblackbelt

45
Je ne pense pas que cette question doive être close. C'est une question légitime à un problème auquel je suis également confronté.
marienke

12
Je peux voir cela si souvent, la plupart des questions les plus utiles avec réponse ont été fermées. Une question peut ne pas toujours répondre aux exigences exactes d'une question appropriée, mais si nous avons un PROBLÈME bien expliqué et une RÉPONSE bien expliquée, il n'y a aucune raison de fermer la question. Les gens utilisent Stackoverflow pour échanger des connaissances et fermer un fil de questions qui est si activement et si productif que "hors sujet" ne me semble pas une bonne idée ..
John

Réponses:


336

Je cherchais moi-même une solution à ce problème sans succès, j'ai donc dû lancer la mienne que j'aimerais partager ici avec vous. (Veuillez excuser mon mauvais anglais) (C'est un peu fou de répondre à un autre tchèque en anglais :-))

La première chose que j'ai essayé a été d'utiliser un bon vieux PopupWindow. C'est assez simple - il suffit d'écouter OnMarkerClickListeneret d'afficher une personnalisation PopupWindowau-dessus du marqueur. Certains autres gars ici sur StackOverflow ont suggéré cette solution et cela semble plutôt bon à première vue. Mais le problème avec cette solution apparaît lorsque vous commencez à déplacer la carte. Vous devez déplacer PopupWindowvous-même d'une manière ou d'une autre, ce qui est possible (en écoutant certains événements onTouch), mais à mon humble avis, vous ne pouvez pas le faire paraître assez bien, en particulier sur certains appareils lents. Si vous le faites de la manière simple, il "saute" d'un endroit à un autre. Vous pouvez également utiliser des animations pour peaufiner ces sauts, mais de cette façon, il y PopupWindowaura toujours "un pas en arrière" là où il devrait être sur la carte, ce que je n'aime tout simplement pas.

À ce stade, je réfléchissais à une autre solution. J'ai réalisé que je n'avais pas vraiment besoin de beaucoup de liberté - pour montrer mes vues personnalisées avec toutes les possibilités qui vont avec (comme des barres de progression animées, etc.). Je pense qu'il y a une bonne raison pour laquelle même les ingénieurs Google ne le font pas de cette façon dans l'application Google Maps. Tout ce dont j'ai besoin est un bouton ou deux sur l'InfoWindow qui affichera un état enfoncé et déclenchera certaines actions lorsque vous cliquez dessus. J'ai donc proposé une autre solution qui se divise en deux parties:

Première partie:
La première partie est de pouvoir attraper les clics sur les boutons pour déclencher une action. Mon idée est la suivante:

  1. Conservez une référence à l'infoWindow personnalisée créée dans InfoWindowAdapter.
  2. Enveloppez le MapFragment(ou MapView) dans un ViewGroup personnalisé (le mien s'appelle MapWrapperLayout)
  3. Remplacez le MapWrapperLayoutdispatchTouchEvent de et (si l'InfoWindow est actuellement affiché) acheminez d'abord les MotionEvents vers l'InfoWindow créée précédemment. S'il ne consomme pas les MotionEvents (par exemple, parce que vous n'avez cliqué sur aucune zone cliquable dans InfoWindow, etc.) alors (et seulement alors) laissez les événements descendre à la superclasse de MapWrapperLayout afin qu'il soit finalement livré à la carte.

Voici le code source de MapWrapperLayout:

package com.circlegate.tt.cg.an.lib.map;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;

import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

public class MapWrapperLayout extends RelativeLayout {
    /**
     * Reference to a GoogleMap object 
     */
    private GoogleMap map;

    /**
     * Vertical offset in pixels between the bottom edge of our InfoWindow 
     * and the marker position (by default it's bottom edge too).
     * It's a good idea to use custom markers and also the InfoWindow frame, 
     * because we probably can't rely on the sizes of the default marker and frame. 
     */
    private int bottomOffsetPixels;

    /**
     * A currently selected marker 
     */
    private Marker marker;

    /**
     * Our custom view which is returned from either the InfoWindowAdapter.getInfoContents 
     * or InfoWindowAdapter.getInfoWindow
     */
    private View infoWindow;    

    public MapWrapperLayout(Context context) {
        super(context);
    }

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

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

    /**
     * Must be called before we can route the touch events
     */
    public void init(GoogleMap map, int bottomOffsetPixels) {
        this.map = map;
        this.bottomOffsetPixels = bottomOffsetPixels;
    }

    /**
     * Best to be called from either the InfoWindowAdapter.getInfoContents 
     * or InfoWindowAdapter.getInfoWindow. 
     */
    public void setMarkerWithInfoWindow(Marker marker, View infoWindow) {
        this.marker = marker;
        this.infoWindow = infoWindow;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean ret = false;
        // Make sure that the infoWindow is shown and we have all the needed references
        if (marker != null && marker.isInfoWindowShown() && map != null && infoWindow != null) {
            // Get a marker position on the screen
            Point point = map.getProjection().toScreenLocation(marker.getPosition());

            // Make a copy of the MotionEvent and adjust it's location
            // so it is relative to the infoWindow left top corner
            MotionEvent copyEv = MotionEvent.obtain(ev);
            copyEv.offsetLocation(
                -point.x + (infoWindow.getWidth() / 2), 
                -point.y + infoWindow.getHeight() + bottomOffsetPixels);

            // Dispatch the adjusted MotionEvent to the infoWindow
            ret = infoWindow.dispatchTouchEvent(copyEv);
        }
        // If the infoWindow consumed the touch event, then just return true.
        // Otherwise pass this event to the super class and return it's result
        return ret || super.dispatchTouchEvent(ev);
    }
}

Tout cela rendra les vues à l'intérieur de l'InfoView "live" à nouveau - les OnClickListeners commenceront à se déclencher, etc.

Deuxième partie: le problème restant est que, de toute évidence, vous ne pouvez pas voir les modifications de l'interface utilisateur de votre InfoWindow à l'écran. Pour ce faire, vous devez appeler manuellement Marker.showInfoWindow. Maintenant, si vous effectuez un changement permanent dans votre InfoWindow (comme changer le libellé de votre bouton pour quelque chose d'autre), cela suffit.

Mais montrer l'état d'un bouton enfoncé ou quelque chose de cette nature est plus compliqué. Le premier problème est que (au moins) je n'ai pas pu faire en sorte que InfoWindow affiche l'état normal du bouton enfoncé. Même si j'ai appuyé sur le bouton pendant un long moment, il est juste resté non appuyé sur l'écran. Je pense que c'est quelque chose qui est géré par le cadre de la carte lui-même, ce qui garantit probablement de ne pas afficher d'état transitoire dans les fenêtres d'informations. Mais je peux me tromper, je n'ai pas essayé de le découvrir.

Ce que j'ai fait est un autre hack désagréable - j'ai attaché un OnTouchListenerau bouton et changé manuellement son arrière-plan lorsque le bouton a été enfoncé ou relâché sur deux dessinables personnalisés - l'un avec un bouton dans un état normal et l'autre dans un état enfoncé. Ce n'est pas très agréable, mais ça marche :). Maintenant, j'ai pu voir le bouton basculer entre les états normal et pressé à l'écran.

Il y a encore un dernier problème - si vous cliquez sur le bouton trop rapidement, il ne montre pas l'état enfoncé - il reste juste dans son état normal (bien que le clic lui-même soit déclenché donc le bouton "fonctionne"). Au moins, c'est ainsi que cela apparaît sur mon Galaxy Nexus. Donc, la dernière chose que j'ai faite, c'est que j'ai retardé un peu le bouton dans son état enfoncé. C'est aussi assez moche et je ne sais pas comment cela fonctionnerait sur certains appareils plus anciens et lents, mais je soupçonne que même le cadre de la carte lui-même fait quelque chose comme ça. Vous pouvez l'essayer vous-même - lorsque vous cliquez sur toute l'InfoWindow, il reste dans un état enfoncé un peu plus longtemps, puis les boutons normaux le font (encore une fois - au moins sur mon téléphone). Et c'est en fait ainsi que cela fonctionne même sur l'application originale Google Maps.

Quoi qu'il en soit, je me suis écrit une classe personnalisée qui gère les changements d'état des boutons et toutes les autres choses que j'ai mentionnées, voici donc le code:

package com.circlegate.tt.cg.an.lib.map;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import com.google.android.gms.maps.model.Marker;

public abstract class OnInfoWindowElemTouchListener implements OnTouchListener {
    private final View view;
    private final Drawable bgDrawableNormal;
    private final Drawable bgDrawablePressed;
    private final Handler handler = new Handler();

    private Marker marker;
    private boolean pressed = false;

    public OnInfoWindowElemTouchListener(View view, Drawable bgDrawableNormal, Drawable bgDrawablePressed) {
        this.view = view;
        this.bgDrawableNormal = bgDrawableNormal;
        this.bgDrawablePressed = bgDrawablePressed;
    }

    public void setMarker(Marker marker) {
        this.marker = marker;
    }

    @Override
    public boolean onTouch(View vv, MotionEvent event) {
        if (0 <= event.getX() && event.getX() <= view.getWidth() &&
            0 <= event.getY() && event.getY() <= view.getHeight())
        {
            switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: startPress(); break;

            // We need to delay releasing of the view a little so it shows the pressed state on the screen
            case MotionEvent.ACTION_UP: handler.postDelayed(confirmClickRunnable, 150); break;

            case MotionEvent.ACTION_CANCEL: endPress(); break;
            default: break;
            }
        }
        else {
            // If the touch goes outside of the view's area
            // (like when moving finger out of the pressed button)
            // just release the press
            endPress();
        }
        return false;
    }

    private void startPress() {
        if (!pressed) {
            pressed = true;
            handler.removeCallbacks(confirmClickRunnable);
            view.setBackground(bgDrawablePressed);
            if (marker != null) 
                marker.showInfoWindow();
        }
    }

    private boolean endPress() {
        if (pressed) {
            this.pressed = false;
            handler.removeCallbacks(confirmClickRunnable);
            view.setBackground(bgDrawableNormal);
            if (marker != null) 
                marker.showInfoWindow();
            return true;
        }
        else
            return false;
    }

    private final Runnable confirmClickRunnable = new Runnable() {
        public void run() {
            if (endPress()) {
                onClickConfirmed(view, marker);
            }
        }
    };

    /**
     * This is called after a successful click 
     */
    protected abstract void onClickConfirmed(View v, Marker marker);
}

Voici un fichier de mise en page InfoWindow personnalisé que j'ai utilisé:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginRight="10dp" >

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Title" />

        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="snippet" />

    </LinearLayout>

    <Button
        android:id="@+id/button" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

Fichier de mise en page de l'activité de test (se MapFragmenttrouvant à l'intérieur du MapWrapperLayout):

<com.circlegate.tt.cg.an.lib.map.MapWrapperLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map_relative_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.MapFragment" />

</com.circlegate.tt.cg.an.lib.map.MapWrapperLayout>

Et enfin le code source d'une activité de test, qui rassemble tout cela:

package com.circlegate.testapp;

import com.circlegate.tt.cg.an.lib.map.MapWrapperLayout;
import com.circlegate.tt.cg.an.lib.map.OnInfoWindowElemTouchListener;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {    
    private ViewGroup infoWindow;
    private TextView infoTitle;
    private TextView infoSnippet;
    private Button infoButton;
    private OnInfoWindowElemTouchListener infoButtonListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final MapFragment mapFragment = (MapFragment)getFragmentManager().findFragmentById(R.id.map);
        final MapWrapperLayout mapWrapperLayout = (MapWrapperLayout)findViewById(R.id.map_relative_layout);
        final GoogleMap map = mapFragment.getMap();

        // MapWrapperLayout initialization
        // 39 - default marker height
        // 20 - offset between the default InfoWindow bottom edge and it's content bottom edge 
        mapWrapperLayout.init(map, getPixelsFromDp(this, 39 + 20)); 

        // We want to reuse the info window for all the markers, 
        // so let's create only one class member instance
        this.infoWindow = (ViewGroup)getLayoutInflater().inflate(R.layout.info_window, null);
        this.infoTitle = (TextView)infoWindow.findViewById(R.id.title);
        this.infoSnippet = (TextView)infoWindow.findViewById(R.id.snippet);
        this.infoButton = (Button)infoWindow.findViewById(R.id.button);

        // Setting custom OnTouchListener which deals with the pressed state
        // so it shows up 
        this.infoButtonListener = new OnInfoWindowElemTouchListener(infoButton,
                getResources().getDrawable(R.drawable.btn_default_normal_holo_light),
                getResources().getDrawable(R.drawable.btn_default_pressed_holo_light)) 
        {
            @Override
            protected void onClickConfirmed(View v, Marker marker) {
                // Here we can perform some action triggered after clicking the button
                Toast.makeText(MainActivity.this, marker.getTitle() + "'s button clicked!", Toast.LENGTH_SHORT).show();
            }
        }; 
        this.infoButton.setOnTouchListener(infoButtonListener);


        map.setInfoWindowAdapter(new InfoWindowAdapter() {
            @Override
            public View getInfoWindow(Marker marker) {
                return null;
            }

            @Override
            public View getInfoContents(Marker marker) {
                // Setting up the infoWindow with current's marker info
                infoTitle.setText(marker.getTitle());
                infoSnippet.setText(marker.getSnippet());
                infoButtonListener.setMarker(marker);

                // We must call this to set the current marker and infoWindow references
                // to the MapWrapperLayout
                mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow);
                return infoWindow;
            }
        });

        // Let's add a couple of markers
        map.addMarker(new MarkerOptions()
            .title("Prague")
            .snippet("Czech Republic")
            .position(new LatLng(50.08, 14.43)));

        map.addMarker(new MarkerOptions()
            .title("Paris")
            .snippet("France")
            .position(new LatLng(48.86,2.33)));

        map.addMarker(new MarkerOptions()
            .title("London")
            .snippet("United Kingdom")
            .position(new LatLng(51.51,-0.1)));
    }

    public static int getPixelsFromDp(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5f);
    }
}

C'est ça. Jusqu'à présent, je n'ai testé cela que sur mon Galaxy Nexus (4.2.1) et Nexus 7 (également 4.2.1), je vais l'essayer sur un téléphone Gingerbread lorsque j'en aurai l'occasion. Une limitation que j'ai trouvée jusqu'à présent est que vous ne pouvez pas faire glisser la carte d'où se trouve votre bouton à l'écran et déplacer la carte. Cela pourrait probablement être surmonté d'une manière ou d'une autre, mais pour l'instant, je peux vivre avec ça.

Je sais que c'est un truc moche mais je n'ai rien trouvé de mieux et j'ai tellement besoin de ce modèle de conception que ce serait vraiment une raison de revenir au framework map v1 (ce que j'aimerais vraiment éviter pour une nouvelle application avec des fragments, etc.). Je ne comprends tout simplement pas pourquoi Google n'offre pas aux développeurs un moyen officiel d'avoir un bouton sur InfoWindows. C'est un modèle de conception si courant, de plus, ce modèle est utilisé même dans l'application officielle Google Maps :). Je comprends les raisons pour lesquelles ils ne peuvent pas simplement faire "vivre" vos vues dans InfoWindows - cela tuerait probablement les performances lors du déplacement et du défilement de la carte. Mais il devrait y avoir un moyen d'obtenir cet effet sans utiliser de vues.


6
solution intéressante. Je veux essayer ça. Comment était la performance?
Patrick

10
besoin de remplacer view.setBackground(bgDrawablePressed);par if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { view.setBackgroundDrawable(bgDrawablePressed); }else{ view.setBackground(bgDrawablePressed); }pour fonctionner dans Android Gingerbread, Remarque: il existe une autre occurrence de view.setBackgrounddans le code.
Balaji

4
KK_07k11A0585: Il semble que votre implémentation InfoWindowAdapter soit probablement erronée. Il existe deux méthodes à remplacer: getInfoWindow () et getInfoContents (). Le framework appelle d'abord la méthode getInfoWindow () et (uniquement) s'il retourne null, il appelle la méthode getInfoContents (). Si vous renvoyez une vue de getInfoWindow, le framework la placera dans le cadre de la fenêtre d'informations par défaut - ce que vous ne voulez pas dans votre cas. Donc, renvoyez simplement null de getInfoWindow et renvoyez-vous View de getInfoContent et cela devrait être bien alors.
choisi007

18
Si quelqu'un est intéressé, j'utilise cette solution dans ma propre application - CG Transit (vous pouvez la trouver sur Google Play). Après deux mois, il a été utilisé par plusieurs dizaines de milliers d'utilisateurs et personne ne s'en est encore plaint.
choisi007

2
comment déterminez-vous le décalage inférieur si vous n'utilisez pas une fenêtre infowind standard?
Rarw

12

Je vois que cette question est déjà ancienne mais quand même ...

Nous avons créé une bibliothèque sipmle dans notre entreprise pour réaliser ce qui est souhaité - Une fenêtre d'information interactive avec des vues et tout. Vous pouvez le vérifier sur github .

J'espère que cela aide :)


Est-il possible de l'utiliser avec des clusters?
gcolucci

9

Voici mon point de vue sur le problème. Je crée une AbsoluteLayoutsuperposition qui contient une fenêtre d'informations (une vue régulière avec toutes les capacités d'interactivité et de dessin). Ensuite, je commence Handlerce qui synchronise la position de la fenêtre d'information avec la position du point sur la carte toutes les 16 ms. Cela semble fou, mais fonctionne réellement.

Vidéo de démonstration: https://www.youtube.com/watch?v=bT9RpH4p9mU (tenez compte du fait que les performances sont diminuées en raison de l'émulateur et de l'enregistrement vidéo exécutés simultanément).

Code de la démo: https://github.com/deville/info-window-demo

Un article fournissant des détails (en russe): http://habrahabr.ru/post/213415/


Cela ralentira / retardera le défilement de la carte
Shane Ekanayake

2

Pour ceux qui n'ont pas pu obtenir de choose007'sréponse opérationnelle

Si clickListenerne fonctionne pas correctement à tout moment dans la chose007'ssolution, essayez de mettre en œuvre à la View.onTouchListenerplace de clickListener. Gérez l'événement tactile en utilisant l'une des actions ACTION_UPou ACTION_DOWN. Pour une raison quelconque, les cartes infoWindowprovoquent un comportement étrange lors de la distribution vers clickListeners.

infoWindow.findViewById(R.id.my_view).setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
          int action = MotionEventCompat.getActionMasked(event);
          switch (action){
                case MotionEvent.ACTION_UP:
                    Log.d(TAG,"a view in info window clicked" );
                    break;
                }
                return true;
          }

Edit: Voici comment je l'ai fait étape par étape

Commencez par gonfler votre propre infowindow (variable globale) quelque part dans votre activité / fragment. Le mien est dans le fragment. Assurez-vous également que la vue racine dans votre disposition d'infowindow est linéaire (pour une raison quelconque, la disposition relative prenait toute la largeur de l'écran dans l'infowindow)

infoWindow = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.info_window, null);
/* Other global variables used in below code*/
private HashMap<Marker,YourData> mMarkerYourDataHashMap = new HashMap<>();
private GoogleMap mMap;
private MapWrapperLayout mapWrapperLayout;

Ensuite, dans le rappel onMapReady de l'api android de google maps (suivez ceci si vous ne savez pas ce qu'est onMapReady Maps> Documentation - Mise en route )

   @Override
    public void onMapReady(GoogleMap googleMap) {
       /*mMap is global GoogleMap variable in activity/fragment*/
        mMap = googleMap;
       /*Some function to set map UI settings*/ 
        setYourMapSettings();

MapWrapperLayoutinitialisation http://stackoverflow.com/questions/14123243/google-maps-android-api-v2- interactive-infowindow-like-in-original-android-go / 15040761 # 15040761 39 - hauteur du marqueur par défaut 20 - décalage entre les bord inférieur par défaut d'InfoWindow et bord inférieur de son contenu * /

        mapWrapperLayout.init(mMap, Utils.getPixelsFromDp(mContext, 39 + 20));

        /*handle marker clicks separately - not necessary*/
       mMap.setOnMarkerClickListener(this);

       mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
                @Override
                public View getInfoWindow(Marker marker) {
                    return null;
                }

            @Override
            public View getInfoContents(Marker marker) {
                YourData data = mMarkerYourDataHashMap.get(marker);
                setInfoWindow(marker,data);
                mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow);
                return infoWindow;
            }
        });
    }

Méthode SetInfoWindow

private void setInfoWindow (final Marker marker, YourData data)
            throws NullPointerException{
        if (data.getVehicleNumber()!=null) {
            ((TextView) infoWindow.findViewById(R.id.VehicelNo))
                    .setText(data.getDeviceId().toString());
        }
        if (data.getSpeed()!=null) {
            ((TextView) infoWindow.findViewById(R.id.txtSpeed))
                    .setText(data.getSpeed());
        }

        //handle dispatched touch event for view click
        infoWindow.findViewById(R.id.any_view).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = MotionEventCompat.getActionMasked(event);
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        Log.d(TAG,"any_view clicked" );
                        break;
                }
                return true;
            }
        });

Manipulez le marqueur en cliquant séparément

    @Override
    public boolean onMarkerClick(Marker marker) {
        Log.d(TAG,"on Marker Click called");
        marker.showInfoWindow();
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(marker.getPosition())      // Sets the center of the map to Mountain View
                .zoom(10)
                .build();
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition),1000,null);
        return true;
    }

pouvez-vous me montrer votre code? J'ai pu enregistrer les événements tactiles avec succès mais pas les événements de clic, alors j'ai opté pour cette implémentation. En outre, vous devez implémenter correctement la solution de choose007.
Manish Jangid

J'ai partagé ma mise en œuvre complète. Faites-moi savoir si vous avez des questions
Manish Jangid

0

Juste une spéculation, je n'ai pas assez d'expérience pour l'essayer ...) -:

Étant donné que GoogleMap est un fragment, il devrait être possible d'attraper l'événement Marker onClick et d'afficher une vue fragmentée personnalisée . Un fragment de carte sera toujours visible en arrière-plan. Quelqu'un a-t-il essayé? Une raison pour laquelle cela ne pouvait pas fonctionner?

L'inconvénient est que le fragment de carte serait gelé sur l'arrière-plan, jusqu'à ce qu'un fragment d'information personnalisé lui retourne le contrôle.


5
Je ne pense pas qu'il soit intéressant de poster une spéculation comme réponse. Cela devrait peut-être être un commentaire sur la question initiale. Juste mes pensées.
Johan Karlsson

1
Cette bibliothèque github.com/Appolica/InteractiveInfoWindowAndroid fait quelque chose de similaire
Deyan Genovski

-2

J'ai créé un exemple de projet de studio Android pour cette question.

captures d'écran de sortie: -

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

Téléchargez le code source complet du projet Cliquez ici

Remarque: vous devez ajouter votre clé API dans Androidmanifest.xml


5
Il est préférable de partager votre code via Github / gist au lieu d'un fichier zip. Nous pouvons donc vérifier directement là-bas.
Noundla Sandeep

un petit bug que j'ai trouvé, lorsque vous appuyez sur un bouton, tous les boutons sont cliqués.
Morozov

3
@Noundla & autres: ne vous embêtez pas à télécharger son code zippé. C'est un copier-coller exact de la réponse acceptée pour cette question.
Ganesh Mohan

1
@ ganesh2shiv a raison, le code zippé est inutile. Vous ne devriez pas être joint code copié-collé
Onkar Nene

-7

C'est vraiment simple.

googleMap.setInfoWindowAdapter(new InfoWindowAdapter() {

            // Use default InfoWindow frame
            @Override
            public View getInfoWindow(Marker marker) {              
                return null;
            }           

            // Defines the contents of the InfoWindow
            @Override
            public View getInfoContents(Marker marker) {

                // Getting view from the layout file info_window_layout
                View v = getLayoutInflater().inflate(R.layout.info_window_layout, null);

                // Getting reference to the TextView to set title
                TextView note = (TextView) v.findViewById(R.id.note);

                note.setText(marker.getTitle() );

                // Returning the view containing InfoWindow contents
                return v;

            }

        });

Ajoutez simplement le code ci-dessus dans votre classe où vous utilisez GoogleMap. R.layout.info_window_layout est notre mise en page personnalisée qui montre la vue qui viendra à la place de l'infowindow. Je viens d'ajouter le textview ici. Vous pouvez ajouter une vue supplémentaire ici pour en faire un exemple d'accrochage. Mon info_window_layout était

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:orientation="vertical" 
    >

        <TextView 
        android:id="@+id/note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

J'espère que cela aidera. Nous pouvons trouver un exemple fonctionnel d'infowindow personnalisé à l' adresse http://wptrafficanalyzer.in/blog/customizing-infowindow-contents-in-google-map-android-api-v2-using-infowindowadapter/#comment-39731

EDITED: Ce code montre comment nous pouvons ajouter une vue personnalisée sur infoWindow. Ce code n'a pas géré les clics sur les éléments de vue personnalisée. Il est donc proche de la réponse, mais pas exactement la réponse, c'est pourquoi cela n'est pas accepté comme réponse.


Remplacez-vous getInfoContents et getInfoWindow où vous créez les marqueurs? Par exemple, si j'ajoute différents types de marqueurs et que j'utilise différentes méthodes, est-ce que je remplacerais simplement dans le corps de chacune de ces méthodes où je crée les différents marqueurs?
Rarw

Oui, je remplace getInfoContents et getInfoWindow.
Zeeshan Mirza

Je viens de l'essayer et cela fonctionne comme pour gonfler n'importe quelle autre mise en page xml - bon travail
Rarw

31
Il semble que vous n’avez pas lu ma question. Je sais comment créer une vue personnalisée dans infowindow. Ce que je ne sais pas, c'est comment créer un bouton personnalisé à l'intérieur et écouter ses événements de clic.
user1943012

5
Cette solution ne répond pas à la question d'origine
biddulph.r
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.