GrabDuck

Custom info window click listener in Google map Android

:

I was just looking at Google maps on my friend’s Android Cellphone; He asked me a great question related to info window that was “Can We People Customize info window?” Neither of us ever had seen a customized info window. I just kept thinking how it could be done If Google doesn’t provide us facilities to customize info window. I tried a lot to make a custom window.

I was trying to put more than one view on info window. Info window works only as one view that can be clicked only once as full. So Customization of info info window means adding more than one view (Buttons, or any clickable that will do some work).

I just tried it and succeed. Here are the steps or what I did.

(I assume everyone, who is referring to this post, has basic knowledge about info window and map)

This is done in two parts-

First Part:


The first part is to catch the clicks on the buttons to do some action (I just showed toast on clicks). The idea is like this:

1.    We can keep a reference to the custom info window created in the InfoWindowAdapter.

2.    Wrapping the Map Fragment inside a custom View Group (here CustomLayout)

3.    Override the CustomLayout'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 CustomLayout's super class so it will eventually be delivered to the map.

Here is the CustomLayout Code

==============================Code==============================

package ankit.custominfowindow;

import android.content.Context;

import android.graphics.Point;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.widget.RelativeLayout;

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

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

public class CustomLayout extends RelativeLayout {

      

       private GoogleMap map ;

                private int offPXL ;

       private Marker marker ;

       private View infoWindow ;

       public CustomLayout(Context context) {

              super (context);

       }

       public CustomLayout(Context context, AttributeSet attrs) {

              super (context, attrs);

       }

       public CustomLayout(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 . offPXL = bottomOffsetPixels;

       }

      

       public void setMarkerWithInfoWindow(Marker marker, View infoWindow) {

              this . marker = marker;

              this . infoWindow = infoWindow;

       }

       @Override

       public boolean dispatchTouchEvent(MotionEvent ev) {

              boolean ret = false ;

             

              if ( marker != null && marker .isInfoWindowShown() && map != null

                           && infoWindow != null ) {

                    

                     Point point = map .getProjection().toScreenLocation(

                                  marker .getPosition());

                    

                     MotionEvent copyEv = MotionEvent.obtain(ev);

                     copyEv.offsetLocation(-point. x + ( infoWindow .getWidth() / 2),

                                  -point. y + infoWindow .getHeight() + offPXL );

                                         ret = infoWindow .dispatchTouchEvent(copyEv);

              }

             

              return ret || super .dispatchTouchEvent(ev);

       }

}

Second part:   The next problem is that the UI changes of your InfoWindow are not visible on screen.

To achieve that you need to manually call Marker.showInfoWindow.

Now, if you perform some permanent changes in your InfoWindow , it is good enough.

When the button is pressed, a “p” image will be visible in place of the button to show that button has been pressed.

And Toast will be visible to show the normal button's pressed state.

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:

==============================Code==============================

package ankit.custominfowindow;

import android.annotation.SuppressLint;

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 ;

                     case MotionEvent. ACTION_UP :

                           handler .postDelayed( confirmClickRunnable , 150);

                           break ;

                     case MotionEvent. ACTION_CANCEL :

                           endPress();

                           break ;

                     default :

                           break ;

                     }

              } else {

                    

                     endPress();

              }

              return false ;

       }

       @SuppressLint ( "NewApi" ) private void startPress() {

              if (! pressed ) {

                     pressed = true ;

                     handler .removeCallbacks( confirmClickRunnable );

                     view .setBackground( bgDrawablePressed );

                     if ( marker != null )

                           marker .showInfoWindow();

              }

       }

       @SuppressLint ( "NewApi" ) 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 );

                     }

              }

       };

      

       protected abstract void onClickConfirmed(View v, Marker marker);

}

And xml file for this custominfowindow is here

==============================Code==============================

<? 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:layout_marginRight="10dp"

        android:orientation="vertical" >

        < TextView

            android:id="@+id/title"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Title"

            android:textSize="18sp" />

        < TextView

            android:id="@+id/snippet"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="snippet" />

    </ LinearLayout >

    < LinearLayout

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginRight="10dp"

        android:orientation="vertical" >

        < Button

            android:id="@+id/button2"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="@drawable/btn2" />

        < Button

            android:id="@+id/button"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="@drawable/btn" />

    </ LinearLayout >

</ LinearLayout >

Now its time to write Main Activity. What we have to do in main activity is to invoke above two codes appropriately.

So Code of Main Activity is here.

==============================Code==============================

  package ankit.custominfowindow;

import android.annotation.SuppressLint;

import android.content.Context;

import android.os.Bundle;

import android.support.v4.app.FragmentActivity;

import android.view.View;

import android.view.ViewGroup;

import android.widget.Button;

import android.widget.TextView;

import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;

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;

public class MainActivity extends FragmentActivity {

       private ViewGroup infoWindow ;

       private TextView infoTitle ;

       private TextView infoSnippet ;

       private Button infoButton , infoButton2 ;

       private OnInfoWindowElemTouchListener infoButtonListener ;

       @SuppressLint ( "NewApi" )

       @Override

       protected void onCreate(Bundle savedInstanceState) {

              super .onCreate(savedInstanceState);

              setContentView(R.layout. activity_main );

              final MapFragment mapFragment = (MapFragment) getFragmentManager()

                           .findFragmentById(R.id. map );

              final CustomLayout mapWrapperLayout = (CustomLayout) findViewById(R.id. map_relative_layout );

              final GoogleMap map = mapFragment.getMap();

              map.animateCamera(CameraUpdateFactory.newLatLngZoom( new LatLng(

                           28.611892, 77.376226), 16));

      

              mapWrapperLayout.init(map, getPixelsFromDp( this , 39 + 20));

              this . infoWindow = (ViewGroup) getLayoutInflater().inflate(

                           R.layout. custominfowindow , 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 );

              this . infoButton2 = (Button) infoWindow .findViewById(R.id. button2 );

              // Setting custom OnTouchListener which deals with the pressed state

              // so it shows up

              this . infoButtonListener = new OnInfoWindowElemTouchListener( infoButton ,

                           getResources().getDrawable(R.drawable. btn ), getResources()

                                         .getDrawable(R.drawable. btn )) {

                     @Override

                     protected void onClickConfirmed(View v, Marker marker) {

                          

                           Toast.makeText(MainActivity. this , "button Down clicked!" , Toast. LENGTH_LONG ).show();

                     }

              };

              this . infoButton .setOnTouchListener( infoButtonListener );

              this . infoButtonListener = new OnInfoWindowElemTouchListener(

                           infoButton2 , getResources().getDrawable(R.drawable. btn2 ),

                           getResources().getDrawable(R.drawable. btn2 )) {

                     @Override

                     protected void onClickConfirmed(View v, Marker marker) {

                           Toast.makeText(MainActivity. this , "button Up clicked!" ,

                                         Toast. LENGTH_LONG ).show();

                     }

              };

              this . infoButton2 .setOnTouchListener( infoButtonListener );

              map.setInfoWindowAdapter( new InfoWindowAdapter() {

                     @Override

                     public View getInfoWindow(Marker marker) {

                           return null ;

                     }

                     @Override

                     public View getInfoContents(Marker marker) {

                           infoTitle .setText(marker.getTitle());

                           infoSnippet .setText(marker.getSnippet());

                           infoButtonListener .setMarker(marker);

                           mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow );

                           return infoWindow ;

                     }

              });

              // Let's add a couple of markers

              map.addMarker( new MarkerOptions().title( "22by4 Consulting pvt ltd" )

                           .snippet( "India" ).position( new LatLng( 12.956098 , 77.636928 )));

       }

       public static int getPixelsFromDp(Context context, float dp) {

              final float scale = context.getResources().getDisplayMetrics(). density ;

              return ( int ) (dp * scale + 0.5f);

       }

}

Xml Code for main activity is

==============================Code==============================

< ankit.custominfowindow.CustomLayout 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" />

</ ankit.custominfowindow.CustomLayout >

Its done here, Thanks for looking this blog.