Monday, November 10, 2014

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. 

1 comment:

  1. Casino Nightclub Review - Dr.MCD
    I had a great time playing the game 공주 출장샵 at this one and noticed the 안동 출장안마 amount of play I could get at it. It 익산 출장마사지 was 제주도 출장샵 a lot more fun to do the casino 서울특별 출장안마 night

    ReplyDelete