Monday, February 6, 2012

How to make custom overlay on android?


Using the standard Google Maps markers on a map in your application might not be the thing you want. Instead, you would like to personalize it a bit using a custom icon, for example. But what if you want to draw that overlay from scratch, containing some dynamic information, instead of using a drawable or bitmap?

 It actually is quite simple, you just need to override the draw method in the Overlay class and the onTap method if you want to do some actions resulting a tap.
I commented the code. The way I’ve selected the different points to draw on is the result of trying, moving it a bit to the left, to the top, etc. so you shouldn’t focus on that.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Typeface;
import android.util.Log;
import be.emich.villo.R;
import be.emich.villo.Villo;
import be.emich.villo.messages.StationMessage;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

public class CustomOverlay extends Overlay {
 protected Bitmap[] icons;
 protected GeoPoint point;
 protected StationMessage msg;

 protected static Bitmap GREENBIKE=null;
 protected static Bitmap REDBIKE=null;
 protected static Bitmap GREENPARKING=null;
 protected static Bitmap REDPARKING=null;
 protected static Bitmap MASTERCARD=null;

 protected static final int offsetX=18;
 protected static final int offsetY=20;

 protected int mapWidth;
 protected int mapHeight;

 protected Context ctx;

 /**
  * Constructor of the overlay. Takes in a stationmessage,
  * a POJO containing all info about the overlay to draw.
  * The context, needed to create the images and show a popup
  * when the overlay is clicked. The height and width are needed
  * so we know if drawing the overlay is necessary or not.
  * If the overlay is outside of the map, we don't minde because
  * we can't see it.
  */
 public CustomOverlay(Context ctx,StationMessage msg,int mapWidth,int mapHeight){
  this.point=new GeoPoint((int)(msg.getLatitude()*1000000),(int)(msg.getLongitude()*1000000));
  this.msg=msg;

  //If the bitmaps are not loaded yet, we load them and put them in
  //a static context so another instance of this overlay doesn't have
  //to load it.
  if(GREENBIKE==null)GREENBIKE=BitmapFactory.decodeResource(ctx.getResources(), R.drawable.greenbikesmall);
  if(REDBIKE==null)REDBIKE=BitmapFactory.decodeResource(ctx.getResources(), R.drawable.redbikesmall);
  if(GREENPARKING==null)GREENPARKING=BitmapFactory.decodeResource(ctx.getResources(), R.drawable.parkinggreen);
  if(REDPARKING==null)REDPARKING=BitmapFactory.decodeResource(ctx.getResources(), R.drawable.parkingred);
  if(MASTERCARD==null)MASTERCARD=BitmapFactory.decodeResource(ctx.getResources(), R.drawable.mastercard);

  //If ticket equals 1 we have to draw an extra icon.
  if(msg.getTicket()==1){icons = new Bitmap[3];icons[2]=MASTERCARD;}
  else icons = new Bitmap[2];

  //We fill up the other icons here.
  icons[0]=msg.getBikes()!=0?GREENBIKE:REDBIKE;
  icons[1]=msg.getParking()!=0?GREENPARKING:REDPARKING;

  this.mapWidth=mapWidth;
  this.mapHeight=mapWidth;
  this.ctx=ctx;
 }

 @Override
 public void draw(Canvas canvas, MapView mapView, boolean shadow) {
  //Translate point to x y coordinates on the screen
  Point pt = mapView.getProjection().toPixels(point, null);

  //Is the overlay outside the viewing area? If yes, do not draw it
  if(pt.x<-90 || pt.x>mapWidth || pt.y<-50 || pt.y>mapHeight+50)return;

  //Define brush 1
  Paint p1 = new Paint();
  p1.setColor(Color.BLACK);

  //Define brush 2
  Paint p2 = new Paint();
  p2.setColor(Color.WHITE);

  //Define path
  Path path = new Path();
  //Corner top left
  path.moveTo(offsetX+pt.x-20,offsetY+pt.y-60);
  //Corner top right
  path.lineTo(offsetX+pt.x+2+(18*(getCases()-1)),offsetY+pt.y-60);
  //Corner bottom right
  path.lineTo(offsetX+pt.x+2+(18*(getCases()-1)),offsetY+pt.y-30);
  //Corner triangle right
  path.lineTo(offsetX+pt.x-10,offsetY+pt.y-30);
  //Bottom triangle middle
  path.lineTo(offsetX+pt.x-20,offsetY+pt.y-20);
  //Corner bottom left
  path.lineTo(offsetX+pt.x-20,offsetY+pt.y-30);
  //Corner top left
  path.lineTo(offsetX+pt.x-20,offsetY+pt.y-60);

  //Paint polygon (made with path) and fill it using brush 2
  p2.setStyle(Paint.Style.FILL);
  canvas.drawPath(path,p2);

  //Yellow station name rect using brush 1
  p1.setColor(Color.rgb(255, 193, 40));
  p1.setStyle(Paint.Style.FILL);

  //Draw yellow box
  Path path2=new Path();
  //Corner top left
  path2.moveTo(offsetX+pt.x-20,offsetY+pt.y-60);
  //Corner top right
  path2.lineTo(offsetX+pt.x+2+(18*(getCases()-1)),offsetY+pt.y-60);
  //Corner bottom right
  path2.lineTo(offsetX+pt.x+2+(18*(getCases()-1)),offsetY+pt.y-49);
  //Corner bottom left
  path2.lineTo(offsetX+pt.x-20,offsetY+pt.y-49);
  //Corner top left
  path2.lineTo(offsetX+pt.x-20,offsetY+pt.y-60);
  canvas.drawPath(path2, p1);

  //Paint stroke above using brush 1
  p1.setColor(Color.BLACK);
  p1.setAntiAlias(false);
  p1.setStyle(Paint.Style.STROKE);
  canvas.drawPath(path,p1);

  //Draw icons inside map
  for(int i = 0;i<icons.length;i++){
   canvas.drawBitmap(icons[i],
     offsetX+pt.x+(36*(i-1))+18,
     offsetY+pt.y-48,
     p1);
  }

  p1.setAntiAlias(true);
  p1.setTypeface(Typeface.DEFAULT);
  p1.setTextSize(10);

  //Draw station occupancy
  canvas.drawText(
    Integer.toString(msg.getBikes()),
    offsetX+pt.x+2,
    offsetY+pt.y-36,
    p1);
  canvas.drawText(
    Integer.toString(msg.getParking()),
    offsetX+pt.x+2+36,
    offsetY+pt.y-36,
    p1);

  p1.setAntiAlias(true);
  p1.setTextSize(9);
  p1.setColor(Color.rgb(14,95,160));
  p1.setTypeface(Typeface.DEFAULT_BOLD);

  //Draw station name above yellow box
  canvas.drawText(
    msg.getName(),
    offsetX+2+pt.x-20,
    offsetY+pt.y-51,
    p1);
  super.draw(canvas, mapView, shadow);
 }

 /* Utility function to get the number of used "cases"
 ** this is necessary to know how large the overlay will be
 */
 protected int getCases(){
  return icons.length+2;
 }

 /*
  * This function will detect if an overlay
  * has been tapped or not and return true if so
  */
 @Override
 public boolean onTap(GeoPoint p, MapView mapView) {
  Point pointTap = mapView.getProjection().toPixels(p, null);
  Point pointMap = mapView.getProjection().toPixels(point, null);
  if(
    pointTap.x-pointMap.x>=0
    && pointTap.x-pointMap.x<=80
    && pointMap.y-pointTap.y>=0
    && pointMap.y-pointTap.y<=50
  )
  {
   Log.v("onTap",msg.getName());
   ((Villo)ctx).showDialogForMessage(msg);
   return true;
  }

  return super.onTap(p, mapView);

 }

}

No comments:

Post a Comment