Google Maps Android API v2 - Interactive InfoWindow (like in original android google maps)

127,715

Solution 1

I was looking for a solution to this problem myself with no luck, so I had to roll my own which I would like to share here with you. (Please excuse my bad English) (It's a little crazy to answer another Czech guy in English :-) )

The first thing I tried was to use a good old PopupWindow. It's quite easy - one only has to listen to the OnMarkerClickListener and then show a custom PopupWindow above the marker. Some other guys here on StackOverflow suggested this solution and it actually looks quite good at first glance. But the problem with this solution shows up when you start to move the map around. You have to move the PopupWindow somehow yourself which is possible (by listening to some onTouch events) but IMHO you can't make it look good enough, especially on some slow devices. If you do it the simple way it "jumps" around from one spot to another. You could also use some animations to polish those jumps but this way the PopupWindow will always be "a step behind" where it should be on the map which I just don't like.

At this point, I was thinking about some other solution. I realized that I actually don't really need that much freedom - to show my custom views with all the possibilities that come with it (like animated progress bars etc.). I think there is a good reason why even the google engineers don't do it this way in the Google Maps app. All I need is a button or two on the InfoWindow that will show a pressed state and trigger some actions when clicked. So I came up with another solution which splits up into two parts:

First part:
The first part is to be able to catch the clicks on the buttons to trigger some action. My idea is as follows:

  1. Keep a reference to the custom infoWindow created in the InfoWindowAdapter.
  2. Wrap the MapFragment (or MapView) inside a custom ViewGroup (mine is called MapWrapperLayout)
  3. Override the MapWrapperLayout's dispatchTouchEvent and (if the InfoWindow is currently shown) first route the MotionEvents to the previously created InfoWindow. If it doesn't consume the MotionEvents (like because you didn't click on any clickable area inside InfoWindow etc.) then (and only then) let the events go down to the MapWrapperLayout's superclass so it will eventually be delivered to the map.

Here is the MapWrapperLayout's source code:

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 this will make the views inside the InfoView "live" again - the OnClickListeners will start triggering etc.

Second part: The remaining problem is, that obviously, you can't see any UI changes of your InfoWindow on screen. To do that you have to manually call Marker.showInfoWindow. Now, if you perform some permanent change in your InfoWindow (like changing the label of your button to something else), this is good enough.

But showing a button pressed state or something of that nature is more complicated. The first problem is, that (at least) I wasn't able to make the InfoWindow show normal button's pressed state. Even if I pressed the button for a long time, it just remained unpressed on the screen. I believe this is something that is handled by the map framework itself which probably makes sure not to show any transient state in the info windows. But I could be wrong, I didn't try to find this out.

What I did is another nasty hack - I attached an OnTouchListener to the button and manually switched it's background when the button was pressed or released to two custom drawables - one with a button in a normal state and the other one in a pressed state. This is not very nice, but it works :). Now I was able to see the button switching between normal to pressed states on the screen.

There is still one last glitch - if you click the button too fast, it doesn't show the pressed state - it just remains in its normal state (although the click itself is fired so the button "works"). At least this is how it shows up on my Galaxy Nexus. So the last thing I did is that I delayed the button in it's pressed state a little. This is also quite ugly and I'm not sure how would it work on some older, slow devices but I suspect that even the map framework itself does something like this. You can try it yourself - when you click the whole InfoWindow, it remains in a pressed state a little longer, then normal buttons do (again - at least on my phone). And this is actually how it works even on the original Google Maps app.

Anyway, I wrote myself a custom class which handles the buttons state changes and all the other things I mentioned, so here is the 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);
}

Here is a custom InfoWindow layout file that I used:

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

Test activity layout file (MapFragment being inside the 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>

And finally source code of a test activity, which glues all this together:

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

That's it. So far I only tested this on my Galaxy Nexus (4.2.1) and Nexus 7 (also 4.2.1), I will try it on some Gingerbread phone when I have a chance. A limitation I found so far is that you can't drag the map from where is your button on the screen and move the map around. It could probably be overcome somehow but for now, I can live with that.

I know this is an ugly hack but I just didn't find anything better and I need this design pattern so badly that this would really be a reason to go back to the map v1 framework (which btw. I would really really like to avoid for a new app with fragments etc.). I just don't understand why Google doesn't offer developers some official way to have a button on InfoWindows. It's such a common design pattern, moreover this pattern is used even in the official Google Maps app :). I understand the reasons why they can't just make your views "live" in the InfoWindows - this would probably kill performance when moving and scrolling map around. But there should be some way how to achieve this effect without using views.

Solution 2

I see that this question is already old but still...

We made a sipmle library at our company for achieving what is desired - An interactive info window with views and everything. You can check it out on github.

I hope it helps :)

Solution 3

Here's my take on the problem. I create AbsoluteLayout overlay which contains Info Window (a regular view with every bit of interactivity and drawing capabilities). Then I start Handler which synchronizes the info window's position with position of point on the map every 16 ms. Sounds crazy, but actually works.

Demo video: https://www.youtube.com/watch?v=bT9RpH4p9mU (take into account that performance is decreased because of emulator and video recording running simultaneously).

Code of the demo: https://github.com/deville/info-window-demo

An article providing details (in Russian): http://habrahabr.ru/post/213415/

Solution 4

For those who couldn't get choose007's answer up and running

If clickListener is not working properly at all times in chose007's solution, try to implement View.onTouchListener instead of clickListener. Handle touch event using any of the action ACTION_UP or ACTION_DOWN. For some reason, maps infoWindow causes some weird behaviour when dispatching to 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 : This is how I did it step by step

First inflate your own infowindow (global variable) somewhere in your activity/fragment. Mine is within fragment. Also insure that root view in your infowindow layout is linearlayout (for some reason relativelayout was taking full width of screen in 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;

Then in onMapReady callback of google maps android api (follow this if you donot know what onMapReady is Maps > Documentation - Getting Started )

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

MapWrapperLayout initialization http://stackoverflow.com/questions/14123243/google-maps-android-api-v2- interactive-infowindow-like-in-original-android-go/15040761#15040761 39 - default marker height 20 - offset between the default InfoWindow bottom edge and it's content bottom edge */

        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 method

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

Handle marker click separately

    @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;
    }
Share:
127,715

Related videos on Youtube

user1943012
Author by

user1943012

Updated on January 18, 2020

Comments

  • user1943012
    user1943012 over 4 years

    I am trying to a make custom InfoWindow after a click on a marker with the new Google Maps API v2. I want it to look like in the original maps application by Google. Like this:

    Example Image

    When I have ImageButton inside, its not working - the entire InfoWindow is slected and not just the ImageButton. I read that it is because there isn't a View itself but it's snapshot, so individual items cannot be distinguished from each other.

    EDIT: In the documentation (thanks to Disco S2):

    As mentioned in the previous section on info windows, an info window is not a live View, rather the view is rendered as an image onto the map. As a result, any listeners you set on the view are disregarded and you cannot distinguish between click events on various parts of the view. You are advised not to place interactive components — such as buttons, checkboxes, or text inputs — within your custom info window.

    But if Google use it, there must be some way to make it. Does anyone have any idea?

    • CommonsWare
      CommonsWare over 11 years
      "its not working" is not an especially useful description of your symptoms. Here is a sample project that shows having a custom info window with an image: github.com/commonsguy/cw-omnibus/tree/master/MapsV2/Popups
    • user1943012
      user1943012 over 11 years
      @CommonsWare I wrote reason of that. From original documentation Note: The info window that is drawn is not a live view. The view is rendered as an image (using View.draw(Canvas))... This means that any subsequent changes to the view will not be reflected by the info window on the map. To update the info window later Furthermore, the info window will not respect any of the interactivity typical for a normal view such as touch or gesture events. However you can listen to a generic click event on the whole info window as described in the section below. ... in your example is only textview
    • tkblackbelt
      tkblackbelt about 11 years
      Hi, how did you get the my location button to appear below the action bar when in full screen mode? Thanks!
    • Driss Bounouar
      Driss Bounouar almost 11 years
      @tkblackbelt try relativeLayout's
    • marienke
      marienke over 9 years
      I don't think that this question should be closed. It's a legit question to a problem I'm also facing.
    • John
      John over 9 years
      I can see this so often, many of the most useful questions with answer have been closed. A question might not always fit the exact requirements of a proper question but if we have a well explained PROBLEM and a well explained ANSWER then there is no reason to close the question. People use Stackoverflow to exchange knowledge and closing a question thread which is so actively and so productive as "off-topic" does not seem like a good idea to me..
  • Rarw
    Rarw about 11 years
    Do you override getInfoContents and getInfoWindow where you create the markers? For example, if I am adding different kinds of markers and using different methods, would I just override within the body of each of those methods where I create the different markers?
  • Zeeshan Mirza
    Zeeshan Mirza about 11 years
    Yes I override getInfoContents and getInfoWindow.
  • Rarw
    Rarw about 11 years
    Just tried this out and it works jus like inflating any other xml layout - good job
  • user1943012
    user1943012 about 11 years
    It looks like you didnt read my question. I know how to create custom view inside infowindow. Which i dont know is how to make custom button inside and listen to its click events.
  • Patrick Jackson
    Patrick Jackson about 11 years
    interesting solution. I want to try this. How was the performance?
  • biddulph.r
    biddulph.r about 11 years
    This solution does not answer the original question
  • Venkat
    Venkat about 11 years
    actually i place a button on the popup dialog.i write onClickListener for that button.but it is not working. how to write listener for the button in the popup dialog???? @zeeshan0026
  • Harsh Trivedi
    Harsh Trivedi about 11 years
    Hello I have checked your code and it works fine for me but i have one more que that Can we change the position of infowindow from Upper side to right side of marker ???
  • Harsh Trivedi
    Harsh Trivedi about 11 years
    zeeshan i just want to know is it possible or not ?
  • TlmaK0
    TlmaK0 almost 11 years
    This solution does not answer the question
  • user2251725
    user2251725 almost 11 years
    i have a issue, toast show after first click button & rortate device.
  • LostPuppy
    LostPuppy almost 11 years
    you only receive callbacks for clicks on the info window.
  • Zeeshan Mirza
    Zeeshan Mirza almost 11 years
    @TlmaK0 We know this is not answer and I also mention it at the end of the answer.
  • user2251725
    user2251725 almost 11 years
    change return type of onTouch(View v,Motion event) of OnInfoWindowElemTouchListener abstract class to true.Then Toast show after click, means touch identify on touch.
  • Balaji
    Balaji almost 11 years
    need to replace view.setBackground(bgDrawablePressed); with if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { view.setBackgroundDrawable(bgDrawablePressed); }else{ view.setBackground(bgDrawablePressed); } to work in Android Gingerbread , Note: there is one more occurrence of view.setBackground in code.
  • HaMMeReD
    HaMMeReD almost 11 years
    This isn't really the solution, it's just restating the question. This adapter takes a snapshot of the view by rendering it to a bitmap, it's not interactive.
  • rgrocha
    rgrocha almost 11 years
    A new optimization for the OnInfoWindowElemTouchListener class: Use view.setPressed() to avoid handling two backgrounds drawables. In this way you can customize the backgrounds using drawable state selectors or any other standard way of assigning backgrounds, just like in a normal layout.
  • Chintan Raghwani
    Chintan Raghwani almost 11 years
    @Balaji, I needed to add your code snippet as you suggested that make my code working in newer devices. Thank You, too.
  • Balaji
    Balaji almost 11 years
    @ChintanRaghwani the original code in the answer will work in newer devices (say jelly bean) , the code snippet in my comment is to make it work for all android build versions.
  • KK_07k11A0585
    KK_07k11A0585 almost 11 years
    Hi @chose007! Thank you very much for your answer but i have a small issue. I was able to show a custom view on marker click but still the default white layout is visible. How to hide that ..
  • chose007
    chose007 almost 11 years
    KK_07k11A0585: It seems that your InfoWindowAdapter implementation is probably wrong. There are two methods to override: getInfoWindow() and getInfoContents(). The framework first calls the getInfoWindow() method and (only) if it returns null, it calls the getInfoContents(). If you return some View from getInfoWindow, the framework will lay it inside the default info window frame - which in your case you don't want to. So just return null from getInfoWindow and return you View from getInfoContent and it should be fine then.
  • chose007
    chose007 almost 11 years
    If anybody is interested, I'm using this solution in my own app - CG Transit (you can find it on Google Play). After two months it has been used by several tens of thousands of users and nobody complained about it yet.
  • Sebastian
    Sebastian over 10 years
    I tried your code and yet it worked fine, until I tried opening a dialog from one of the buttons I added. Didn't work. It's saying "BadTokenException: Unable to add window - token null is not for an application". You got any clue on this? EDIT: Fixed it. I used "getApplicationContext()" instead of "this" when building the AlertDialog. Using "this" fixes it.
  • Dory
    Dory over 10 years
    @KK_07k11A0585 hey i m also facing same prob like yours. did you solved it.. and how ?
  • Dory
    Dory over 10 years
    @chose007 thnks,grt answer !!! Can we have as many buttons on info window and how it handles the click event of one, can all button click event be handled same way.
  • corsair992
    corsair992 over 10 years
    The MapView doesn't clear the transient state of the info window. The reason click listeners don't work properly is that the press release event (and also the press event inside scrolling containers) is posted on the ViewRoot run queue instead of a Handler if the View is not attached to a Window, and thus will only execute when the ViewRoot is refreshed. My implementation bypasses this issue by creating an invisible wrapper for the info window and adding it to the MapView wrapper. See the next comment for more.
  • corsair992
    corsair992 over 10 years
    ... The wrapper intercepts invalidate and layout request calls from the info window and dispatches them to the MapView by calling showInfoWindow() on the Marker (with a refresh buffer to ensure that the state is shown). This should make the info window fully live, although the refresh rate will be much lower than usual. Also, it's easy to enable scrolling on the map while pressing a button by dispatching the touch event to the MapView also, but then the whole window will be displayed as selected by the MapView.
  • Sergii
    Sergii over 10 years
    Thanks for this awesome solution! Is it possible here to make asynchronous update of such InfoWindow, already displayed to the user? The case is about loading images from network EDIT: solution here stackoverflow.com/questions/15503266/…
  • maza23
    maza23 about 10 years
    is it possible to use getInfoWindow() instead of getInfoContents()?
  • Gaurav Pansheriya
    Gaurav Pansheriya about 10 years
    i try this code but i has nullpointerException arise.. at com.ngshah.goglemapv2withlazyloading.MainActivity.onCreate(M‌​ainActivity.java:69) arise error exception in this code map.setInfoWindowAdapter(new InfoWindowAdapter() { }
  • edi233
    edi233 about 10 years
    I loose my gestures after click on marker. Everything is ok, untill I click on marker. After that I can't do zoom by fingers. It seems to me that some view gets my gestures. Any idea?
  • TouchBoarder
    TouchBoarder about 10 years
    did anyone else got the pressed state to work? I implemented the code as above but used a state drawable, and view.pressed(). And do I need to set the marker and showInfoView() on start/end pressed for this to work?
  • Lee Yi Hong
    Lee Yi Hong about 10 years
    I tried but there is no feedback after clicking on it. Debug and realise that startPress() is being called but endPress() is not... Anyone face the same problem?
  • Lee Yi Hong
    Lee Yi Hong about 10 years
    I sorry for spamming... But I found out the reason why mine didn't manage trigger endPress(). I made a mistake with the click element. Instead of Button, I use ImageView. After changing the ImageView to Button. It works perfectly :) Thanks @chose007
  • Frank
    Frank about 10 years
    I tryed this code, but like @GauravPansheriya i have a null pointer exceptiom in the main activity. mapWrapperLayout is ever null final MapWrapperLayout mapWrapperLayout = (MapWrapperLayout)findViewById(R.id.map_relative_layout);
  • Gorgeous_DroidVirus
    Gorgeous_DroidVirus almost 10 years
    @chose007 i m trying to create the custom adapter of infowindow and get that but when i try to get the button click on it thats time i am not get the button click.. i think this listener not work in custom adapter of infowindow.. any help..??
  • Herry
    Herry almost 10 years
    @chose007 I have use your solution for making button click work for infowindow but when change getInfoContents to use getInfoWindow for create my Map overlay it is now work perfect for deliver click event of Button ,while view return from getInfoContents work well.Do you known how to make it work perfect like it work for getInfoContents.i have to use getInfoWindow because i want to change Frame of Overlay as well.
  • Herry
    Herry almost 10 years
    @chose007 i think in your app CG Transityou are using getInfoContents to create view for your Overlay infowindow,Right?but if you change it to getInfoWindowthen it willl effect Click event of Button let me known same happen with you .
  • Pranav Mahajan
    Pranav Mahajan almost 10 years
    The solution looks huge but is as simple as Ctrl+C..Ctrl+V
  • Deinlandel
    Deinlandel over 9 years
    Thanks for the answer! Anyone posted it on github? =)
  • Code Word
    Code Word over 9 years
    Hi ! I am using the same approach to get a live infowindow with buttons. Though I cannot animate the progress bar inside but it's fine. The problem comes when I navigate back from the map fragment and come back to it. I found the infowindow started behaving like the default one. Now it is no more live. Do you have any solution for this ?
  • Rarw
    Rarw over 9 years
    how do you determine the bottom offset if you're not using a standard infowindow?
  • Manikandan
    Manikandan over 9 years
    I add my custom layout. It shows the white rectangle in the background of my custom layout. How to remove the white rectangle.
  • Manikandan
    Manikandan over 9 years
    I have a close button in the infowindow, when I click the button, I need to close the infowindow. I just set the visibility of the infowindow to gone. But, when I again click any of the markers, the infowindow doesn't appear. How to fix it?
  • Manikandan
    Manikandan over 9 years
    I like to do something on snippet text click. But it is not triggered. How to achieve this?
  • sham.y
    sham.y over 9 years
    Hi @manikandan.. instead of touch listner implement onclick listener..Me also working on same.. it is working but sometimes it is not working..I am wondering why it is not working..
  • sham.y
    sham.y over 9 years
    hi @chose007 please help me,, i am facing problem with clicking on textview instead of button.. i have 3 textviews with 3 different listners. I changed touch listener with clicklistener.. working fine with one marker.. but for other markers the listners are not calling..please help me..
  • Rethinavel
    Rethinavel over 9 years
    @chose007 Will this solution work for Map work for clustering.
  • patrickjason91
    patrickjason91 about 9 years
    I tried using this hack, it worked at first, but for my use case, I need multiple buttons to be clicked in the InfoWindow. The first button fires the onClickConfirmed callback, however the second button doesnt. Any modifications needed to make multiple buttons work?
  • Johan Karlsson
    Johan Karlsson about 9 years
    It does not seems to really answer the original question. Maybe you could expand the answer to be a full answer? Otherwise it seems no idea to upvote this answer.
  • Johan Karlsson
    Johan Karlsson about 9 years
    I don´t think it is interesting to post a speculation as an answer. Maybe it should be a comment on the original question. Just my thoughts.
  • harikrishnan
    harikrishnan about 9 years
    Hi, i tried like, write on click listener for more than one button means, listener is not working.. only, single button listener is working fine.. can you reply is it possible to write on click for more than one buttton
  • Barodapride
    Barodapride about 9 years
    How do you deal with different resolutions needing a different bottomOffsetPixels? You cannot hard code bottomOffsetPixels
  • Jaffar Raza
    Jaffar Raza about 9 years
    If we load a map fragment inside the fragment than whether this should be supported or not.
  • Volodymyr Kulyk
    Volodymyr Kulyk about 9 years
    Change OnInfoWindowElemTouchListener onTouch to return true; and this work for me.
  • Shehan Ekanayake
    Shehan Ekanayake about 9 years
    This will slow down/lag scrolling the map
  • Manish Jangid
    Manish Jangid about 8 years
    can you show me your code ? I was able to log touch events successfully but not click events so I went for this implementation. Also, you need to implement choose007's solution properly.
  • Manish Jangid
    Manish Jangid about 8 years
    I shared my complete implementation. Let me know if you have any queries
  • Marc Plano-Lesay
    Marc Plano-Lesay over 7 years
    For a weird reason, View.OnClickListener doesn't work on Android 7. Using a View.OnTouchListener filtered on MotionEvent.ACTION_UP works fine as a work-around.
  • Carson
    Carson over 7 years
    This is amazing, not only is it the perfect solution for my map, but it also taught me a lot about how to make custom views.
  • Deyan Genovski
    Deyan Genovski over 7 years
    This library github.com/Appolica/InteractiveInfoWindowAndroid does something similar
  • Mithun Sarker Shuvro
    Mithun Sarker Shuvro over 7 years
    @chose007 great solution, but I am facing a small issue, how can I make infowindow transparent? I can make it transparent but returning getInfoWindow a non null value, but then click event doesn't work
  • Noundla Sandeep
    Noundla Sandeep over 7 years
    It's better to share your code through Github/gist instead of zip file. So we can directly check there.
  • Morozov
    Morozov about 7 years
    a little bug i found, when u press on some button, all buttons are clicked.
  • Fullhdpixel
    Fullhdpixel about 7 years
    Do you know how to change this implementation for a gridview? I outlined my question here: stackoverflow.com/questions/43655642/…
  • Ganesh Mohan
    Ganesh Mohan almost 7 years
    @Noundla & others: do not bother downloading his zipped code. It's an exact copy paste of the accepted answer for this question.
  • Onkar Nene
    Onkar Nene almost 7 years
    @ganesh2shiv is right, the zipped code is useless. You shouldn't be attached copy pasted code
  • Yog Guru
    Yog Guru almost 7 years
    Can any buddy help.. This is not working for me. I followed all the steps mentioned in the answer but no luck. Checked in OS 7 in device MOTO G4+.
  • gcolucci
    gcolucci over 6 years
    Is it possible to use it with clusters?
  • Yogendra Patel
    Yogendra Patel over 3 years
    is this library work with react native expo project?
  • Jasmin Sojitra
    Jasmin Sojitra about 2 years
    Can you suggest how to make Edit text work in this? I have added Edit text but I am unable to click on it. prnt.sc/NcF6ZwDo6STJ