Google Maps Android API v2 - Interactive InfoWindow (jak w oryginalnych mapach Google na Androida)

191

Staram się dostosować do własnych potrzeb InfoWindowpo kliknięciu znacznika za pomocą nowego interfejsu API Map Google w wersji 2. Chcę, żeby wyglądało to w oryginalnej aplikacji Google do tworzenia map. Lubię to:

Przykładowy obraz

Kiedy mam w ImageButtonśrodku, nie działa - całość InfoWindowjest zaznaczona, a nie tylko ImageButton. Czytam, że dzieje się tak, ponieważ nie ma Viewsamego siebie, ale jest to migawka, więc poszczególnych elementów nie można od siebie odróżnić.

EDYCJA: W dokumentacji (dzięki Disco S2 ):

Jak wspomniano w poprzedniej sekcji o oknach informacyjnych, okno informacyjne nie jest widokiem na żywo, ale widok jest renderowany jako obraz na mapie. W rezultacie nasłuchiwania ustawione w widoku są ignorowane i nie można rozróżnić zdarzeń kliknięcia w różnych częściach widoku. Odradza się umieszczanie interaktywnych elementów - takich jak przyciski, pola wyboru lub wprowadzanie tekstu - w niestandardowym oknie informacyjnym.

Ale jeśli Google go używa, musi być jakiś sposób, aby to zrobić. Czy ktokolwiek ma jakiś pomysł?

użytkownik1943012
źródło
„nie działa” nie jest szczególnie przydatnym opisem twoich objawów. Oto przykładowy projekt pokazujący posiadanie niestandardowego okna informacyjnego z obrazem: github.com/commonsguy/cw-omnibus/tree/master/MapsV2/Popups
CommonsWare
8
@CommonsWare Napisałem powód tego. Z oryginalnej dokumentacji Uwaga: Rysowane okno informacyjne nie jest widokiem na żywo. Widok jest renderowany jako obraz (przy użyciu View.draw(Canvas)) ... Oznacza to, że wszelkie późniejsze zmiany w widoku nie zostaną odzwierciedlone przez okno informacyjne na mapie. Aby później zaktualizować okno informacyjne Ponadto okno informacyjne nie będzie respektować żadnej z interaktywności typowych dla normalnego widoku, takich jak zdarzenia dotyku lub gestu. Możesz jednak wysłuchać ogólnego zdarzenia kliknięcia w całym oknie informacyjnym, jak opisano w poniższej sekcji. ... w twoim przykładzie jest tylko
podgląd tekstu
Cześć, jak udało Ci się wyświetlić przycisk mojej lokalizacji pod paskiem akcji w trybie pełnoekranowym? Dzięki!
tkblackbelt
45
Nie sądzę, że to pytanie powinno zostać zamknięte. To uzasadnione pytanie do problemu, z którym również się zmagam.
marienke
12
Widzę to tak często, wiele najbardziej przydatnych pytań z odpowiedzią zostało zamkniętych. Pytanie może nie zawsze odpowiadać dokładnym wymaganiom właściwego pytania, ale jeśli mamy dobrze wyjaśniony PROBLEM i dobrze wyjaśnioną ODPOWIEDŹ, nie ma powodu, aby zamykać pytanie. Ludzie używają Stackoverflow do wymiany wiedzy i zamykania wątku pytań, który jest tak aktywnie i tak produktywny, że „nie na temat” nie wydaje mi się dobrym pomysłem.
John

Odpowiedzi:

333

Sam szukałem rozwiązania tego problemu bez powodzenia, więc musiałem rzucić własny, którym chciałbym się tutaj z Wami podzielić. (Proszę wybaczyć mój zły angielski) (To trochę szalone, aby odpowiedzieć innemu Czechowi po angielsku :-))

Pierwszą rzeczą, jakiej próbowałem, było użycie starego, dobrego PopupWindow. To całkiem proste - wystarczy tylko wysłuchać, OnMarkerClickListenera następnie pokazać niestandardowy PopupWindowznak nad znacznikiem. Inni faceci na StackOverflow zasugerowali to rozwiązanie i na pierwszy rzut oka wygląda całkiem dobrze. Ale problem z tym rozwiązaniem pojawia się, gdy zaczynasz przesuwać mapę. Musisz przenieść PopupWindowjakoś samemu, co jest możliwe (słuchając niektórych wydarzeń onTouch), ale IMHO nie może sprawić, aby wyglądał wystarczająco dobrze, szczególnie na niektórych powolnych urządzeniach. Jeśli zrobisz to w prosty sposób, „przeskakuje” z jednego miejsca do drugiego. Możesz także użyć animacji, aby dopracować te skoki, ale w ten sposóbPopupWindow zawsze będzie to „krok do tyłu” tam, gdzie powinno być na mapie, co mi się nie podoba.

W tym momencie myślałem o innym rozwiązaniu. Uświadomiłem sobie, że tak naprawdę nie potrzebuję tak dużo swobody - aby pokazać swoje niestandardowe widoki ze wszystkimi dostępnymi możliwościami (takimi jak animowane paski postępu itp.). Myślę, że istnieje dobry powód, dla którego nawet inżynierowie Google nie robią tego w aplikacji Mapy Google. Wszystko, czego potrzebuję, to przycisk lub dwa w oknie InfoWindow, które pokażą stan naciśnięcia i wywołają pewne działania po kliknięciu. Wymyśliłem więc inne rozwiązanie, które dzieli się na dwie części:

Pierwsza część:
pierwszą częścią jest możliwość złapania kliknięć przycisków, aby wywołać akcję. Mój pomysł jest następujący:

  1. Zachowaj odniesienie do niestandardowego infoWindow utworzonego w InfoWindowAdapter.
  2. Zawiń MapFragment(lubMapView ) wewnątrz niestandardowej grupy ViewGroup (moja nazywa się MapWrapperLayout)
  3. Zastąp MapWrapperLayout'dispatchTouchEvent i (jeśli aktualnie wyświetla się InfoWindow) najpierw skieruj MotionEvent do wcześniej utworzonego InfoWindow. Jeśli nie zużywa MotionEvents (na przykład dlatego, że nie kliknąłeś żadnego klikalnego obszaru w InfoWindow itp.), To (i tylko wtedy) pozwól, by zdarzenia zejść do nadklasy MapWrapperLayout, aby ostatecznie zostały dostarczone na mapę.

Oto kod źródłowy 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);
    }
}

Wszystko to sprawi, że widoki wewnątrz InfoView znów będą „na żywo” - OnClickListeners zacznie się uruchamiać itp.

Druga część: Pozostały problem polega na tym, że na ekranie nie widać żadnych zmian interfejsu użytkownika programu InfoWindow. Aby to zrobić, musisz ręcznie wywołać Marker.showInfoWindow. Teraz, jeśli wykonasz jakąś stałą zmianę w swoim InfoWindow (np. Zmieniając etykietę przycisku na coś innego), to wystarczy.

Ale pokazanie stanu naciśnięcia przycisku lub czegoś takiego jest bardziej skomplikowane. Pierwszym problemem jest to, że (przynajmniej) nie byłem w stanie sprawić, aby InfoWindow pokazywał stan naciśnięcia przycisku normalnego. Nawet jeśli długo naciskałem przycisk, po prostu nie został wciśnięty na ekranie. Wierzę, że jest to coś, co jest obsługiwane przez samą strukturę mapy, co prawdopodobnie upewnia się, że nie pokazuje żadnego stanu przejściowego w oknach informacyjnych. Ale mogłem się mylić, nie próbowałem tego dowiedzieć.

To, co zrobiłem, to kolejny paskudny hack - załączyłem OnTouchListener przycisk do przycisku i ręcznie przełączyłem jego tło, gdy przycisk został naciśnięty lub zwolniony do dwóch niestandardowych drawables - jednego z przyciskiem w normalnym stanie, a drugiego w stanie wciśniętym. To nie jest bardzo miłe, ale działa :). Teraz mogłem zobaczyć, jak przycisk przełącza się między stanem normalnym a naciśnięciem na ekranie.

Jest jeszcze jedna usterka - jeśli klikniesz przycisk zbyt szybko, nie pokaże on stanu naciśnięcia - po prostu pozostanie w swoim normalnym stanie (chociaż samo kliknięcie jest uruchamiane, więc przycisk „działa”). Przynajmniej tak pokazuje się na moim Galaxy Nexusie. Ostatnią rzeczą, którą zrobiłem, jest to, że nieco opóźniłem przycisk w stanie wciśniętym. Jest to również dość brzydkie i nie jestem pewien, jak to działałoby na niektórych starszych, wolnych urządzeniach, ale podejrzewam, że nawet sama struktura mapy robi coś takiego. Możesz spróbować sam - po kliknięciu całego okna InfoWindow pozostaje on dłużej w stanie wciśniętym, niż zwykłe przyciski (ponownie - przynajmniej na moim telefonie). I tak to działa nawet w oryginalnej aplikacji Google Maps.

W każdym razie napisałem sobie niestandardową klasę, która obsługuje zmiany stanu przycisków i wszystkie inne rzeczy, o których wspomniałem, więc oto kod:

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

Oto niestandardowy plik układu InfoWindow, którego użyłem:

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

Testuj plik układu działania ( MapFragmentznajdujący się w 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>

I na koniec kod źródłowy działania testowego, który łączy to wszystko razem:

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

Otóż ​​to. Do tej pory testowałem to tylko na moim Galaxy Nexusie (4.2.1) i Nexusie 7 (również 4.2.1), wypróbuję to na niektórych telefonach z Piernika, kiedy będę miał okazję. Ograniczeniem, które do tej pory znalazłem, jest to, że nie możesz przeciągać mapy z miejsca, w którym znajduje się przycisk na ekranie i przesuwać mapy. Prawdopodobnie można to jakoś pokonać, ale na razie mogę z tym żyć.

Wiem, że to brzydki hack, ale po prostu nie znalazłem nic lepszego i tak bardzo potrzebuję tego wzorca projektowego, że naprawdę byłby to powód, aby wrócić do frameworku map v1 (który przy okazji. Naprawdę bardzo chciałbym uniknąć dla nowej aplikacji z fragmentami itp.). Po prostu nie rozumiem, dlaczego Google nie oferuje programistom oficjalnego sposobu na przycisk w InfoWindows. To taki powszechny wzorzec projektowy, zresztą ten wzór jest używany nawet w oficjalnej aplikacji Google Maps :). Rozumiem powody, dla których nie mogą po prostu sprawić, by Twoje widoki były „na żywo” w InfoWindows - prawdopodobnie zabiłoby to wydajność podczas przesuwania i przewijania mapy. Ale powinien istnieć sposób na osiągnięcie tego efektu bez korzystania z widoków.

wybrał 007
źródło
6
ciekawe rozwiązanie. Chcę tego spróbować. Jak było na występie?
Patrick
10
trzeba wymienić view.setBackground(bgDrawablePressed);z if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { view.setBackgroundDrawable(bgDrawablePressed); }else{ view.setBackground(bgDrawablePressed); }do pracy w Android Gingerbread, uwaga: jest jeszcze jedna wystąpienie view.setBackgroundw kodzie.
Balaji
4
KK_07k11A0585: Wygląda na to, że Twoja implementacja InfoWindowAdapter jest prawdopodobnie nieprawidłowa. Istnieją dwie metody przesłonięcia: getInfoWindow () i getInfoContents (). Framework najpierw wywołuje metodę getInfoWindow () i (tylko), jeśli zwraca null, wywołuje getInfoContents (). Jeśli zwrócisz Widok z getInfoWindow, framework umieści go w domyślnej ramce okna informacyjnego - czego w twoim przypadku nie chcesz. Więc po prostu zwróć null z getInfoWindow i zwróć Widok z getInfoContent i powinno być w porządku.
wybrał 007
18
Jeśli ktoś jest zainteresowany, korzystam z tego rozwiązania we własnej aplikacji - CG Transit (można go znaleźć w Google Play). Po dwóch miesiącach korzystało z niego kilkadziesiąt tysięcy użytkowników i nikt jeszcze na to nie narzekał.
wybrał 007
2
Jak określić dolne przesunięcie, jeśli nie korzystasz ze standardowego okna informacyjnego?
Rarw,
12

Widzę, że to pytanie jest już stare, ale wciąż ...

W naszej firmie stworzyliśmy bibliotekę sipmle, aby osiągnąć to, co jest pożądane - interaktywne okno informacyjne z widokami i wszystkim. Możesz to sprawdzić na github .

Mam nadzieję, że to pomoże :)

dephinera
źródło
Czy można go używać z klastrami?
gcolucci
9

Oto moje podejście do problemu. Tworzę AbsoluteLayoutnakładkę, która zawiera okno informacyjne (zwykły widok z wszelkimi możliwościami interakcji i rysowania). Następnie zaczynam, Handlerktóry synchronizuje pozycję okna informacyjnego z pozycją punktu na mapie co 16 ms. Brzmi szalenie, ale tak naprawdę działa.

Wersja demonstracyjna wideo: https://www.youtube.com/watch?v=bT9RpH4p9mU (należy wziąć pod uwagę, że wydajność jest zmniejszona z powodu jednoczesnego działania emulatora i nagrywania wideo).

Kod wersji demo: https://github.com/deville/info-window-demo

Artykuł zawierający szczegóły (w języku rosyjskim): http://habrahabr.ru/post/213415/

Andrii Chernenko
źródło
Spowolni to / opóźni przewijanie mapy
Shane Ekanayake
2

Dla tych, którzy nie mogli dostać choose007's odpowiedzi

Jeśli clickListenernie zawsze działa poprawnie w chose007'srozwiązaniu, spróbuj wdrożyć View.onTouchListenerzamiast clickListener. Obsługa zdarzenia dotykowego za pomocą dowolnej akcji ACTION_UPlub ACTION_DOWN. Z jakiegoś powodu mapy infoWindowpowodują dziwne zachowanie podczas wysyłania do 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;
          }

Edycja: Tak zrobiłem krok po kroku

Najpierw napompuj własne okno informacyjne (zmienną globalną) gdzieś w swojej aktywności / fragmencie. Mój jest w obrębie fragmentu. Upewnij się również, że widok główny w układzie okna informacyjnego jest liniowy (z jakiegoś powodu układ względny zajmował całą szerokość ekranu w oknie informacyjnym)

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;

Następnie w wywołaniu zwrotnym onMapReady w Google Maps Android API (wykonaj to, jeśli nie wiesz, co to jest onMapReady Mapy> Dokumentacja - Pierwsze kroki )

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

MapWrapperLayoutinicjalizacja http://stackoverflow.com/questions/14123243/google-maps-android-api-v2- Interactive-infowindow-like-in-original-android-go / 15040761 # 15040761 39 - domyślna wysokość znacznika 20 - przesunięcie między domyślna dolna krawędź InfoWindow i jej dolna krawędź zawartości * /

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

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

Uchwyt znacznika kliknij osobno

    @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
źródło
czy możesz mi pokazać swój kod? Udało mi się zalogować zdarzenia dotykowe, ale nie kliknąłem, więc zdecydowałem się na tę implementację. Ponadto musisz poprawnie wdrożyć rozwiązanie select007.
Manish Jangid
Udostępniłem moją pełną implementację. Daj mi znać, jeśli masz jakieś pytania
Manish Jangid
0

Tylko spekulacja, nie mam wystarczającego doświadczenia, aby spróbować ...) -:

Ponieważ GoogleMap jest fragmentem, powinno być możliwe złapanie znacznika zdarzenia onClick i wyświetlenie niestandardowego widoku fragmentu . Fragment mapy będzie nadal widoczny w tle. Czy ktoś tego próbował? Czy jest jakiś powód, dla którego to nie działa

Wadą jest to, że fragment mapy zostałby zamrożony na tle, dopóki niestandardowy fragment informacji nie zwróci do niego kontroli.

Łał
źródło
5
Nie wydaje mi się, aby interesujące było opublikowanie spekulacji jako odpowiedzi. Może powinien to być komentarz do pierwotnego pytania. Tylko moje myśli.
Johan Karlsson,
1
Ta biblioteka github.com/Appolica/InteractiveInfoWindowAndroid robi coś podobnego
Deyan Genovski
-2

Dla tego pytania zbudowałem przykładowy projekt studia na Androida.

wyjściowe zrzuty ekranu: -

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Pobierz pełny kod źródłowy projektu Kliknij tutaj

Uwaga: musisz dodać klucz API w Androidmanifest.xml

Premkumar Manipillai
źródło
5
Lepiej jest udostępnić swój kod przez Github / gist zamiast pliku zip. Możemy więc bezpośrednio to sprawdzić.
Noundla Sandeep
mały błąd, który znalazłem, kiedy naciskasz jakiś przycisk, wszystkie przyciski są klikane.
Morozow
3
@Noundla i inni: nie zawracaj sobie głowy pobieraniem jego skompresowanego kodu. Jest to dokładna kopia wklejki zaakceptowanej odpowiedzi na to pytanie.
Ganesh Mohan
1
@ ganesh2shiv ma rację, kod pocztowy jest bezużyteczny. Nie powinieneś dołączać skopiowanego wklejonego kodu
Onkar Nene
-7

To jest naprawdę proste.

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;

            }

        });

Po prostu dodaj powyższy kod do swojej klasy, w której korzystasz z GoogleMap. R.layout.info_window_layout to nasz niestandardowy układ, który pokazuje widok, który pojawi się w miejscu okna informacyjnego. Właśnie dodałem tutaj widok tekstu. Możesz dodać tutaj dodatkowy widok, aby był podobny do przyciągania próbki. Mój info_window_layout był

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

Mam nadzieję, że to pomoże. Pracujący przykład niestandardowego okna informacyjnego znajduje się pod adresem stronie http://wptrafficanalyzer.in/blog/customizing-infowindow-contents-in-google-map-android-api-v2-using-infowindowadapter/#comment-39731

ZMIENIONO: Ten kod pokazuje, jak możemy dodać niestandardowy widok do infoWindow. Ten kod nie obsługiwał kliknięć elementów widoku niestandardowego. Jest więc blisko odpowiedzi, ale nie do końca odpowiedź, dlatego nie jest akceptowana jako odpowiedź.

Zeeshan Mirza
źródło
Czy zastępujesz getInfoContents i getInfoWindow, gdzie tworzysz znaczniki? Na przykład, jeśli dodaję różne rodzaje markerów i używam różnych metod, czy po prostu zastąpiłbym w obrębie każdej z tych metod, w których tworzę różne markery?
Rarw
Tak, zastępuję getInfoContents i getInfoWindow.
Zeeshan Mirza
Właśnie to wypróbowałem i działa jak nadmuchiwanie dowolnego innego układu xml - dobra robota
Rarw
31
Wygląda na to, że nie przeczytałeś mojego pytania. Wiem, jak utworzyć niestandardowy widok w oknie informacyjnym. Nie wiem, jak zrobić niestandardowy przycisk wewnątrz i słuchać jego zdarzeń kliknięcia.
user1943012,
5
To rozwiązanie nie odpowiada na pierwotne pytanie
biddulph.r 24.04.13