package com.marshalchen.common.uimodule.tileView.tileview;
import android.content.Context;
import android.graphics.*;
import android.view.View;
import android.view.ViewGroup;
import com.marshalchen.common.uimodule.tileView.layouts.AnchorLayout;
import com.marshalchen.common.uimodule.tileView.layouts.ZoomPanLayout;
import com.marshalchen.common.uimodule.tileView.tileview.detail.DetailLevelEventListener;
import com.marshalchen.common.uimodule.tileView.tileview.detail.DetailLevelPatternParser;
import com.marshalchen.common.uimodule.tileView.tileview.detail.DetailManager;
import com.marshalchen.common.uimodule.tileView.tileview.geom.PositionManager;
import com.marshalchen.common.uimodule.tileView.tileview.graphics.BitmapDecoder;
import com.marshalchen.common.uimodule.tileView.tileview.graphics.BitmapDecoderHttp;
import com.marshalchen.common.uimodule.tileView.tileview.hotspots.HotSpot;
import com.marshalchen.common.uimodule.tileView.tileview.hotspots.HotSpotEventListener;
import com.marshalchen.common.uimodule.tileView.tileview.hotspots.HotSpotManager;
import com.marshalchen.common.uimodule.tileView.tileview.markers.CalloutManager;
import com.marshalchen.common.uimodule.tileView.tileview.markers.MarkerEventListener;
import com.marshalchen.common.uimodule.tileView.tileview.markers.MarkerManager;
import com.marshalchen.common.uimodule.tileView.tileview.paths.DrawablePath;
import com.marshalchen.common.uimodule.tileView.tileview.paths.PathHelper;
import com.marshalchen.common.uimodule.tileView.tileview.paths.PathManager;
import com.marshalchen.common.uimodule.tileView.tileview.samples.SampleManager;
import com.marshalchen.common.uimodule.tileView.tileview.tiles.TileManager;
import com.marshalchen.common.uimodule.tileView.tileview.tiles.TileRenderListener;
import com.marshalchen.common.uimodule.tileView.tileview.tiles.selector.TileSetSelector;
import com.marshalchen.common.uimodule.tileView.tileview.tiles.selector.TileSetSelectorMinimalUpScale;
import java.util.HashSet;
import java.util.List;
/**
* The TileView widget is a subclass of ViewGroup that provides a mechanism to asynchronously display tile-based images,
* with additional functionality for 2D dragging, flinging, pinch or double-tap to zoom, adding overlaying Views (markers),
* built-in Hot Spot support, dynamic path drawing, multiple levels of detail, and support for any relative positioning or
* coordinate system.
*
* <p>A minimal implementation:</p>
*
* <pre>{@code
* TileView tileView = new TileView(this);
* tileView.setSize(3000,5000);
* tileView.addDetailLevel(1.0f, "path/to/tiles/%col%-%row%.jpg");
* }</pre>
*
* A more advanced implementation might look like:
* <pre>{@code
* TileView tileView = new TileView(this);
* tileView.setSize(3000,5000);
* tileView.addTileViewEventListener(someMapEventListener);
* tileView.defineRelativeBounds(42.379676, -71.094919, 42.346550, -71.040280);
* tileView.addDetailLevel(1.000f, "tiles/boston-1000-%col%_%row%.jpg", 256, 256);
* tileView.addDetailLevel(0.500f, "tiles/boston-500-%col%_%row%.jpg", 256, 256);
* tileView.addDetailLevel(0.250f, "tiles/boston-250-%col%_%row%.jpg", 256, 256);
* tileView.addDetailLevel(0.125f, "tiles/boston-125-%col%_%row%.jpg", 128, 128);
* tileView.addMarker(someView, 42.35848, -71.063736);
* tileView.addMarker(anotherView, 42.3665, -71.05224);
* tileView.addMarkerEventListener(someMarkerEventListener);
* }</pre>
*
*/
public class TileView extends ZoomPanLayout {
private HashSet<TileViewEventListener> tileViewEventListeners = new HashSet<TileViewEventListener>();
private DetailManager detailManager = new DetailManager();
private PositionManager positionManager = new PositionManager();
private HotSpotManager hotSpotManager = new HotSpotManager( detailManager );
private SampleManager sampleManager;
private TileManager tileManager;
private PathManager pathManager;
private MarkerManager markerManager;
private CalloutManager calloutManager;
/**
* Constructor to use when creating a TileView from code. Inflating from XML is not currently supported.
* @param context (Context) The Context the TileView is running in, through which it can access the current theme, resources, etc.
*/
public TileView( Context context ) {
super( context );
sampleManager = new SampleManager( context, detailManager );
addView( sampleManager );
tileManager = new TileManager( context, detailManager );
addView( tileManager );
pathManager = new PathManager( context, detailManager );
addView( pathManager );
markerManager = new MarkerManager( context, detailManager );
addView( markerManager );
calloutManager = new CalloutManager( context, detailManager );
addView( calloutManager );
detailManager.addDetailLevelEventListener( detailLevelEventListener );
tileManager.setTileRenderListener( renderListener );
addZoomPanListener( zoomPanListener );
addGestureListener( gestureListener );
requestRender();
}
//------------------------------------------------------------------------------------
// PUBLIC API
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
// Event Management API
//------------------------------------------------------------------------------------
/**
* Register an event listener callback object for this TileView.
* Note this is method adds a listener to an array of listeners, and does not set
* a single listener member a single listener.
* @param listener (TileViewEventListener) an implementation of the TileViewEventListener interface
*/
public void addTileViewEventListener( TileViewEventListener listener ) {
tileViewEventListeners.add( listener );
}
/**
* Removes a TileViewEventListener object from those listening to this TileView.
* @param listener (TileViewEventListener) an implementation of the TileViewEventListener interface
*/
public void removeTileViewEventListener( TileViewEventListener listener ) {
tileViewEventListeners.remove( listener );
}
//------------------------------------------------------------------------------------
// Rendering API
//------------------------------------------------------------------------------------
/**
* Request that the current tile set is re-examined and re-drawn.
* The request is added to a queue and is not guaranteed to be processed at any particular
* time, and will never be handled immediately.
*/
public void requestRender(){
tileManager.requestRender();
}
/**
* Notify the TileView that it may stop rendering tiles. The rendering thread will be
* sent an interrupt request, but no guarantee is provided when the request will be responded to.
*/
public void cancelRender() {
tileManager.cancelRender();
}
/**
* Enables or disables tile image caching (in-memory and on-disk)
* @param shouldCache (boolean) true to enable caching, false to disable it (default)
*/
public void setCacheEnabled( boolean shouldCache ) {
tileManager.setCacheEnabled( shouldCache );
}
/**
* Sets a custom class to perform the decode operation when tile bitmaps are requested.
* By default, a BitmapDecoder implementation is provided that renders bitmaps from the context's Assets,
* but alternative implementations could be used that fetch images via HTTP, or from the SD card, or resources, SVG, etc.
* This signature is identical to calling setTileDecoder and setDownsampleDecoder with the same decoder instance as the parameter.
* {@link BitmapDecoderHttp} is an example of such an implementation.
* @param decoder (BitmapDecoder) A class instance that implements BitmapDecoder, and must define a decode method, which accepts a String file name and a Context object, and returns a Bitmap
*/
public void setDecoder( BitmapDecoder decoder ) {
setTileDecoder( decoder );
setDownsampleDecoder( decoder );
}
/**
* Sets a custom class to perform the decode operation when tile bitmaps are requested for tile images only.
* By default, a BitmapDecoder implementation is provided that renders bitmaps from the context's Assets,
* but alternative implementations could be used that fetch images via HTTP, or from the SD card, or resources, SVG, etc.
* {@link BitmapDecoderHttp} is an example of such an implementation.
* @param decoder (BitmapDecoder) A class instance that implements BitmapDecoder, and must define a decode method, which accepts a String file name and a Context object, and returns a Bitmap
*/
public void setTileDecoder( BitmapDecoder decoder ) {
tileManager.setDecoder( decoder );
}
/**
* Sets a custom class to perform the decode operation when tile bitmaps are requested for downsample images only.
* By default, a BitmapDecoder implementation is provided that renders bitmaps from the context's Assets,
* but alternative implementations could be used that fetch images via HTTP, or from the SD card, or resources, SVG, etc.
* {@link BitmapDecoderHttp} is an example of such an implementation.
* @param decoder (BitmapDecoder) A class instance that implements BitmapDecoder, and must define a decode method, which accepts a String file name and a Context object, and returns a Bitmap
*/
public void setDownsampleDecoder( BitmapDecoder decoder ) {
sampleManager.setDecoder( decoder );
}
/**
* Get the {@link TileSetSelector} implementation currently used to select tile sets.
* @return TileSetSelector implementation currently in use.
*/
public TileSetSelector getTileSetSelector() {
return detailManager.getTileSetSelector();
}
/**
* Set the tile selection method, defaults to {@link TileSetSelectorMinimalUpScale}
* Implement the {@link TileSetSelector} interface to customize how tile sets are selected.
* @param selector (TileSetSelector) implementation that handles tile set selection as scale is changed.
*/
public void setTileSetSelector(TileSetSelector selector) {
detailManager.setTileSetSelector(selector);
}
/**
* Defines whether tile bitmaps should be rendered using an AlphaAnimation
* @param enabled (boolean) true if the TileView should render tiles with fade transitions
*/
public void setTransitionsEnabled( boolean enabled ) {
tileManager.setTransitionsEnabled( enabled );
}
/**
* Define the duration (in milliseconds) for each tile transition.
* @param duration (int) the duration of the transition in milliseconds.
*/
public void setTransitionDuration( int duration ) {
tileManager.setTransitionDuration( duration );
}
//------------------------------------------------------------------------------------
// Detail Level Management API
//------------------------------------------------------------------------------------
/**
* Defines the total size, in pixels, of the tile set at 100% scale.
* The TileView wills pan within it's layout dimensions, with the content (scrollable)
* size defined by this method.
* @param width (int) total width of the tiled set
* @param height (int) total height of the tiled set
*/
@Override
public void setSize( int w, int h ) {
// super (define clip area)
super.setSize( w, h );
// coordinate with other components
detailManager.setSize( w, h );
// notify manager for relative positioning
positionManager.setSize( w, h );
}
/**
* Register a tile set to be used for a particular detail level.
* Each tile set to be used must be registered using this method,
* and at least one tile set must be registered for the TileView to render any tiles.
* @param detailScale (float) scale at which the TileView should use the tiles in this set.
* @param pattern (String) string path to the location of the tile image files, to be parsed by a DetailLevelPatternParser
*/
public void addDetailLevel( float detailScale, String pattern ) {
detailManager.addDetailLevel( detailScale, pattern, null );
}
/**
* Register a tile set to be used for a particular detail level.
* Each tile set to be used must be registered using this method,
* and at least one tile set must be registered for the TileView to render any tiles.
* @param detailScale (float) scale at which the TileView should use the tiles in this set.
* @param pattern (String) string path to the location of the tile image files, to be parsed by a DetailLevelPatternParser
* @param downsample (String) string path to the location of an optional non-tiled single image file that will fill the tile view, on a z-layer below tiles
*/
public void addDetailLevel( float detailScale, String pattern, String downsample ){
detailManager.addDetailLevel( detailScale, pattern, downsample );
}
/**
* Register a tile set to be used for a particular detail level.
* Each tile set to be used must be registered using this method,
* and at least one tile set must be registered for the TileView to render any tiles.
* @param detailScale (float) scale at which the TileView should use the tiles in this set.
* @param pattern (String) string path to the location of the tile image files, to be parsed by a DetailLevelPatternParser
* @param downsample (String) string path to the location of an optional non-tiled single image file that will fill the tile view, on a z-layer below tiles
* @param tileWidth (int) size of each tiled column
* @param tileHeight (int) size of each tiled row
*/
public void addDetailLevel( float detailScale, String pattern, String downsample, int tileWidth, int tileHeight ){
detailManager.addDetailLevel( detailScale, pattern, downsample, tileWidth, tileHeight );
}
/**
* Clear all previously registered zoom levels. This method is experimental.
*/
public void resetDetailLevels(){
detailManager.resetDetailLevels();
refresh();
}
/**
* While the detail level is locked (after this method is invoked, and before unlockDetailLevel is invoked),
* the DetailLevel will not change, and the current DetailLevel will be scaled beyond the normal
* bounds. Normally, during any scale change the details manager searches for the DetailLevel with
* a registered scale closest to the defined scale. While locked, this does not occur.
*/
public void lockDetailLevel(){
detailManager.lockDetailLevel();
}
/**
* Unlocks a DetailLevel locked with lockDetailLevel
*/
public void unlockDetailLevel(){
detailManager.unlockDetailLevel();
}
/**
* pads the viewport by the number of pixels passed. e.g., setViewportPadding( 100 ) instructs the
* TileView to interpret it's actual viewport offset by 100 pixels in each direction (top, left,
* right, bottom), so more tiles will qualify for "visible" status when intersections are calculated.
* @param padding (int) the number of pixels to pad the viewport by
*/
public void setViewportPadding( int padding ) {
detailManager.setPadding( padding );
}
/**
* Define a custom parser to manage String file names representing image tiles
* @param parser (DetailLevelPatternParser) parser that returns String objects from passed pattern, column and row.
*/
public void setTileSetPatternParser( DetailLevelPatternParser parser ) {
detailManager.setDetailLevelPatternParser( parser );
}
//------------------------------------------------------------------------------------
// Positioning API
//------------------------------------------------------------------------------------
/**
* Register a set of offset points to use when calculating position within the TileView.
* Any type of coordinate system can be used (any type of lat/lng, percentile-based, etc),
* and all positioned are calculated relatively. If relative bounds are defined, position parameters
* received by TileView methods will be translated to the the appropriate pixel value.
* To remove this process, use undefineRelativeBounds
* @param left (double) the left edge of the rectangle used when calculating position
* @param top (double) the top edge of the rectangle used when calculating position
* @param right (double) the right edge of the rectangle used when calculating position
* @param bottom (double) the bottom edge of the rectangle used when calculating position
*/
public void defineRelativeBounds( double left, double top, double right, double bottom ) {
positionManager.setBounds( left, top, right, bottom );
}
/**
* Unregisters arbitrary bounds and coordinate system. After invoking this method, TileView methods that
* receive position method parameters will use pixel values, relative to the TileView's registered size (at 1.0d scale)
*/
public void undefineRelativeBounds() {
positionManager.unsetBounds();
}
/**
* Translate a relative x and y position into a Point object with x and y values populated as pixel values, relative to the size of the TileView.
* @param x (int) relative x position to be translated to absolute pixel value
* @param y (int) relative y position to be translated to absolute pixel value
* @return Point a Point object with x and y values calculated from the relative Position x and y values
*/
public Point translate( double x, double y ) {
return positionManager.translate( x, y );
}
/**
* Translate a List of relative x and y positions (double array... { x, y }
* into Point objects with x and y values populated as pixel values, relative to the size of the TileView.
* @param positions (List<double[]>) List of 2-element double arrays to be translated to Points (pixel values). The first double should represent the relative x value, the second is y
* @return List<Point> List of Point objects with x and y values calculated from the corresponding x and y values
*/
public List<Point> translate( List<double[]> positions ) {
return positionManager.translate( positions );
}
/**
* Divides a number by the current scale value, effectively flipping scaled values. This can be useful when
* determining a relative position or dimension from a real pixel value.
* @param value (double) The number to be inversely scaled.
* @return (double) The inversely scaled product.
*/
public double unscale( double value ) {
return value / getScale();
}
/**
* Scrolls (instantly) the TileView to the x and y positions provided.
* @param x (double) the relative x position to move to
* @param y (double) the relative y position to move to
*/
public void moveTo( double x, double y ) {
Point point = positionManager.translate( x, y, getScale() );
scrollToPoint( point );
requestRender();
}
/**
* Scrolls (instantly) the TileView to the x and y positions provided, then centers the viewport to the position.
* @param x (double) the relative x position to move to
* @param y (double) the relative y position to move to
*/
public void moveToAndCenter( double x, double y ) {
Point point = positionManager.translate( x, y, getScale() );
scrollToAndCenter( point );
requestRender();
}
/**
* Scrolls (with animation) the TIelView to the relative x and y positions provided.
* @param x (double) the relative x position to move to
* @param y (double) the relative y position to move to
*/
public void slideTo( double x, double y ) {
Point point = positionManager.translate( x, y, getScale() );
slideToPoint( point );
}
/**
* Scrolls (with animation) the TileView to the x and y positions provided, then centers the viewport to the position.
* @param x (double) the relative x position to move to
* @param y (double) the relative y position to move to
*/
public void slideToAndCenter( double x, double y ) {
Point point = positionManager.translate( x, y, getScale() );
slideToAndCenter( point );
}
/**
* Scales and moves TileView so that each of the passed points is visible.
* @param points (List<double[]>) List of 2-element double arrays to be translated to Points (pixel values). The first double should represent the relative x value, the second is y
*/
public void framePoints( List<double[]> points ) {
double topMost = -Integer.MAX_VALUE;
double bottomMost = Integer.MAX_VALUE;
double leftMost = Integer.MAX_VALUE;
double rightMost = -Integer.MAX_VALUE;
for( double[] coordinate : points ) {
double x = coordinate[0];
double y = coordinate[1];
if(positionManager.contains( x, y )){
topMost = Math.max( topMost, x );
bottomMost = Math.min( bottomMost, x );
leftMost = Math.min( leftMost, y );
rightMost = Math.max( rightMost, y );
}
}
Point topRight = translate( topMost, rightMost );
Point bottomLeft = translate( bottomMost, leftMost );
int width = bottomLeft.x - topRight.x;
int height = bottomLeft.y - topRight.y;
double scaleX = Math.abs( getWidth() / (double) width );
double scaleY = Math.abs( getHeight() / (double) height );
double destinationScale = Math.min( scaleX, scaleY );
double middleX = ( rightMost + leftMost ) * 0.5f;
double middleY = ( topMost + bottomMost ) * 0.5f;
moveToAndCenter( middleY, middleX );
setScaleFromCenter( destinationScale );
}
//------------------------------------------------------------------------------------
// Marker, Callout and HotSpot API
//------------------------------------------------------------------------------------
/**
* Markers added to this TileView will have anchor logic applied on the values provided here.
* E.g., setMarkerAnchorPoints(-0.5f, -1.0f) will have markers centered horizontally, positioned
* vertically to a value equal to - 1 * height.
* Note that individual markers can be assigned specific anchors - this method applies a default
* value to all markers added without specifying anchor values.
* @param anchorX (float) the x-axis position of a marker will be offset by a number equal to the width of the marker multiplied by this value
* @param anchorY (float) the y-axis position of a marker will be offset by a number equal to the height of the marker multiplied by this value
*/
public void setMarkerAnchorPoints( float anchorX, float anchorY ) {
markerManager.setAnchors( anchorX, anchorY );
}
/**
* Add a marker to the the TileView. The marker can be any View.
* No LayoutParams are required; the View will be laid out using WRAP_CONTENT for both width and height, and positioned based on the parameters
* @param view (View) View instance to be added to the TileView
* @param x (double) relative x position the View instance should be positioned at
* @param y (double) relative y position the View instance should be positioned at
* @return (View) the View instance added to the TileView
*/
public View addMarker( View view, double x, double y ) {
Point point = positionManager.translate( x, y );
return markerManager.addMarker( view, point.x, point.y );
}
/**
* Add a marker to the the TileView. The marker can be any View.
* No LayoutParams are required; the View will be laid out using WRAP_CONTENT for both width and height, and positioned based on the parameters
* @param view (View) View instance to be added to the TileView
* @param x (double) relative x position the View instance should be positioned at
* @param y (double) relative y position the View instance should be positioned at
* @param aX (float) the x-axis position of a marker will be offset by a number equal to the width of the marker multiplied by this value
* @param aY (float) the y-axis position of a marker will be offset by a number equal to the height of the marker multiplied by this value
* @return (View) the View instance added to the TileView
*/
public View addMarker( View view, double x, double y, float anchorX, float anchorY ) {
Point point = positionManager.translate( x, y );
return markerManager.addMarker( view, point.x, point.y, anchorX, anchorY );
}
/**
* Removes a marker View from the TileView's view tree.
* @param view (View) The marker View to be removed.
*/
public void removeMarker( View view ) {
markerManager.removeMarker( view );
}
/**
* Moves an existing marker to another position.
* @param view The marker View to be repositioned.
* @param x (double) relative x position the View instance should be positioned at
* @param y (double) relative y position the View instance should be positioned at
*/
public void moveMarker( View view, double x, double y ){
if( markerManager.indexOfChild( view ) > -1 ){
Point point = positionManager.translate( x, y );
ViewGroup.LayoutParams params = view.getLayoutParams();
if( params instanceof AnchorLayout.LayoutParams ) {
AnchorLayout.LayoutParams anchorLayoutParams = (AnchorLayout.LayoutParams) params;
anchorLayoutParams.x = point.x;
anchorLayoutParams.y = point.y;
view.setLayoutParams( anchorLayoutParams );
markerManager.requestLayout();
}
}
}
/**
* Scroll the TileView so that the View passed is centered in the viewport
* @param view (View) the View marker that the TileView should center on.
* @params animate (boolean) should the movement use a transition effectg
*/
public void moveToMarker( View view, boolean animate ) {
if( markerManager.indexOfChild( view ) > -1 ){
ViewGroup.LayoutParams params = view.getLayoutParams();
if( params instanceof AnchorLayout.LayoutParams ) {
AnchorLayout.LayoutParams anchorLayoutParams = (AnchorLayout.LayoutParams) params;
int scaledX = (int) ( anchorLayoutParams.x * getScale() );
int scaledY = (int) ( anchorLayoutParams.y * getScale() );
Point point = new Point( scaledX, scaledY );
if( animate ) {
slideToAndCenter( point );
} else {
scrollToAndCenter( point );
}
}
}
}
/**
* Scroll the TileView so that the View passed is centered in the viewport
* @param view (View) the View marker that the TileView should center on.
*/
public void moveToMarker( View view ) {
moveToMarker( view, false );
}
/**
* Register a MarkerEventListener. Unlike standard touch events attached to marker View's (e.g., View.OnClickListener),
* MarkerEventListeners do not consume the touch event, so will not interfere with scrolling. While the event is
* dispatched from a Tap event, it's routed though a hit detection API to trigger the listener.
* @param listener (MarkerEventListener) listener to be added to the TileView's list of MarkerEventListeners
*/
public void addMarkerEventListener( MarkerEventListener listener ) {
markerManager.addMarkerEventListener( listener );
}
/**
* Removes a MarkerEventListener from the TileView's registry.
* @param listener (MarkerEventListener) listener to be removed From the TileView's list of MarkerEventListeners
*/
public void removeMarkerEventListener( MarkerEventListener listener ) {
markerManager.removeMarkerEventListener( listener );
}
/**
* Add a callout to the the TileView. The callout can be any View.
* No LayoutParams are required; the View will be laid out using WRAP_CONTENT for both width and height, and positioned based on the parameters
* Callout views will always be positioned at the top of the view tree (at the highest z-index), and will always be removed during any touch event
* that is not consumed by the callout View.
* @param view (View) View instance to be added to the TileView's
* @param x (double) relative x position the View instance should be positioned at
* @param y (double) relative y position the View instance should be positioned at
* @return (View) the View instance added to the TileView's
*/
public View addCallout( View view, double x, double y ) {
Point point = positionManager.translate( x, y );
return calloutManager.addMarker( view, point.x, point.y );
}
/**
* Add a callout to the the TileView. The callout can be any View.
* No LayoutParams are required; the View will be laid out using WRAP_CONTENT for both width and height, and positioned based on the parameters
* Callout views will always be positioned at the top of the view tree (at the highest z-index), and will always be removed during any touch event
* that is not consumed by the callout View.
* @param view (View) View instance to be added to the TileView's
* @param x (double) relative x position the View instance should be positioned at
* @param y (double) relative y position the View instance should be positioned at
* @param aX (float) the x-axis position of a callout view will be offset by a number equal to the width of the callout view multiplied by this value
* @param aY (float) the y-axis position of a callout view will be offset by a number equal to the height of the callout view multiplied by this value
* @return (View) the View instance added to the TileView's
*/
public View addCallout( View view, double x, double y, float anchorX, float anchorY ) {
Point point = positionManager.translate( x, y );
return calloutManager.addMarker( view, point.x, point.y, anchorX, anchorY );
}
/**
* Removes a callout View from the TileView's view tree.
* @param view The callout View to be removed.
* @return (boolean) true if the view was in the view tree and was removed, false if it was not in the view tree
*/
public boolean removeCallout( View view ) {
if( calloutManager.indexOfChild( view ) > -1 ) {
calloutManager.removeView( view );
return true;
}
return false;
}
/**
* Register a HotSpot that should fire an listener when a touch event occurs that intersects that rectangle.
* The HotSpot moves and scales with the TileView.
* @param hotSpot (HotSpot) the hotspot that is tested against touch events that occur on the TileView
* @return HotSpot the hotspot created with this method
*/
public HotSpot addHotSpot( HotSpot hotSpot ){
hotSpotManager.addHotSpot( hotSpot );
return hotSpot;
}
/**
* Register a HotSpot that should fire an listener when a touch event occurs that intersects that rectangle.
* The HotSpot moves and scales with the TileView.
* @param positions (List<double[]>) List of paired doubles { x, y } that represents the points that make up the region.
* @return HotSpot the hotspot created with this method
*/
public HotSpot addHotSpot( List<double[]> positions ) {
List<Point> points = positionManager.translate( positions );
Path path = PathHelper.pathFromPoints( points );
path.close();
RectF bounds = new RectF();
path.computeBounds( bounds, true );
Rect rect = new Rect();
bounds.round( rect );
Region clip = new Region( rect );
HotSpot hotSpot = new HotSpot();
hotSpot.setPath( path, clip );
return addHotSpot( hotSpot );
}
/**
* Register a HotSpot that should fire an listener when a touch event occurs that intersects that rectangle.
* The HotSpot moves and scales with the TileView.
* @param positions (List<double[]>) List of paired doubles { x, y } that represents the points that make up the region.
* @param listener (HotSpotEventListener) listener to attach to this hotspot, which will be invoked if a Tap event is fired that intersects the hotspot's Region
* @return HotSpot the hotspot created with this method
*/
public HotSpot addHotSpot( List<double[]> positions, HotSpotEventListener listener ) {
HotSpot hotSpot = addHotSpot( positions );
hotSpot.setHotSpotEventListener( listener );
return hotSpot;
}
/**
* Remove a HotSpot registered with addHotSpot
* @param hotSpot (HotSpot) the hotspot to remove
* @return (boolean) true if a hotspot was removed, false if not
*/
public void removeHotSpot( HotSpot hotSpot ) {
hotSpotManager.removeHotSpot( hotSpot );
}
/**
* Register a HotSpotEventListener with the TileView. This listener will fire if any hotspot's region intersects a Tap event.
* @param listener (HotSpotEventListener) the listener to be added.
*/
public void addHotSpotEventListener( HotSpotEventListener listener ) {
hotSpotManager.addHotSpotEventListener( listener );
}
/**
* Remove a HotSpotEventListener from the TileView's registry.
* @param listener (HotSpotEventListener) the listener to be removed
*/
public void removeHotSpotEventListener( HotSpotEventListener listener ) {
hotSpotManager.removeHotSpotEventListener( listener );
}
//------------------------------------------------------------------------------------
// Path Drawing API
//------------------------------------------------------------------------------------
/**
* Register a Path and Paint that will be drawn on a layer above the tiles, but below markers.
* This Path's will be scaled with the TileView, but will always be as wide as the stroke set for the Paint.
* @param drawablePath (DrawablePath) a DrawablePath instance to be drawn by the TileView
* @return DrawablePath the DrawablePath instance passed to the TileView
*/
public DrawablePath drawPath( DrawablePath drawablePath ) {
return pathManager.addPath( drawablePath );
}
/**
* Register a Path and Paint that will be drawn on a layer above the tiles, but below markers.
* This Path's will be scaled with the TileView, but will always be as wide as the stroke set for the Paint.
* @param positions (List<double[]>) List of doubles { x, y } that represent the points of the Path.
* @return DrawablePath the DrawablePath instance passed to the TileView
*/
public DrawablePath drawPath( List<double[]> positions ) {
List<Point> points = positionManager.translate( positions );
return pathManager.addPath( points );
}
/**
* Register a Path and Paint that will be drawn on a layer above the tiles, but below markers.
* This Path's will be scaled with the TileView, but will always be as wide as the stroke set for the Paint.
* @param positions (List<double[]>) List of doubles { x, y } that represent the points of the Path.
* @param paint (Paint) the Paint instance that defines the style of the drawn path.
* @return DrawablePath the DrawablePath instance passed to the TileView
*/
public DrawablePath drawPath( List<double[]> positions, Paint paint ) {
List<Point> points = positionManager.translate( positions );
return pathManager.addPath( points, paint );
}
/**
* Removes a DrawablePath from the TileView's registry. This path will no longer be drawn by the TileView.
* @param drawablePath (DrawablePath) the DrawablePath instance to be removed.
*/
public void removePath( DrawablePath drawablePath ) {
pathManager.removePath( drawablePath );
}
/**
* Returns the Paint instance used by default. This can be modified for future Path paint operations.
* @return Paint the Paint instance used by default.
*/
public Paint getPathPaint(){
return pathManager.getPaint();
}
//------------------------------------------------------------------------------------
// Memory Management API
//------------------------------------------------------------------------------------
/**
* Clear bitmap image files, appropriate for Activity.onPause
*/
public void clear() {
tileManager.clear();
sampleManager.clear();
pathManager.setShouldDraw( false );
}
/**
* Clear bitmap image files, appropriate for Activity.onPause (mirror for .clear)
*/
public void pause() {
clear();
}
/**
* Clear tile image files and remove all views, appropriate for Activity.onDestroy
* References to TileView should be set to null following invocations of this method.
*/
public void destroy() {
tileManager.clear();
sampleManager.clear();
pathManager.clear();
}
/**
* Restore visible state (generally after a call to .clear()
* Appropriate for Activity.onResume
*/
public void resume(){
updateViewport();
tileManager.requestRender();
sampleManager.update();
pathManager.setShouldDraw( true );
}
/**
* Request the TileView reevaluate tile sets, rendered tiles, samples, invalidates, etc
*/
public void refresh() {
updateViewport();
tileManager.updateTileSet();
tileManager.requestRender();
sampleManager.update();
redraw();
}
//------------------------------------------------------------------------------------
// PRIVATE API
//------------------------------------------------------------------------------------
// make sure we keep the viewport UTD, and if layout changes we'll need to recompute what tiles to show
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout( changed, l, t, r, b );
updateViewport();
requestRender();
}
// let the zoom manager know what tiles to show based on our position and dimensions
private void updateViewport(){
int left = getScrollX();
int top = getScrollY();
int right = left + getWidth();
int bottom = top + getHeight();
detailManager.updateViewport( left, top, right, bottom );
}
// tell the tile renderer to not start any more tasks, but it can continue with any that are already running
private void suppressRender() {
tileManager.suppressRender();
}
//------------------------------------------------------------------------------------
// Private Listeners
//------------------------------------------------------------------------------------
private ZoomPanListener zoomPanListener = new ZoomPanListener() {
@Override
public void onZoomPanEvent(){
}
@Override
public void onScrollChanged( int x, int y ) {
updateViewport();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onScrollChanged( x, y );
}
}
@Override
public void onScaleChanged( double scale ) {
detailManager.setScale( scale );
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onScaleChanged( scale );
}
}
@Override
public void onZoomStart( double scale ) {
detailManager.lockDetailLevel();
detailManager.setScale( scale );
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onZoomStart( scale );
}
}
@Override
public void onZoomComplete( double scale ) {
detailManager.unlockDetailLevel();
detailManager.setScale( scale );
requestRender(); // put this here instead of gesture listener so we catch animations and pinches
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onZoomComplete( scale );
}
}
};
private DetailLevelEventListener detailLevelEventListener = new DetailLevelEventListener(){
@Override
public void onDetailLevelChanged() {
requestRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onDetailLevelChanged();
}
}
/*
* do *not* update scale in response to changes in the zoom manager
* transactions are one-way - set scale on TileView (ZoomPanLayout)
* and pass those to DetailManager, which then distributes, manages
* and notifies all other interested parties.
*/
@Override
public void onDetailScaleChanged( double scale ) {
}
};
private GestureListener gestureListener = new GestureListener(){
@Override
public void onDoubleTap( Point point ) {
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onDoubleTap( point.x, point.y );
}
}
@Override
public void onDrag( Point point ) {
suppressRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onDrag( point.x, point.y );
}
}
@Override
public void onFingerDown( Point point ) {
suppressRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onFingerDown( point.x, point.y );
}
}
@Override
public void onFingerUp( Point point ) {
if ( !isFlinging() ) {
requestRender();
}
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onFingerUp( point.x, point.y );
}
}
@Override
public void onFling( Point startPoint, Point finalPoint ) {
suppressRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onFling( startPoint.x, startPoint.y, finalPoint.x, finalPoint.y );
}
}
@Override
public void onFlingComplete( Point point ) {
requestRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onFlingComplete( point.x, point.y );
}
}
@Override
public void onPinch( Point point ) {
suppressRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onPinch( point.x, point.y );
}
}
@Override
public void onPinchComplete( Point point ) {
requestRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onPinchComplete( point.x, point.y );
}
}
@Override
public void onPinchStart( Point point ) {
suppressRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onPinchStart( point.x, point.y );
}
}
@Override
public void onTap( Point point ) {
markerManager.processHit( point );
hotSpotManager.processHit( point );
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onTap( point.x, point.y );
}
}
@Override
public void onScrollComplete( Point point ) {
requestRender();
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onScrollChanged( point.x, point.y );
}
}
};
private TileRenderListener renderListener = new TileRenderListener(){
@Override
public void onRenderCancelled() {
}
@Override
public void onRenderComplete() {
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onRenderComplete();
}
}
@Override
public void onRenderStart() {
for ( TileViewEventListener listener : tileViewEventListeners ) {
listener.onRenderStart();
}
}
};
//------------------------------------------------------------------------------------
// Public static interfaces and classes
//------------------------------------------------------------------------------------
/**
* Interface for implementations to receive TileView events. This interface consolidates several disparate
* listeners (Gestures, ZoomPan Events, TileView events) into a single unit for ease of use.
*/
public static interface TileViewEventListener {
/**
* Fires when a ACTION_DOWN event is raised from the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onFingerDown(int x, int y);
/**
* Fires when a ACTION_UP event is raised from the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onFingerUp(int x, int y);
/**
* Fires while the TileView is being dragged
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onDrag(int x, int y);
/**
* Fires when a user double-taps the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onDoubleTap(int x, int y);
/**
* Fires when a user taps the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onTap(int x, int y);
/**
* Fires while a user is pinching the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onPinch(int x, int y);
/**
* Fires when a user starts a pinch action
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onPinchStart(int x, int y);
/**
* Fires when a user completes a pinch action
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onPinchComplete(int x, int y);
/**
* Fires when a user initiates a fling action
* @param sx (int) the x position of the start of the fling
* @param sy (int) the y position of the start of the fling
* @param dx (int) the x position of the end of the fling
* @param dy (int) the y position of the end of the fling
*/
public void onFling(int sx, int sy, int dx, int dy);
/**
* Fires when a fling action has completed
* @param x (int) the final x scroll position of the TileView after the fling
* @param y (int) the final y scroll position of the TileView after the fling
*/
public void onFlingComplete(int x, int y);
/**
* Fires when the TileView's scale has updated
* @param scale (double) the new scale of the TileView (0-1)
*/
public void onScaleChanged(double scale);
/**
* Fires when the TileView's scroll position has updated
* @param x (int) the new x scroll position of the TileView
* @param y (int) the new y scroll position of the TileView
*/
public void onScrollChanged(int x, int y);
/**
* Fires when a zoom action starts (typically through a pinch of double-tap action,
* or by programmatic animated zoom methods.
* @param scale (double) the new scale of the TileView (0-1)
*/
public void onZoomStart(double scale);
/**
* Fires when a zoom action ends (typically through a pinch of double-tap action,
* or by programmatic animated zoom methods.
* @param scale (double) the new scale of the TileView (0-1)
*/
public void onZoomComplete(double scale);
/**
* Fires when the TileView should start using a new DetailLevel
*/
public void onDetailLevelChanged();
/**
* Fires when the rendering thread has started to update the visible tiles.
*/
public void onRenderStart();
/**
* Fires when the rendering thread has completed updating the visible tiles, but before cleanup
*/
public void onRenderComplete();
}
/**
* Convenience class that implements {@TileViewEventListener}
*/
public static class TileViewEventListenerImplementation implements TileViewEventListener {
/**
* Fires when a ACTION_DOWN event is raised from the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onFingerDown( int x, int y ) {
}
/**
* Fires when a ACTION_UP event is raised from the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onFingerUp( int x, int y ) {
}
/**
* Fires while the TileView is being dragged
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onDrag( int x, int y ) {
}
/**
* Fires when a user double-taps the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onDoubleTap( int x, int y ) {
}
/**
* Fires when a user taps the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onTap( int x, int y ) {
}
/**
* Fires while a user is pinching the TileView
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onPinch( int x, int y ) {
}
/**
* Fires when a user starts a pinch action
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onPinchStart( int x, int y ) {
}
/**
* Fires when a user completes a pinch action
* @param x (int) the x position of the event
* @param y (int) the y position of the event
*/
public void onPinchComplete( int x, int y ) {
}
/**
* Fires when a user initiates a fling action
* @param sx (int) the x position of the start of the fling
* @param sy (int) the y position of the start of the fling
* @param dx (int) the x position of the end of the fling
* @param dy (int) the y position of the end of the fling
*/
public void onFling( int sx, int sy, int dx, int dy ) {
}
/**
* Fires when a fling action has completed
* @param x (int) the final x scroll position of the TileView after the fling
* @param y (int) the final y scroll position of the TileView after the fling
*/
public void onFlingComplete( int x, int y ) {
}
/**
* Fires when the TileView's scale has updated
* @param scale (double) the new scale of the TileView (0-1)
*/
public void onScaleChanged( double scale ) {
}
/**
* Fires when the TileView's scroll position has updated
* @param x (int) the new x scroll position of the TileView
* @param y (int) the new y scroll position of the TileView
*/
public void onScrollChanged( int x, int y ) {
}
/**
* Fires when a zoom action starts (typically through a pinch of double-tap action,
* or by programmatic animated zoom methods.
* @param scale (double) the new scale of the TileView (0-1)
*/
public void onZoomStart( double scale ) {
}
/**
* Fires when a zoom action ends (typically through a pinch of double-tap action,
* or by programmatic animated zoom methods.
* @param scale (double) the new scale of the TileView (0-1)
*/
public void onZoomComplete( double scale ) {
}
/**
* Fires when the TileView should start using a new DetailLevel
* @param oldZoom (int) the zoom level the TileView was using before the change
* @param currentZoom (int) the zoom level the TileView has changed to
*/
public void onDetailLevelChanged() {
}
/**
* Fires when the rendering thread has started to update the visible tiles.
*/
public void onRenderStart() {
}
/**
* Fires when the rendering thread has completed updating the visible tiles, but before cleanup
*/
public void onRenderComplete() {
}
}
public PositionManager getPositionManager() {
return positionManager;
}
public PathManager getPathManager() {
return pathManager;
}
}