// Created by plusminus on 20:50:06 - 03.10.2008 package org.mozilla.osmdroid.views.overlay; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import org.mozilla.osmdroid.DefaultResourceProxyImpl; import org.mozilla.osmdroid.ResourceProxy; import org.mozilla.osmdroid.views.MapView; import org.mozilla.osmdroid.views.overlay.OverlayItem.HotspotPlace; import java.util.List; public class ItemizedOverlayWithFocus<Item extends OverlayItem> extends ItemizedIconOverlay<Item> { // =========================================================== // Constants // =========================================================== public static final int DESCRIPTION_BOX_PADDING = 3; public static final int DESCRIPTION_BOX_CORNERWIDTH = 3; public static final int DESCRIPTION_LINE_HEIGHT = 12; /** * Additional to <code>DESCRIPTION_LINE_HEIGHT</code>. */ public static final int DESCRIPTION_TITLE_EXTRA_LINE_HEIGHT = 2; // protected static final Point DEFAULTMARKER_FOCUSED_HOTSPOT = new Point(10, 19); protected static final int DEFAULTMARKER_BACKGROUNDCOLOR = Color.rgb(101, 185, 74); protected static final int DESCRIPTION_MAXWIDTH = 200; // =========================================================== // Fields // =========================================================== protected final int mMarkerFocusedBackgroundColor; protected final Paint mMarkerBackgroundPaint, mDescriptionPaint, mTitlePaint; private final Point mFocusedScreenCoords = new Point(); private final String UNKNOWN; private final Rect mRect = new Rect(); protected Drawable mMarkerFocusedBase; protected int mFocusedItemIndex; // =========================================================== // Constructors // =========================================================== protected boolean mFocusItemsOnTap; public ItemizedOverlayWithFocus(final Context ctx, final List<Item> aList, final OnItemGestureListener<Item> aOnItemTapListener) { this(aList, aOnItemTapListener, new DefaultResourceProxyImpl(ctx)); } public ItemizedOverlayWithFocus(final List<Item> aList, final OnItemGestureListener<Item> aOnItemTapListener, final ResourceProxy pResourceProxy) { this(aList, pResourceProxy.getDrawable(ResourceProxy.bitmap.marker_default), null, NOT_SET, aOnItemTapListener, pResourceProxy); } // =========================================================== // Getter & Setter // =========================================================== public ItemizedOverlayWithFocus(final List<Item> aList, final Drawable pMarker, final Drawable pMarkerFocused, final int pFocusedBackgroundColor, final OnItemGestureListener<Item> aOnItemTapListener, final ResourceProxy pResourceProxy) { super(aList, pMarker, aOnItemTapListener, pResourceProxy); UNKNOWN = mResourceProxy.getString(ResourceProxy.string.unknown); if (pMarkerFocused == null) { this.mMarkerFocusedBase = boundToHotspot( mResourceProxy.getDrawable(ResourceProxy.bitmap.marker_default_focused_base), HotspotPlace.BOTTOM_CENTER); } else this.mMarkerFocusedBase = pMarkerFocused; this.mMarkerFocusedBackgroundColor = (pFocusedBackgroundColor != NOT_SET) ? pFocusedBackgroundColor : DEFAULTMARKER_BACKGROUNDCOLOR; this.mMarkerBackgroundPaint = new Paint(); // Color is set in onDraw(...) this.mDescriptionPaint = new Paint(); this.mDescriptionPaint.setAntiAlias(true); this.mTitlePaint = new Paint(); this.mTitlePaint.setFakeBoldText(true); this.mTitlePaint.setAntiAlias(true); this.unSetFocusedItem(); } public Item getFocusedItem() { if (this.mFocusedItemIndex == NOT_SET) { return null; } return this.mItemList.get(this.mFocusedItemIndex); } public void setFocusedItem(final int pIndex) { this.mFocusedItemIndex = pIndex; } public void setFocusedItem(final Item pItem) { final int indexFound = super.mItemList.indexOf(pItem); if (indexFound < 0) { throw new IllegalArgumentException(); } this.setFocusedItem(indexFound); } public void unSetFocusedItem() { this.mFocusedItemIndex = NOT_SET; } // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== public void setFocusItemsOnTap(final boolean doit) { this.mFocusItemsOnTap = doit; } @Override protected boolean onSingleTapUpHelper(final int index, final Item item, final MapView mapView) { if (this.mFocusItemsOnTap) { this.mFocusedItemIndex = index; mapView.postInvalidate(); } return this.mOnItemGestureListener.onItemSingleTapUp(index, item); } @Override public void draw(final Canvas c, final MapView osmv, final boolean shadow) { super.draw(c, osmv, shadow); if (shadow) { return; } if (this.mFocusedItemIndex == NOT_SET) { return; } // get focused item's preferred marker & hotspot final Item focusedItem = super.mItemList.get(this.mFocusedItemIndex); Drawable markerFocusedBase = focusedItem.getMarker(OverlayItem.ITEM_STATE_FOCUSED_MASK); if (markerFocusedBase == null) { markerFocusedBase = this.mMarkerFocusedBase; } /* Calculate and set the bounds of the marker. */ osmv.getProjection().toPixels(focusedItem.getPoint(), mFocusedScreenCoords); markerFocusedBase.copyBounds(mRect); mRect.offset(mFocusedScreenCoords.x, mFocusedScreenCoords.y); /* Strings of the OverlayItem, we need. */ final String itemTitle = (focusedItem.getTitle() == null) ? UNKNOWN : focusedItem .getTitle(); final String itemDescription = (focusedItem.getSnippet() == null) ? UNKNOWN : focusedItem .getSnippet(); /* * Store the width needed for each char in the description to a float array. This is pretty * efficient. */ final float[] widths = new float[itemDescription.length()]; this.mDescriptionPaint.getTextWidths(itemDescription, widths); final StringBuilder sb = new StringBuilder(); int maxWidth = 0; int curLineWidth = 0; int lastStop = 0; int i; int lastwhitespace = 0; /* * Loop through the charwidth array and harshly insert a linebreak, when the width gets * bigger than DESCRIPTION_MAXWIDTH. */ for (i = 0; i < widths.length; i++) { if (!Character.isLetter(itemDescription.charAt(i))) { lastwhitespace = i; } final float charwidth = widths[i]; if (curLineWidth + charwidth > DESCRIPTION_MAXWIDTH) { if (lastStop == lastwhitespace) { i--; } else { i = lastwhitespace; } sb.append(itemDescription.subSequence(lastStop, i)); sb.append('\n'); lastStop = i; maxWidth = Math.max(maxWidth, curLineWidth); curLineWidth = 0; } curLineWidth += charwidth; } /* Add the last line to the rest to the buffer. */ if (i != lastStop) { final String rest = itemDescription.substring(lastStop, i); maxWidth = Math.max(maxWidth, (int) this.mDescriptionPaint.measureText(rest)); sb.append(rest); } final String[] lines = sb.toString().split("\n"); /* * The title also needs to be taken into consideration for the width calculation. */ final int titleWidth = (int) this.mDescriptionPaint.measureText(itemTitle); maxWidth = Math.max(maxWidth, titleWidth); final int descWidth = Math.min(maxWidth, DESCRIPTION_MAXWIDTH); /* Calculate the bounds of the Description box that needs to be drawn. */ final int descBoxLeft = mRect.left - descWidth / 2 - DESCRIPTION_BOX_PADDING + mRect.width() / 2; final int descBoxRight = descBoxLeft + descWidth + 2 * DESCRIPTION_BOX_PADDING; final int descBoxBottom = mRect.top; final int descBoxTop = descBoxBottom - DESCRIPTION_TITLE_EXTRA_LINE_HEIGHT - (lines.length + 1) * DESCRIPTION_LINE_HEIGHT /* +1 because of the title. */ - 2 * DESCRIPTION_BOX_PADDING; /* Twice draw a RoundRect, once in black with 1px as a small border. */ this.mMarkerBackgroundPaint.setColor(Color.BLACK); c.drawRoundRect(new RectF(descBoxLeft - 1, descBoxTop - 1, descBoxRight + 1, descBoxBottom + 1), DESCRIPTION_BOX_CORNERWIDTH, DESCRIPTION_BOX_CORNERWIDTH, this.mDescriptionPaint); this.mMarkerBackgroundPaint.setColor(this.mMarkerFocusedBackgroundColor); c.drawRoundRect(new RectF(descBoxLeft, descBoxTop, descBoxRight, descBoxBottom), DESCRIPTION_BOX_CORNERWIDTH, DESCRIPTION_BOX_CORNERWIDTH, this.mMarkerBackgroundPaint); final int descLeft = descBoxLeft + DESCRIPTION_BOX_PADDING; int descTextLineBottom = descBoxBottom - DESCRIPTION_BOX_PADDING; /* Draw all the lines of the description. */ for (int j = lines.length - 1; j >= 0; j--) { c.drawText(lines[j].trim(), descLeft, descTextLineBottom, this.mDescriptionPaint); descTextLineBottom -= DESCRIPTION_LINE_HEIGHT; } /* Draw the title. */ c.drawText(itemTitle, descLeft, descTextLineBottom - DESCRIPTION_TITLE_EXTRA_LINE_HEIGHT, this.mTitlePaint); c.drawLine(descBoxLeft, descTextLineBottom, descBoxRight, descTextLineBottom, mDescriptionPaint); /* * Finally draw the marker base. This is done in the end to make it look better. */ Overlay.drawAt(c, markerFocusedBase, mFocusedScreenCoords.x, mFocusedScreenCoords.y, false, osmv.getMapOrientation()); } // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== }