Google Maps Android API v2 - Interaktives InfoWindow (wie in Original-Android-Google Maps)

191

Ich versuche, InfoWindownach dem Klicken auf eine Markierung mit der neuen Google Maps API v2 eine benutzerdefinierte Einstellung vorzunehmen . Ich möchte, dass es so aussieht wie in der ursprünglichen Kartenanwendung von Google. So was:

Beispielbild

Wenn ich ImageButtondrinnen bin , funktioniert es nicht - das InfoWindowGanze wird geschlafen und nicht nur das ImageButton. Ich habe gelesen, dass es daran liegt, dass es kein ViewSelbst gibt, sondern einen Schnappschuss, sodass einzelne Elemente nicht voneinander unterschieden werden können.

EDIT: In der Dokumentation (danke an Disco S2 ):

Wie im vorherigen Abschnitt zu Infofenstern erwähnt, ist ein Infofenster keine Live-Ansicht, sondern die Ansicht wird als Bild auf der Karte gerendert. Infolgedessen werden alle Listener, die Sie in der Ansicht festgelegt haben, ignoriert und Sie können nicht zwischen Klickereignissen in verschiedenen Teilen der Ansicht unterscheiden. Es wird empfohlen, keine interaktiven Komponenten wie Schaltflächen, Kontrollkästchen oder Texteingaben in Ihrem benutzerdefinierten Infofenster zu platzieren.

Aber wenn Google es verwendet, muss es eine Möglichkeit geben, es zu machen. Hat jemand eine Idee?

user1943012
quelle
"Es funktioniert nicht" ist keine besonders nützliche Beschreibung Ihrer Symptome. Hier ist ein Beispielprojekt, das zeigt, dass ein benutzerdefiniertes
Infofenster
8
@ CommonsWare Ich habe Grund dafür geschrieben. Aus der Originaldokumentation Hinweis: Das gezeichnete Infofenster ist keine Live-Ansicht. Die Ansicht wird als Bild gerendert (mit View.draw(Canvas)) ... Dies bedeutet, dass nachfolgende Änderungen an der Ansicht nicht vom Infofenster auf der Karte übernommen werden. So aktualisieren Sie das Infofenster später Darüber hinaus berücksichtigt das Infofenster keine der für eine normale Ansicht typischen Interaktivitäten wie Berührungs- oder Gestenereignisse. Sie können jedoch ein allgemeines Klickereignis im gesamten Infofenster abhören, wie im folgenden Abschnitt beschrieben. ... in Ihrem Beispiel ist nur Textansicht
user1943012
Hallo, wie haben Sie die Schaltfläche "Mein Standort" im Vollbildmodus unter der Aktionsleiste angezeigt? Vielen Dank!
Tkblackbelt
45
Ich denke nicht, dass diese Frage geschlossen werden sollte. Es ist eine berechtigte Frage zu einem Problem, mit dem ich auch konfrontiert bin.
Marienke
12
Ich kann das so oft sehen, dass viele der nützlichsten Fragen mit Antwort geschlossen wurden. Eine Frage entspricht möglicherweise nicht immer den genauen Anforderungen einer richtigen Frage. Wenn wir jedoch ein gut erklärtes PROBLEM und eine gut erläuterte ANTWORT haben, gibt es keinen Grund, die Frage zu schließen. Die Leute benutzen Stackoverflow, um Wissen auszutauschen und einen Fragethread zu schließen, der so aktiv und produktiv ist wie "Off-Topic". Das scheint mir keine gute Idee zu sein.
John

Antworten:

333

Ich habe selbst ohne Glück nach einer Lösung für dieses Problem gesucht, also musste ich meine eigene rollen, die ich hier mit Ihnen teilen möchte. (Bitte entschuldigen Sie mein schlechtes Englisch) (Es ist ein bisschen verrückt, einem anderen Tschechen auf Englisch zu antworten :-))

Das erste, was ich versuchte, war, ein gutes altes zu verwenden PopupWindow. Es ist ganz einfach - man muss nur das anhören OnMarkerClickListenerund dann einen Brauch PopupWindowüber dem Marker anzeigen. Einige andere Leute hier auf StackOverflow haben diese Lösung vorgeschlagen und sie sieht auf den ersten Blick ziemlich gut aus. Das Problem mit dieser Lösung tritt jedoch auf, wenn Sie die Karte verschieben. Sie müssen das PopupWindowirgendwie selbst bewegen, was möglich ist (indem Sie einige onTouch-Ereignisse anhören), aber meiner Meinung nach können Sie es nicht gut genug aussehen lassen, insbesondere auf einigen langsamen Geräten. Wenn Sie es auf einfache Weise tun, "springt" es von einem Punkt zum anderen. Sie könnten auch einige Animationen verwenden, um diese Sprünge zu polieren, aber auf diese Weise PopupWindowwird der immer "ein Schritt zurück" sein, wo er auf der Karte sein sollte, was mir einfach nicht gefällt.

Zu diesem Zeitpunkt dachte ich über eine andere Lösung nach. Mir wurde klar, dass ich eigentlich nicht so viel Freiheit brauche - um meine benutzerdefinierten Ansichten mit allen damit verbundenen Möglichkeiten (wie animierten Fortschrittsbalken usw.) anzuzeigen. Ich denke, es gibt einen guten Grund, warum selbst die Google-Ingenieure dies in der Google Maps-App nicht so machen. Ich brauche nur ein oder zwei Schaltflächen im InfoWindow, die einen gedrückten Status anzeigen und beim Klicken einige Aktionen auslösen. Also habe ich eine andere Lösung gefunden, die sich in zwei Teile aufteilt:

Erster Teil:
Der erste Teil besteht darin, die Klicks auf die Schaltflächen zu erfassen, um eine Aktion auszulösen. Meine Idee ist wie folgt:

  1. Behalten Sie einen Verweis auf das benutzerdefinierte infoWindow bei, das im InfoWindowAdapter erstellt wurde.
  2. Wickeln Sie das MapFragment(oder MapView) in eine benutzerdefinierte ViewGroup (meine heißt MapWrapperLayout).
  3. Überschreiben Sie das MapWrapperLayoutdispatchTouchEvent des Routens und leiten Sie die MotionEvents (falls das InfoWindow derzeit angezeigt wird) zuerst zum zuvor erstellten InfoWindow. Wenn die MotionEvents nicht verwendet werden (z. B. weil Sie in InfoWindow usw. nicht auf einen anklickbaren Bereich geklickt haben), lassen Sie die Ereignisse (und nur dann) in die Superklasse von MapWrapperLayout herunter, damit sie schließlich an die Karte gesendet werden.

Hier ist der Quellcode von 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);
    }
}

All dies macht die Ansichten in der InfoView wieder "live" - ​​die OnClickListener werden ausgelöst usw.

Zweiter Teil: Das verbleibende Problem ist, dass Sie offensichtlich keine Änderungen an der Benutzeroberfläche Ihres InfoWindow auf dem Bildschirm sehen können. Dazu müssen Sie Marker.showInfoWindow manuell aufrufen. Wenn Sie nun eine permanente Änderung in Ihrem InfoWindow vornehmen (z. B. die Bezeichnung Ihrer Schaltfläche in etwas anderes ändern), ist dies gut genug.

Es ist jedoch komplizierter, einen Knopfdruckzustand oder etwas Ähnliches anzuzeigen. Das erste Problem ist, dass ich (zumindest) das InfoWindow nicht dazu bringen konnte, den gedrückten Zustand der normalen Taste anzuzeigen. Selbst wenn ich die Taste lange gedrückt habe, blieb sie auf dem Bildschirm einfach nicht gedrückt. Ich glaube, dies wird vom Karten-Framework selbst erledigt, das wahrscheinlich sicherstellt, dass in den Infofenstern kein vorübergehender Status angezeigt wird. Aber ich könnte mich irren, ich habe nicht versucht, das herauszufinden.

Was ich getan habe, ist ein weiterer böser Hack - ich habe einen OnTouchListeneran den Button angehängt und den Hintergrund manuell umgeschaltet, als der Button gedrückt oder auf zwei benutzerdefinierte Drawables losgelassen wurde - eines mit einem Button im normalen Zustand und das andere im gedrückten Zustand. Das ist nicht sehr schön, aber es funktioniert :). Jetzt konnte ich sehen, wie die Taste auf dem Bildschirm zwischen normalem und gedrücktem Zustand umschaltete.

Es gibt noch einen letzten Fehler - wenn Sie zu schnell auf die Schaltfläche klicken, wird der gedrückte Zustand nicht angezeigt - sie bleibt nur im normalen Zustand (obwohl der Klick selbst ausgelöst wird, sodass die Schaltfläche "funktioniert"). Zumindest zeigt sich das auf meinem Galaxy Nexus so. Das Letzte, was ich getan habe, ist, dass ich den Knopf im gedrückten Zustand etwas verzögert habe. Dies ist auch ziemlich hässlich und ich bin mir nicht sicher, wie es auf einigen älteren, langsamen Geräten funktionieren würde, aber ich vermute, dass sogar das Karten-Framework selbst so etwas tut. Sie können es selbst versuchen - wenn Sie auf das gesamte InfoWindow klicken, bleibt es etwas länger gedrückt, als dies bei normalen Tasten der Fall ist (wieder - zumindest auf meinem Telefon). Und so funktioniert es auch mit der ursprünglichen Google Maps-App.

Wie auch immer, ich habe mir eine benutzerdefinierte Klasse geschrieben, die die Statusänderungen der Schaltflächen und alle anderen Dinge behandelt, die ich erwähnt habe. Hier ist also der 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);
}

Hier ist eine benutzerdefinierte InfoWindow-Layoutdatei, die ich verwendet habe:

<?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>

Testaktivitäts-Layoutdatei ( MapFragmentinnerhalb der 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>

Und schließlich der Quellcode einer Testaktivität, die all dies zusammenklebt:

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

Das ist es. Bisher habe ich dies nur auf meinem Galaxy Nexus (4.2.1) und Nexus 7 (auch 4.2.1) getestet. Ich werde es auf einem Lebkuchen-Handy ausprobieren, wenn ich die Gelegenheit dazu habe. Eine Einschränkung, die ich bisher festgestellt habe, ist, dass Sie die Karte nicht von der Schaltfläche auf dem Bildschirm ziehen und die Karte verschieben können. Es könnte wahrscheinlich irgendwie überwunden werden, aber im Moment kann ich damit leben.

Ich weiß, dass dies ein hässlicher Hack ist, aber ich habe einfach nichts Besseres gefunden und ich brauche dieses Designmuster so dringend, dass dies wirklich ein Grund wäre, zum Map v1-Framework zurückzukehren (was ich übrigens wirklich sehr gerne vermeiden würde für eine neue App mit Fragmenten etc.). Ich verstehe nur nicht, warum Google Entwicklern keine offizielle Möglichkeit bietet, eine Schaltfläche in InfoWindows zu haben. Es ist so ein gängiges Designmuster, außerdem wird dieses Muster sogar in der offiziellen Google Maps App verwendet :). Ich verstehe die Gründe, warum sie Ihre Ansichten nicht einfach in den InfoWindows "live" machen können - dies würde wahrscheinlich die Leistung beim Verschieben und Scrollen der Karte beeinträchtigen. Es sollte jedoch eine Möglichkeit geben, diesen Effekt zu erzielen, ohne Ansichten zu verwenden.

wählte007
quelle
6
interessante Lösung. Ich möchte das versuchen. Wie war die Leistung?
Patrick
10
müssen ersetzt view.setBackground(bgDrawablePressed);werden if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { view.setBackgroundDrawable(bgDrawablePressed); }else{ view.setBackground(bgDrawablePressed); }, um in Android Gingerbread zu arbeiten. Hinweis: Es gibt noch ein weiteres Vorkommen view.setBackgroundim Code.
Balaji
4
KK_07k11A0585: Ihre InfoWindowAdapter-Implementierung scheint wahrscheinlich falsch zu sein. Es gibt zwei Methoden zum Überschreiben: getInfoWindow () und getInfoContents (). Das Framework ruft zuerst die Methode getInfoWindow () auf und (nur) wenn es null zurückgibt, ruft es getInfoContents () auf. Wenn Sie eine Ansicht von getInfoWindow zurückgeben, legt das Framework diese in den Standardrahmen für das Infofenster - was Sie in Ihrem Fall nicht möchten. Geben Sie also einfach null von getInfoWindow zurück und geben Sie View von getInfoContent zurück. Dann sollte es in Ordnung sein.
wählte 007
18
Wenn jemand interessiert ist, verwende ich diese Lösung in meiner eigenen App - CG Transit (Sie finden sie bei Google Play). Nach zwei Monaten wurde es von mehreren Zehntausenden von Benutzern verwendet und noch hat sich niemand darüber beschwert.
wählte 007
2
Wie bestimmen Sie den unteren Versatz, wenn Sie kein Standard-Infofenster verwenden?
Rarw
12

Ich sehe, dass diese Frage schon alt ist, aber immer noch ...

Wir haben in unserem Unternehmen eine Sipmle-Bibliothek erstellt, um das zu erreichen, was gewünscht wird - ein interaktives Infofenster mit Ansichten und allem. Sie können es auf Github überprüfen .

Ich hoffe, es hilft :)

Dephinera
quelle
Ist es möglich, es mit Clustern zu verwenden?
Gcolucci
9

Hier ist meine Sicht auf das Problem. Ich erstelle ein AbsoluteLayoutOverlay, das das Infofenster enthält (eine reguläre Ansicht mit jeder Menge Interaktivität und Zeichenfunktionen). Dann beginne ich, Handlerder alle 16 ms die Position des Infofensters mit der Position des Punktes auf der Karte synchronisiert. Klingt verrückt, funktioniert aber tatsächlich.

Demo-Video: https://www.youtube.com/watch?v=bT9RpH4p9mU (berücksichtigen Sie, dass die Leistung aufgrund der gleichzeitigen Ausführung von Emulator- und Videoaufzeichnung abnimmt).

Code der Demo: https://github.com/deville/info-window-demo

Ein Artikel mit Details (auf Russisch): http://habrahabr.ru/post/213415/

Andrii Chernenko
quelle
Dies wird das Scrollen der Karte verlangsamen / verzögern
Shane Ekanayake
2

Für diejenigen, die keine choose007'sAntwort bekommen konnten

Wenn die Lösung clickListenerin der chose007'sLösung nicht immer ordnungsgemäß funktioniert , versuchen Sie View.onTouchListenerstattdessen , sie zu implementieren clickListener. Behandeln Sie Berührungsereignisse mit einer der Aktionen ACTION_UPoder ACTION_DOWN. Aus irgendeinem Grund infoWindowverursachen Karten beim Versenden an ein seltsames Verhalten 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;
          }

Bearbeiten: So habe ich es Schritt für Schritt gemacht

Blasen Sie zuerst Ihr eigenes Infofenster (globale Variable) irgendwo in Ihrer Aktivität / Ihrem Fragment auf. Meins ist in Fragment. Stellen Sie außerdem sicher, dass die Stammansicht in Ihrem Infofenster-Layout ein lineares Layout ist (aus irgendeinem Grund nahm das relative Layout im Infofenster die volle Bildschirmbreite ein).

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;

Dann in onMapReady Rückruf von Google Maps Android API (folgen Sie diesem, wenn Sie nicht wissen, was onMapReady ist Maps> Dokumentation - Erste Schritte )

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

MapWrapperLayoutInitialisierung http://stackoverflow.com/questions/14123243/google-maps-android-api-v2-interaktives-infowindow-like-in-original-android-go / 15040761 # 15040761 39 - Standardmarkierungshöhe 20 - Versatz zwischen dem Standard-InfoWindow-Unterkante und Inhaltsunterkante * /

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

SetInfoWindow-Methode

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

Griffmarker klicken separat

    @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;
    }
Manish Jangid
quelle
Kannst du mir deinen Code zeigen? Ich konnte Berührungsereignisse erfolgreich protokollieren, aber keine Klickereignisse, also habe ich mich für diese Implementierung entschieden. Außerdem müssen Sie die Lösung von Choose007 ordnungsgemäß implementieren.
Manish Jangid
Ich habe meine vollständige Implementierung geteilt. Lassen Sie mich wissen, wenn Sie Fragen haben
Manish Jangid
0

Nur eine Spekulation, ich habe nicht genug Erfahrung, um es zu versuchen ...) -:

Da GoogleMap ein Fragment ist, sollte es möglich sein, das Ereignis onClick zu markieren und eine benutzerdefinierte Fragmentansicht anzuzeigen. Im Hintergrund ist weiterhin ein Kartenfragment sichtbar. Hat es jemand versucht? Gibt es einen Grund, warum es nicht funktionieren konnte?

Der Nachteil ist, dass das Kartenfragment im Hintergrund eingefroren wird, bis ein benutzerdefiniertes Infofragment die Steuerung zurückgibt.

Wooff
quelle
5
Ich finde es nicht interessant, eine Spekulation als Antwort zu posten. Vielleicht sollte es ein Kommentar zur ursprünglichen Frage sein. Nur meine Gedanken.
Johan Karlsson
1
Diese Bibliothek github.com/Appolica/InteractiveInfoWindowAndroid macht etwas ähnliches
Deyan Genovski
-2

Ich habe ein Beispiel für ein Android Studio-Projekt für diese Frage erstellt.

Screenshots ausgeben: -

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Vollständigen Projektquellcode herunterladen Klicken Sie hier

Bitte beachten Sie : Sie müssen Ihren API-Schlüssel in Androidmanifest.xml hinzufügen

Premkumar Manipillai
quelle
5
Es ist besser, Ihren Code über Github / gist zu teilen, als über eine Zip-Datei. So können wir dort direkt nachsehen.
Noundla Sandeep
Ein kleiner Fehler, den ich gefunden habe, wenn du auf eine Schaltfläche drückst, werden alle Schaltflächen angeklickt.
Morozov
3
@Noundla & andere: Laden Sie nicht seine Postleitzahl herunter. Es ist eine exakte Kopie der akzeptierten Antwort auf diese Frage.
Ganesh Mohan
1
@ ganesh2shiv ist richtig, die Postleitzahl ist nutzlos. Sie sollten nicht angehängt werden, kopieren Sie den eingefügten Code
Onkar Nene
-7

Es ist wirklich einfach.

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;

            }

        });

Fügen Sie einfach den obigen Code in Ihre Klasse ein, in der Sie GoogleMap verwenden. R.layout.info_window_layout ist unser benutzerdefiniertes Layout, das die Ansicht anzeigt, die anstelle des Infofensters angezeigt wird. Ich habe gerade die Textansicht hier hinzugefügt. Sie können hier eine zusätzliche Ansicht hinzufügen, damit sie dem Beispiel-Snap entspricht. Mein info_window_layout war

<?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>

Ich hoffe es wird helfen. Ein funktionierendes Beispiel für ein benutzerdefiniertes Infofenster finden Sie unter http://wptrafficanalyzer.in/blog/customizing-infowindow-contents-in-google-map-android-api-v2-using-infowindowadapter/#comment-39731

BEARBEITET: Dieser Code zeigt, wie wir eine benutzerdefinierte Ansicht in infoWindow hinzufügen können. Dieser Code hat die Klicks auf Elemente der benutzerdefinierten Ansicht nicht verarbeitet. Es ist also nah an der Antwort, aber nicht genau die Antwort, weshalb es nicht als Antwort akzeptiert wird.

Zeeshan Mirza
quelle
Überschreiben Sie getInfoContents und getInfoWindow, in denen Sie die Markierungen erstellen? Wenn ich beispielsweise verschiedene Arten von Markern hinzufüge und verschiedene Methoden verwende, würde ich dann einfach den Hauptteil jeder dieser Methoden überschreiben, bei denen ich die verschiedenen Marker erstelle?
Rarw
Ja, ich überschreibe getInfoContents und getInfoWindow.
Zeeshan Mirza
Ich habe
31
Es sieht so aus, als hättest du meine Frage nicht gelesen. Ich weiß, wie man eine benutzerdefinierte Ansicht im Infofenster erstellt. Was ich nicht weiß, ist, wie man eine benutzerdefinierte Schaltfläche erstellt und die Klickereignisse abhört.
user1943012
5
Diese Lösung beantwortet nicht die ursprüngliche Frage
biddulph.r