diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java index bbc2443d..ad943df2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java @@ -64,6 +64,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { mBinding = DataBindingUtil.setContentView(this, R.layout.activity_create_world); mToolTipsManager = new ToolTipsManager(); Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.CREATE_WORLD_OPEN); + mBinding.scroll.post(() -> mBinding.scroll.scrollTo(0, 0)); } public void onClickPositiveButton(View view) { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 3ed55173..298c34d3 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.Context; import android.content.DialogInterface; +import android.content.res.Configuration; import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; @@ -212,7 +213,7 @@ private void moveCameraToPlayer(View view) { Snackbar.make(mBinding.tileView, getString(R.string.something_at_xyz_dim_float, getString(R.string.player), - playerPos.x, playerPos.y, playerPos.z, playerPos.dimension.name), + playerPos.x, playerPos.y, playerPos.z), Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); @@ -251,7 +252,7 @@ private void moveCameraToSpawn(View view) { Snackbar.make(mBinding.tileView, getString(R.string.something_at_xyz_dim_int, getString(R.string.spawn), - spawnPos.x, spawnPos.y, spawnPos.z, spawnPos.dimension.name), + spawnPos.x, spawnPos.y, spawnPos.z), Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); @@ -287,14 +288,31 @@ private void closeFloatPane() { .newInstance(mBinding.selectionBoard.getSelection(), this::doSelectionBasedEdit); trans.add(R.id.float_window_container, fragment); - setUpSelectionMenu(); mFloatingFragment = fragment; + setUpSelectionMenu(); } else mFloatingFragment = null; trans.commit(); } } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (mFloatingFragment != null) { + FloatPaneFragment fragment; + if (mFloatingFragment instanceof AdvancedLocatorFragment) { + fragment = AdvancedLocatorFragment.create(world, this::frameTo); + } else if (mFloatingFragment instanceof SelectionMenuFragment) { + fragment = SelectionMenuFragment + .newInstance(mBinding.selectionBoard.getSelection(), this::doSelectionBasedEdit); + } else return; + closeFloatPane(); + openFloatPane(fragment); + setUpSelectionMenu(); + } + } + /** * Set up selection menu and connect it with selection board. * @@ -517,20 +535,14 @@ Create tile(=bitmap) provider tileView.getMarkerLayout().setMarkerTapListener(new MarkerLayout.MarkerTapListener() { @Override public void onMarkerTap(View view, int tapX, int tapY) { - if (!(view instanceof MarkerImageView)) { - Log.d(this, "Markertaplistener found a marker that is not a MarkerImageView! " + view.toString()); - return; - } + if (!(view instanceof MarkerImageView)) return; final AbstractMarker marker = ((MarkerImageView) view).getMarkerHook(); - if (marker == null) { - Log.d(this, "abstract marker is null! " + view.toString()); - return; - } + if (marker == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder - .setTitle(String.format(getString(R.string.marker_info), marker.getNamedBitmapProvider().getBitmapDisplayName(), marker.getNamedBitmapProvider().getBitmapDataName(), marker.x, marker.y, marker.z, marker.dimension)) + .setTitle(String.format(getString(R.string.marker_info), marker.getNamedBitmapProvider().getBitmapDisplayName(), marker.x, marker.y, marker.z)) .setItems(getMarkerTapOptions(), new DialogInterface.OnClickListener() { @SuppressWarnings("RedundantCast") public void onClick(DialogInterface dialog, int which) { @@ -848,7 +860,7 @@ private void onLongPressed(@NotNull MotionEvent event) { } AlertDialog alertDialog = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.AppTheme_Floating)) - .setTitle(getString(R.string.postion_2D_floats_with_chunkpos, worldX, worldZ, chunkXint, chunkZint, dim.name)) + .setTitle(getString(R.string.postion_2D_floats_with_chunkpos, worldX, worldZ, chunkXint, chunkZint)) .setItems(getLongClickOptions(), (dialog, which) -> { @@ -874,6 +886,8 @@ private void onLongPressed(@NotNull MotionEvent event) { .setCancelable(true) .setNegativeButton(android.R.string.cancel, null) .show(); + alertDialog.getListView().post(() -> + alertDialog.getListView().smoothScrollToPositionFromTop(4, 40)); Window window = alertDialog.getWindow(); if (window != null) { window.setBackgroundDrawable(getResources().getDrawable(R.drawable.bg_dialog_transparent)); @@ -1069,7 +1083,7 @@ private void onChooseTeleportPlayer(float worldX, float worldZ, Dimension dim, V (int) newX, (int) newY, (int) newZ, dim); Snackbar.make(container, - String.format(getString(R.string.teleported_player_to_xyz_dim), newX, newY, newZ, dim.name), + getString(R.string.teleported_player_to_xyz_dim, newX, newY, newZ), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } else { @@ -1430,8 +1444,7 @@ public void onClick(DialogInterface dialog, int whichButton) { playerKey, playerPos.x, playerPos.y, - playerPos.z, - playerPos.dimension.name), + playerPos.z), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapTileView.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapTileView.java index 977aa521..80446d7f 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapTileView.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapTileView.java @@ -61,6 +61,16 @@ public boolean onTouchEvent(MotionEvent event) { return false; } + @Override + public boolean onSingleTapConfirmed(MotionEvent event) { + if (super.onSingleTapConfirmed(event)) return true; + if (mOnLongPressListener != null) { + mOnLongPressListener.onLongPressed(event); + return true; + } + return false; + } + /** * Sets the long press callback. * diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/edit/SearchAndReplaceFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/edit/SearchAndReplaceFragment.java index 9ba6528c..a1467186 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/edit/SearchAndReplaceFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/edit/SearchAndReplaceFragment.java @@ -1,6 +1,7 @@ package com.mithrilmania.blocktopograph.map.edit; import android.app.Dialog; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; @@ -25,6 +26,7 @@ import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; @@ -101,7 +103,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c mBinding.ok.setOnClickListener(this::onClickOk); mBinding.help.setOnClickListener(this::onClickHelpMain); mToolTipsManager = new ToolTipsManager(); - return mBinding.getRoot(); + View root = mBinding.getRoot(); + Dialog dialog = getDialog(); + if (dialog instanceof AlertDialog) { + ((AlertDialog) dialog).setView(root); + } + mBinding.scroll.post(() -> mBinding.scroll.scrollTo(0, 0)); + return root; } private void onClickHelpMain(@NotNull View view) { @@ -257,8 +265,11 @@ private void onClickOk(View view) { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.setTitle(R.string.map_edit_func_snr); + Context context = requireContext(); + AlertDialog dialog = new AlertDialog.Builder(context) + //.setView(onCreateView(LayoutInflater.from(context), null, savedInstanceState)) + .setTitle(R.string.map_edit_func_snr) + .create(); return dialog; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index 37a2de8b..3253a6b8 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -6,7 +6,6 @@ import android.graphics.Rect; import com.mithrilmania.blocktopograph.Log; - import com.mithrilmania.blocktopograph.WorldData; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.Version; @@ -30,6 +29,7 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int for (x = 0, tX = pX; x < 16; x++, tX += pW) { y = chunk.getHeightMapValue(x, z); + if (y == 0) continue; color = getColumnColour(chunk, x, y, z, (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionMenuFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionMenuFragment.java index 0f6c4818..7d7a72a2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionMenuFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionMenuFragment.java @@ -12,6 +12,7 @@ import android.widget.EditText; import android.widget.Toast; +import com.github.florent37.expansionpanel.ExpansionLayout; import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.R; import com.mithrilmania.blocktopograph.databinding.FragSelMenuBinding; @@ -62,9 +63,17 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c mBinding.content.applyButton.setOnClickListener(this::onApply); mBinding.content.funcLampshade.setOnClickListener(this::onChooseLampshade); mBinding.content.funcSnr.setOnClickListener(this::onChooseSnr); + mBinding.expansionLayout.post(() -> mBinding.expansionLayout.scrollTo(0, 0)); + if (mBinding.scroll != null) + mBinding.expansionLayout.addListener(this::onExpansionChanged); return mBinding.getRoot(); } + private void onExpansionChanged(ExpansionLayout layout, boolean expanded) { + mBinding.scroll.setScrollY(mBinding.scroll.getChildAt(0).getMeasuredHeight()); + mBinding.scroll.post(() -> mBinding.scroll.smoothScrollTo(0, 0)); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionView.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionView.java index 41a85d3c..bd5a09e5 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionView.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/selection/SelectionView.java @@ -30,8 +30,6 @@ public class SelectionView extends FrameLayout { - public static final int MIN_DIST_DRAGGERS = 50; - public static final int HALF_MIN_DIST_DRAGGERS = MIN_DIST_DRAGGERS / 2; /** * The view being dragged now. */ @@ -115,6 +113,19 @@ public class SelectionView extends FrameLayout { */ private float BUTTON_TO_BOUND_MIN_DIST; + /** + * The minimal non-auto-scroll distance from touched point to screen boundary. + * + *
+ * Once it's exceed the tileView would be scrolled automatically. + *
+ */ + private int MIN_DIST_TO_SCREEN_BOUND; + + private int MIN_DIST_DRAGGERS; + + private int HALF_MIN_DIST_DRAGGERS; + /** * Runnable used to frequently alter selection while user dragging a adjust button. */ @@ -160,6 +171,9 @@ private void init(Context context) { mPaint.setColor(Color.argb(0x80, 0, 0, 0)); setWillNotDraw(false);// Otherwise `onDraw` won't be called. BUTTON_TO_BOUND_MIN_DIST = UiUtil.dpToPx(context, 72); + MIN_DIST_TO_SCREEN_BOUND = UiUtil.dpToPxInt(context, 100); + MIN_DIST_DRAGGERS = UiUtil.dpToPxInt(context, 50); + HALF_MIN_DIST_DRAGGERS = MIN_DIST_DRAGGERS / 2; mDragger = null; } @@ -385,15 +399,35 @@ private void onMove() { if (mSelectionChangedListener != null) mSelectionChangedListener.onSelectionChanged(mSelectionRect); - // Move the tileView as well. + // Should we move the underlying tileView as well? + // If touched point is near the moving direction (not the dragger position) + // then we scroll. + + int sw = getMeasuredWidth(); + int sh = getMeasuredHeight(); + int minw = Math.max(MIN_DIST_TO_SCREEN_BOUND, sw / 8); + int minh = Math.max(MIN_DIST_TO_SCREEN_BOUND, sh / 8); + switch (draggerId) { case R.id.left: case R.id.right: - tileView.setScrollX((int) (tileView.getScrollX() + movement)); + // (Moving right and near right bound) or + // (Moving left and near left bound) + if (mDragDirection > 0 && sw - mDragCurrentPos < minw + || (mDragDirection < 0 && mDragCurrentPos < minw) + ) + tileView.setScrollX((int) (tileView.getScrollX() + movement)); + else + requestLayout(); break; case R.id.top: case R.id.bottom: - tileView.setScrollY((int) (tileView.getScrollY() + movement)); + if (mDragDirection > 0 && sh - mDragCurrentPos < minh + || (mDragDirection < 0 && mDragCurrentPos < minh) + ) + tileView.setScrollY((int) (tileView.getScrollY() + movement)); + else + requestLayout(); break; } } diff --git a/app/src/main/res/layout/activity_create_world.xml b/app/src/main/res/layout/activity_create_world.xml index 35affb4e..466d7ccd 100644 --- a/app/src/main/res/layout/activity_create_world.xml +++ b/app/src/main/res/layout/activity_create_world.xml @@ -10,10 +10,10 @@ android:padding="6dp">* A minimal implementation might look like this: * *
{@code @@ -49,7 +49,7 @@ * tileView.setSize( 3000, 5000 ); * tileView.addDetailLevel( 1.0f, "path/to/tiles/%d-%d.jpg" ); * }- * + *
* A more advanced implementation might look like: * *
{@code @@ -66,935 +66,934 @@ * }*/ public class TileView extends ZoomPanLayout implements - ZoomPanLayout.ZoomPanListener, - TileCanvasViewGroup.TileRenderListener, - DetailLevelManager.DetailLevelChangeListener { - - protected static final int DEFAULT_TILE_SIZE = 256; - - private DetailLevelManager mDetailLevelManager = new DetailLevelManager(); - private CoordinateTranslater mCoordinateTranslater = new CoordinateTranslater(); - private HotSpotManager mHotSpotManager = new HotSpotManager(); - - private TileCanvasViewGroup mTileCanvasViewGroup; - private CompositePathView mCompositePathView; - private ScalingLayout mScalingLayout; - private MarkerLayout mMarkerLayout; - private CalloutLayout mCalloutLayout; - - private RenderThrottleHandler mRenderThrottleHandler; - - private boolean mShouldRenderWhilePanning = false; - private boolean mShouldUpdateDetailLevelWhileZooming = false; - - /** - * Constructor to use when creating a TileView from code. - * - * @param context The Context the TileView is running in, through which it can access the current theme, resources, etc. - */ - public TileView( Context context ) { - this( context, null ); - } - - public TileView( Context context, AttributeSet attrs ) { - this( context, attrs, 0 ); - } - - public TileView( Context context, AttributeSet attrs, int defStyleAttr ) { - - super( context, attrs, defStyleAttr ); - - mTileCanvasViewGroup = new TileCanvasViewGroup( context ); - addView( mTileCanvasViewGroup ); - - mCompositePathView = new CompositePathView( context ); - addView( mCompositePathView ); - - mScalingLayout = new ScalingLayout( context ); - addView( mScalingLayout ); - - mMarkerLayout = new MarkerLayout( context ); - addView( mMarkerLayout ); - - mCalloutLayout = new CalloutLayout( context ); - addView( mCalloutLayout ); - - mDetailLevelManager.setDetailLevelChangeListener( this ); - mTileCanvasViewGroup.setTileRenderListener( this ); - addZoomPanListener( this ); - - mRenderThrottleHandler = new RenderThrottleHandler( this ); - - requestRender(); - - } - - /** - * Returns the DetailLevelManager instance used by the TileView to coordinate DetailLevels. - * - * @return The DetailLevelManager instance. - */ - public DetailLevelManager getDetailLevelManager() { - return mDetailLevelManager; - } - - /** - * Returns the CoordinateTranslater instance used by the TileView to manage abritrary coordinate - * systems. - * - * @return The CoordinateTranslater instance. - */ - public CoordinateTranslater getCoordinateTranslater() { - return mCoordinateTranslater; - } - - /** - * Returns the HotSpotManager instance used by the TileView to detect and react to touch events - * that intersect a user-defined region. - * - * @return The HotSpotManager instance. - */ - public HotSpotManager getHotSpotManager() { - return mHotSpotManager; - } - - /** - * Returns the CompositePathView instance used by the TileView to draw and scale paths. - * - * @return The CompositePathView instance. - */ - public CompositePathView getCompositePathView() { - return mCompositePathView; - } - - /** - * Returns the TileCanvasViewGroup instance used by the TileView to manage tile bitmap rendering. - * - * @return The TileCanvasViewGroup instance. - */ - public TileCanvasViewGroup getTileCanvasViewGroup() { - return mTileCanvasViewGroup; - } - - /** - * Returns the MakerLayout instance used by the TileView to position and display Views used - * as markers. - * - * @return The MarkerLayout instance. - */ - public MarkerLayout getMarkerLayout() { - return mMarkerLayout; - } - - /** - * Returns the CalloutLayout instance used by the TileView to position and display Views used - * as callouts. - * - * @return The CalloutLayout instance. - */ - public CalloutLayout getCalloutLayout() { - return mCalloutLayout; - } - - /** - * Returns the ScalingLayout instance used by the TileView to allow insertion of arbitrary - * Views and ViewGroups that will scale visually with the TileView. - * - * @return The ScalingLayout instance. - */ - public ScalingLayout getScalingLayout() { - return mScalingLayout; - } - - /** - * Add a ViewGroup to the TileView at a z-index above tiles and paths but beneath - * markers and callouts. The ViewGroup will be laid out to the full dimensions of the largest - * detail level, and will scale with the TileView. - * Note that only the drawing surface of the view is scaled, other operations that depend - * on dimensions are not (e.g., hit areas, invalidation tests). - * - * @param viewGroup The ViewGroup to be added to the TileView, that will scale visually. - */ - public void addScalingViewGroup( ViewGroup viewGroup ) { - mScalingLayout.addView( viewGroup ); - } - - /** - * 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() { - mTileCanvasViewGroup.requestRender(); - } - - /** - * While all render operation requests are queued and batched, this method provides an additional - * throttle layer, so that any subsequent invocations cancel and pending invocations. - * - * This is useful when requesting in a stream fashion, either in a loop or in response to a - * progressive action like an animation or touch move. - */ - public void requestThrottledRender() { - mRenderThrottleHandler.submit(); - } - - /** - * If flinging, defer render, otherwise request now. - * If a render operation starts at the beginning of a fling, a stutter can occur. - */ - protected void requestSafeRender() { - if( isFlinging() ) { - requestThrottledRender(); - } else { - 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() { - mTileCanvasViewGroup.cancelRender(); - } - - /** - * Notify the TileView that it should continue to render any pending tiles, but should not - * accept new render tasks. - */ - public void suppressRender() { - mTileCanvasViewGroup.suppressRender(); - } - - /** - * Notify the TileView that it should resume tiles rendering. - */ - public void resumeRender() { - mTileCanvasViewGroup.resumeRender(); - } - - /** - * Sets a custom class to perform the getBitmap 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. - * - * @param bitmapProvider A class instance that implements BitmapProvider, and must define a getBitmap method, which accepts a String file name and a Context object, and returns a Bitmap - */ - public void setBitmapProvider( BitmapProvider bitmapProvider ) { - mTileCanvasViewGroup.setBitmapProvider( bitmapProvider ); - } - - /** - * Defines whether tile bitmaps should be rendered using an AlphaAnimation - * - * @param enabled True if the TileView should render tiles with fade transitions - */ - public void setTransitionsEnabled( boolean enabled ) { - mTileCanvasViewGroup.setTransitionsEnabled( enabled ); - } - - /** - * Instructs Tile instances to recycle (or not). This can be useful if using a caching system - * that re-uses bitmaps and expects them to not have been recycled. - * - * The default value is true. - * - * @deprecated This value is no longer considered - bitmaps are always recycled when they're no longer used. - * @param shouldRecycleBitmaps True if bitmaps should call Bitmap.recycle when they are removed from view. - */ - public void setShouldRecycleBitmaps( boolean shouldRecycleBitmaps ) { - mTileCanvasViewGroup.setShouldRecycleBitmaps( shouldRecycleBitmaps ); - } - - /** - * 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 Total width of the tiled set. - * @param height Total height of the tiled set. - */ - @Override - public void setSize( int width, int height ) { - super.setSize( width, height ); - mDetailLevelManager.setSize( width, height ); - mCoordinateTranslater.setSize( width, height ); - } - - /** - * 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 Scale at which the TileView should use the tiles in this set. - * @param data An arbitrary object of any type that is passed to the BitmapProvider for each tile on this level. - */ - public void addDetailLevel( float detailScale, Object data ) { - addDetailLevel( detailScale, data, DEFAULT_TILE_SIZE, DEFAULT_TILE_SIZE ); - } - - /** - * 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 Scale at which the TileView should use the tiles in this set. - * @param data An arbitrary object of any type that is passed to the (Adapter|Decoder) for each tile on this level. - * @param tileWidth Size of each tiled column. - * @param tileHeight Size of each tiled row. - */ - public void addDetailLevel( float detailScale, Object data, int tileWidth, int tileHeight ) { - mDetailLevelManager.addDetailLevel( detailScale, data, tileWidth, tileHeight ); - } - - /** - * 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 Scale at which the TileView should use the tiles in this set. - * @param data An arbitrary object of any type that is passed to the (Adapter|Decoder) for each tile on this level. - * @param tileWidth Size of each tiled column. - * @param tileHeight Size of each tiled row. - * @param levelType Type of level, detail-levels can have the same scale but different looks. - */ - public void addDetailLevel(float detailScale, Object data, int tileWidth, int tileHeight, DetailLevelManager.LevelType levelType ) { - mDetailLevelManager.addDetailLevel( detailScale, data, tileWidth, tileHeight, levelType ); - } - - /** - * 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 The number of pixels to pad the viewport by - */ - public void setViewportPadding( int padding ) { - mDetailLevelManager.setViewportPadding( padding ); - } - - /** - * 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 undefineBounds. - * - * @param left The left edge of the rectangle used when calculating position. - * @param top The top edge of the rectangle used when calculating position. - * @param right The right edge of the rectangle used when calculating position. - * @param bottom The bottom edge of the rectangle used when calculating position. - */ - public void defineBounds( double left, double top, double right, double bottom ) { - mCoordinateTranslater.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.0f scale). - */ - public void undefineBounds() { - mCoordinateTranslater.unsetBounds(); - } - - /** - * Scrolls (instantly) the TileView to the x and y positions provided. The is an overload - * of scrollTo( int x, int y ) that accepts doubles; if the TileView has relative bounds defined, - * those relative doubles will be converted to absolute pixel positions. - * - * @param x The relative x position to move to. - * @param y The relative y position to move to. - */ - public void scrollTo( double x, double y ) { - scrollTo( - mCoordinateTranslater.translateAndScaleX( x, getScale() ), - mCoordinateTranslater.translateAndScaleY( y, getScale() ) - ); - } - - /** - * Scrolls (instantly) the TileView to the x and y positions provided, - * then centers the viewport to the position. - * - * @param x The relative x position to move to. - * @param y The relative y position to move to. - */ - public void scrollToAndCenter( double x, double y ) { - scrollToAndCenter( - mCoordinateTranslater.translateAndScaleX( x, getScale() ), - mCoordinateTranslater.translateAndScaleY( y, getScale() ) - ); - } - - /** - * Scrolls (with animation) the TileView to the relative x and y positions provided. - * - * @param x The relative x position to move to. - * @param y The relative y position to move to. - */ - public void slideTo( double x, double y ) { - slideTo( - mCoordinateTranslater.translateAndScaleX( x, getScale() ), - mCoordinateTranslater.translateAndScaleY( y, getScale() ) - ); - } - - /** - * Scrolls (with animation) the TileView to the x and y positions provided, - * then centers the viewport to the position. - * - * @param x The relative x position to move to. - * @param y The relative y position to move to. - */ - public void slideToAndCenter( double x, double y ) { - slideToAndCenter( - mCoordinateTranslater.translateAndScaleX( x, getScale() ), - mCoordinateTranslater.translateAndScaleY( y, getScale() ) - ); - } - - /** - * Scrolls and scales (with animation) the TileView to the specified x, y and scale provided. - * The TileView will be centered to the coordinates passed. - * - * @param x The relative x position to move to. - * @param y The relative y position to move to. - * @param scale The scale the TileView should be at when the animation is complete. - */ - public void slideToAndCenterWithScale( double x, double y, float scale ) { - slideToAndCenterWithScale( - mCoordinateTranslater.translateAndScaleX( x, scale ), - mCoordinateTranslater.translateAndScaleY( y, scale ), - scale - ); - } - - /** - * 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, and aligned - * along the bottom edge to the y value supplied. - * - * Anchor values assigned to individual markers will override these default values. - * - * @param anchorX 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 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 ) { - mMarkerLayout.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 instance to be added to the TileView. - * @param x Relative x position the View instance should be positioned at. - * @param y Relative y position the View instance should be positioned at. - * @param anchorX 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 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 The View instance added to the TileView. - */ - public View addMarker( View view, double x, double y, Float anchorX, Float anchorY ) { - return mMarkerLayout.addMarker( view, - mCoordinateTranslater.translateX( x ), - mCoordinateTranslater.translateY( y ), - anchorX, anchorY - ); - } - - /** - * Removes a marker View from the TileView's view tree. - * - * @param view The marker View to be removed. - */ - public void removeMarker( View view ) { - mMarkerLayout.removeMarker( view ); - } - - /** - * Moves an existing marker to another position. - * - * @param view The marker View to be repositioned. - * @param x Relative x position the View instance should be positioned at. - * @param y Relative y position the View instance should be positioned at. - */ - public void moveMarker( View view, double x, double y ) { - mMarkerLayout.moveMarker( view, - mCoordinateTranslater.translateX( x ), - mCoordinateTranslater.translateY( y ) ); - } - - /** - * Scroll the TileView so that the View passed is centered in the viewport. - * - * @param view The View marker that the TileView should center on. - * @param shouldAnimate True if the movement should use a transition effect. - */ - public void moveToMarker( View view, boolean shouldAnimate ) { - if( mMarkerLayout.indexOfChild( view ) == -1 ) { - throw new IllegalStateException( "The view passed is not an existing marker" ); - } - ViewGroup.LayoutParams params = view.getLayoutParams(); - if( params instanceof MarkerLayout.LayoutParams ) { - MarkerLayout.LayoutParams anchorLayoutParams = (MarkerLayout.LayoutParams) params; - int scaledX = FloatMathHelper.scale( anchorLayoutParams.x, getScale() ); - int scaledY = FloatMathHelper.scale( anchorLayoutParams.y, getScale() ); - if( shouldAnimate ) { - slideToAndCenter( scaledX, scaledY ); - } else { - scrollToAndCenter( scaledX, scaledY ); - } - } - } - - /** - * Register a MarkerTapListener for the TileView instance (rather than on a single marker view). - * Unlike standard touch events attached to marker View's (e.g., View.OnClickListener), - * MarkerTapListener.onMarkerTapEvent does not consume the touch event, so will not interfere - * with scrolling. - * - * @param markerTapListener Listener to be added to the TileView's list of MarkerTapListener. - */ - public void setMarkerTapListener( MarkerLayout.MarkerTapListener markerTapListener ) { - mMarkerLayout.setMarkerTapListener( markerTapListener ); - } - - /** - * 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 according to the x and y values supplied. - * 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 instance to be added to the TileView. - * @param x Relative x position the View instance should be positioned at. - * @param y Relative y position the View instance should be positioned at. - * @param anchorX 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 anchorY 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 The View instance added to the TileView. - */ - public View addCallout( View view, double x, double y, Float anchorX, Float anchorY ) { - return mCalloutLayout.addMarker( view, - mCoordinateTranslater.translateX( x ), - mCoordinateTranslater.translateY( y ), - anchorX, anchorY - ); - } - - /** - * Removes a callout View from the TileView. - * - * @param view The callout View to be removed. - */ - public void removeCallout( View view ) { - mCalloutLayout.removeMarker( view ); - } - - /** - * Register a HotSpot that should fire a listener when a touch event occurs that intersects the - * Region defined by the HotSpot. - * - * The HotSpot virtually moves and scales with the TileView. - * - * @param hotSpot The hotspot that is tested against touch events that occur on the TileView. - * @return The HotSpot instance added. - */ - public HotSpot addHotSpot( HotSpot hotSpot ) { - mHotSpotManager.addHotSpot( hotSpot ); - return hotSpot; - } - - /** - * Register a HotSpot that should fire a listener when a touch event occurs that intersects the - * Region defined by the HotSpot. - * - * The HotSpot virtually moves and scales with the TileView. - * - * @param positions (List
- * However, a zoom out may require a lot of tiles of the locked {@code DetailLevel} to be rendered. - * In worst case, it can cause {@link OutOfMemoryError}. - * Then, disabling the {@code DetailLevel} lock is a bandage to that issue. Using - * {@code setShouldUpdateDetailLevelWhileZooming( true )} is not advised unless you have that issue. - *
- * - * @param shouldUpdate True if it should lock {@link DetailLevel} when a zoom begins. - */ - public void setShouldUpdateDetailLevelWhileZooming( boolean shouldUpdate ) { - mShouldUpdateDetailLevelWhileZooming = shouldUpdate; - } - - /** - * Allows the use of a custom {@link DetailLevelManager}. - *- * For example, to change the logic of {@link DetailLevel} choice for a given scale, you - * declare your own {@code DetailLevelMangerCustom} that extends {@link DetailLevelManager} : - *
{@code - * private class DetailLevelManagerCustom extends DetailLevelManager{ - * @literal @Override - * public DetailLevel getDetailLevelForScale(){ - * // your logic here - * } - * } - * } - *- * Then you should use {@code TileView.setDetailLevelManager} before other method calls, especially - * {@code TileView.setSize} and {@code TileView.addDetailLevel}. - * - * - * @param manager The DetailLevelManager instance used. - */ - public void setDetailLevelManager( DetailLevelManager manager ) { - mDetailLevelManager = manager; - mDetailLevelManager.setDetailLevelChangeListener(this); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mCalloutLayout.removeAllViews(); - return super.onTouchEvent( event ); - } - - @Override - protected void onLayout( boolean changed, int l, int t, int r, int b ) { - super.onLayout( changed, l, t, r, b ); - updateViewport(); - requestRender(); - } - - protected void updateViewport() { - int left = getScrollX(); - int top = getScrollY(); - int right = left + getWidth(); - int bottom = top + getHeight(); - mDetailLevelManager.updateViewport( left, top, right, bottom ); - } - - @Override - protected void onScrollChanged( int l, int t, int oldl, int oldt ) { - super.onScrollChanged( l, t, oldl, oldt ); - updateViewport(); - if( mShouldRenderWhilePanning ) { - requestRender(); - } else { - requestThrottledRender(); - } - } - - @Override - public void onScaleChanged( float scale, float previous ) { - super.onScaleChanged( scale, previous ); - mDetailLevelManager.setScale( scale ); - mHotSpotManager.setScale( scale ); - mTileCanvasViewGroup.setScale( scale ); - mScalingLayout.setScale( scale ); - mCompositePathView.setScale( scale ); - mMarkerLayout.setScale( scale ); - mCalloutLayout.setScale( scale ); - } - - @Override - public void onPanBegin( int x, int y, Origination origin ) { - - } - - @Override - public void onPanUpdate( int x, int y, Origination origin ) { - - } - - @Override - public void onPanEnd( int x, int y, Origination origin ) { - requestRender(); - } - - @Override - public void onZoomBegin( float scale, Origination origin ) { - if ( origin == null ) { - mTileCanvasViewGroup.suppressRender(); - } - mDetailLevelManager.setScale( scale ); - } - - @Override - public void onZoomUpdate( float scale, Origination origin ) { - - } - - @Override - public void onZoomEnd( float scale, Origination origin ) { - if ( origin == null ) { - mTileCanvasViewGroup.resumeRender(); - } - mDetailLevelManager.setScale( scale ); - requestRender(); - } - - @Override - public void onDetailLevelChanged( DetailLevel detailLevel ) { - requestRender(); - mTileCanvasViewGroup.updateTileSet( detailLevel ); - } - - @Override - public boolean onSingleTapConfirmed( MotionEvent event ) { - int x = getScrollX() + (int) event.getX() - getOffsetX(); - int y = getScrollY() + (int) event.getY() - getOffsetY(); - mMarkerLayout.processHit( x, y ); - mHotSpotManager.processHit( x, y ); - return super.onSingleTapConfirmed( event ); - } - - @Override - public void onRenderStart() { + ZoomPanLayout.ZoomPanListener, + TileCanvasViewGroup.TileRenderListener, + DetailLevelManager.DetailLevelChangeListener { + + protected static final int DEFAULT_TILE_SIZE = 256; + + private DetailLevelManager mDetailLevelManager = new DetailLevelManager(); + private CoordinateTranslater mCoordinateTranslater = new CoordinateTranslater(); + private HotSpotManager mHotSpotManager = new HotSpotManager(); + + private TileCanvasViewGroup mTileCanvasViewGroup; + private CompositePathView mCompositePathView; + private ScalingLayout mScalingLayout; + private MarkerLayout mMarkerLayout; + private CalloutLayout mCalloutLayout; + + private RenderThrottleHandler mRenderThrottleHandler; + + private boolean mShouldRenderWhilePanning = false; + private boolean mShouldUpdateDetailLevelWhileZooming = false; + + /** + * Constructor to use when creating a TileView from code. + * + * @param context The Context the TileView is running in, through which it can access the current theme, resources, etc. + */ + public TileView(Context context) { + this(context, null); + } + + public TileView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TileView(Context context, AttributeSet attrs, int defStyleAttr) { + + super(context, attrs, defStyleAttr); + + mTileCanvasViewGroup = new TileCanvasViewGroup(context); + addView(mTileCanvasViewGroup); + + mCompositePathView = new CompositePathView(context); + addView(mCompositePathView); + + mScalingLayout = new ScalingLayout(context); + addView(mScalingLayout); + + mMarkerLayout = new MarkerLayout(context); + addView(mMarkerLayout); + + mCalloutLayout = new CalloutLayout(context); + addView(mCalloutLayout); + + mDetailLevelManager.setDetailLevelChangeListener(this); + mTileCanvasViewGroup.setTileRenderListener(this); + addZoomPanListener(this); + + mRenderThrottleHandler = new RenderThrottleHandler(this); + + requestRender(); + + } + + /** + * Returns the DetailLevelManager instance used by the TileView to coordinate DetailLevels. + * + * @return The DetailLevelManager instance. + */ + public DetailLevelManager getDetailLevelManager() { + return mDetailLevelManager; + } + + /** + * Allows the use of a custom {@link DetailLevelManager}. + *
+ * For example, to change the logic of {@link DetailLevel} choice for a given scale, you + * declare your own {@code DetailLevelMangerCustom} that extends {@link DetailLevelManager} : + *
{@code + * private class DetailLevelManagerCustom extends DetailLevelManager{ + * @literal @Override + * public DetailLevel getDetailLevelForScale(){ + * // your logic here + * } + * } + * } + *+ * Then you should use {@code TileView.setDetailLevelManager} before other method calls, especially + * {@code TileView.setSize} and {@code TileView.addDetailLevel}. + * + * + * @param manager The DetailLevelManager instance used. + */ + public void setDetailLevelManager(DetailLevelManager manager) { + mDetailLevelManager = manager; + mDetailLevelManager.setDetailLevelChangeListener(this); + } - } + /** + * Returns the CoordinateTranslater instance used by the TileView to manage abritrary coordinate + * systems. + * + * @return The CoordinateTranslater instance. + */ + public CoordinateTranslater getCoordinateTranslater() { + return mCoordinateTranslater; + } + + /** + * Returns the HotSpotManager instance used by the TileView to detect and react to touch events + * that intersect a user-defined region. + * + * @return The HotSpotManager instance. + */ + public HotSpotManager getHotSpotManager() { + return mHotSpotManager; + } - @Override - public void onRenderCancelled() { + /** + * Returns the CompositePathView instance used by the TileView to draw and scale paths. + * + * @return The CompositePathView instance. + */ + public CompositePathView getCompositePathView() { + return mCompositePathView; + } - } + /** + * Returns the TileCanvasViewGroup instance used by the TileView to manage tile bitmap rendering. + * + * @return The TileCanvasViewGroup instance. + */ + public TileCanvasViewGroup getTileCanvasViewGroup() { + return mTileCanvasViewGroup; + } - @Override - public void onRenderComplete() { + /** + * Returns the MakerLayout instance used by the TileView to position and display Views used + * as markers. + * + * @return The MarkerLayout instance. + */ + public MarkerLayout getMarkerLayout() { + return mMarkerLayout; + } - } + /** + * Returns the CalloutLayout instance used by the TileView to position and display Views used + * as callouts. + * + * @return The CalloutLayout instance. + */ + public CalloutLayout getCalloutLayout() { + return mCalloutLayout; + } + + /** + * Returns the ScalingLayout instance used by the TileView to allow insertion of arbitrary + * Views and ViewGroups that will scale visually with the TileView. + * + * @return The ScalingLayout instance. + */ + public ScalingLayout getScalingLayout() { + return mScalingLayout; + } + + /** + * Add a ViewGroup to the TileView at a z-index above tiles and paths but beneath + * markers and callouts. The ViewGroup will be laid out to the full dimensions of the largest + * detail level, and will scale with the TileView. + * Note that only the drawing surface of the view is scaled, other operations that depend + * on dimensions are not (e.g., hit areas, invalidation tests). + * + * @param viewGroup The ViewGroup to be added to the TileView, that will scale visually. + */ + public void addScalingViewGroup(ViewGroup viewGroup) { + mScalingLayout.addView(viewGroup); + } + + /** + * 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() { + mTileCanvasViewGroup.requestRender(); + } + + /** + * While all render operation requests are queued and batched, this method provides an additional + * throttle layer, so that any subsequent invocations cancel and pending invocations. + *
+ * This is useful when requesting in a stream fashion, either in a loop or in response to a
+ * progressive action like an animation or touch move.
+ */
+ public void requestThrottledRender() {
+ mRenderThrottleHandler.submit();
+ }
+
+ /**
+ * If flinging, defer render, otherwise request now.
+ * If a render operation starts at the beginning of a fling, a stutter can occur.
+ */
+ protected void requestSafeRender() {
+ if (isFlinging()) {
+ requestThrottledRender();
+ } else {
+ 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() {
+ mTileCanvasViewGroup.cancelRender();
+ }
+
+ /**
+ * Notify the TileView that it should continue to render any pending tiles, but should not
+ * accept new render tasks.
+ */
+ public void suppressRender() {
+ mTileCanvasViewGroup.suppressRender();
+ }
- private static class RenderThrottleHandler extends Handler {
+ /**
+ * Notify the TileView that it should resume tiles rendering.
+ */
+ public void resumeRender() {
+ mTileCanvasViewGroup.resumeRender();
+ }
- private static final int MESSAGE = 0;
- private static final int RENDER_THROTTLE_TIMEOUT = 100;
+ /**
+ * Sets a custom class to perform the getBitmap 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.
+ *
+ * @param bitmapProvider A class instance that implements BitmapProvider, and must define a getBitmap method, which accepts a String file name and a Context object, and returns a Bitmap
+ */
+ public void setBitmapProvider(BitmapProvider bitmapProvider) {
+ mTileCanvasViewGroup.setBitmapProvider(bitmapProvider);
+ }
- private final WeakReference
+ * The default value is true.
+ *
+ * @param shouldRecycleBitmaps True if bitmaps should call Bitmap.recycle when they are removed from view.
+ * @deprecated This value is no longer considered - bitmaps are always recycled when they're no longer used.
+ */
+ public void setShouldRecycleBitmaps(boolean shouldRecycleBitmaps) {
+ mTileCanvasViewGroup.setShouldRecycleBitmaps(shouldRecycleBitmaps);
}
+ /**
+ * 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 Total width of the tiled set.
+ * @param height Total height of the tiled set.
+ */
@Override
- public void handleMessage( Message msg ) {
- TileView tileView = mTileViewWeakReference.get();
- if( tileView != null ) {
- tileView.requestSafeRender();
- }
+ public void setSize(int width, int height) {
+ super.setSize(width, height);
+ mDetailLevelManager.setSize(width, height);
+ mCoordinateTranslater.setSize(width, height);
+ }
+
+ /**
+ * 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 Scale at which the TileView should use the tiles in this set.
+ * @param data An arbitrary object of any type that is passed to the BitmapProvider for each tile on this level.
+ */
+ public void addDetailLevel(float detailScale, Object data) {
+ addDetailLevel(detailScale, data, DEFAULT_TILE_SIZE, DEFAULT_TILE_SIZE);
+ }
+
+ /**
+ * 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 Scale at which the TileView should use the tiles in this set.
+ * @param data An arbitrary object of any type that is passed to the (Adapter|Decoder) for each tile on this level.
+ * @param tileWidth Size of each tiled column.
+ * @param tileHeight Size of each tiled row.
+ */
+ public void addDetailLevel(float detailScale, Object data, int tileWidth, int tileHeight) {
+ mDetailLevelManager.addDetailLevel(detailScale, data, tileWidth, tileHeight);
+ }
+
+ /**
+ * 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 Scale at which the TileView should use the tiles in this set.
+ * @param data An arbitrary object of any type that is passed to the (Adapter|Decoder) for each tile on this level.
+ * @param tileWidth Size of each tiled column.
+ * @param tileHeight Size of each tiled row.
+ * @param levelType Type of level, detail-levels can have the same scale but different looks.
+ */
+ public void addDetailLevel(float detailScale, Object data, int tileWidth, int tileHeight, DetailLevelManager.LevelType levelType) {
+ mDetailLevelManager.addDetailLevel(detailScale, data, tileWidth, tileHeight, levelType);
+ }
+
+ /**
+ * 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 The number of pixels to pad the viewport by
+ */
+ public void setViewportPadding(int padding) {
+ mDetailLevelManager.setViewportPadding(padding);
+ }
+
+ /**
+ * 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 undefineBounds.
+ *
+ * @param left The left edge of the rectangle used when calculating position.
+ * @param top The top edge of the rectangle used when calculating position.
+ * @param right The right edge of the rectangle used when calculating position.
+ * @param bottom The bottom edge of the rectangle used when calculating position.
+ */
+ public void defineBounds(double left, double top, double right, double bottom) {
+ mCoordinateTranslater.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.0f scale).
+ */
+ public void undefineBounds() {
+ mCoordinateTranslater.unsetBounds();
+ }
+
+ /**
+ * Scrolls (instantly) the TileView to the x and y positions provided. The is an overload
+ * of scrollTo( int x, int y ) that accepts doubles; if the TileView has relative bounds defined,
+ * those relative doubles will be converted to absolute pixel positions.
+ *
+ * @param x The relative x position to move to.
+ * @param y The relative y position to move to.
+ */
+ public void scrollTo(double x, double y) {
+ scrollTo(
+ mCoordinateTranslater.translateAndScaleX(x, getScale()),
+ mCoordinateTranslater.translateAndScaleY(y, getScale())
+ );
+ }
+
+ /**
+ * Scrolls (instantly) the TileView to the x and y positions provided,
+ * then centers the viewport to the position.
+ *
+ * @param x The relative x position to move to.
+ * @param y The relative y position to move to.
+ */
+ public void scrollToAndCenter(double x, double y) {
+ scrollToAndCenter(
+ mCoordinateTranslater.translateAndScaleX(x, getScale()),
+ mCoordinateTranslater.translateAndScaleY(y, getScale())
+ );
+ }
+
+ /**
+ * Scrolls (with animation) the TileView to the relative x and y positions provided.
+ *
+ * @param x The relative x position to move to.
+ * @param y The relative y position to move to.
+ */
+ public void slideTo(double x, double y) {
+ slideTo(
+ mCoordinateTranslater.translateAndScaleX(x, getScale()),
+ mCoordinateTranslater.translateAndScaleY(y, getScale())
+ );
}
- public void clear() {
- if( hasMessages( MESSAGE ) ) {
- removeMessages( MESSAGE );
- }
+ /**
+ * Scrolls (with animation) the TileView to the x and y positions provided,
+ * then centers the viewport to the position.
+ *
+ * @param x The relative x position to move to.
+ * @param y The relative y position to move to.
+ */
+ public void slideToAndCenter(double x, double y) {
+ slideToAndCenter(
+ mCoordinateTranslater.translateAndScaleX(x, getScale()),
+ mCoordinateTranslater.translateAndScaleY(y, getScale())
+ );
}
- public void submit() {
- clear();
- sendEmptyMessageDelayed( MESSAGE, RENDER_THROTTLE_TIMEOUT );
+ /**
+ * Scrolls and scales (with animation) the TileView to the specified x, y and scale provided.
+ * The TileView will be centered to the coordinates passed.
+ *
+ * @param x The relative x position to move to.
+ * @param y The relative y position to move to.
+ * @param scale The scale the TileView should be at when the animation is complete.
+ */
+ public void slideToAndCenterWithScale(double x, double y, float scale) {
+ slideToAndCenterWithScale(
+ mCoordinateTranslater.translateAndScaleX(x, scale),
+ mCoordinateTranslater.translateAndScaleY(y, scale),
+ scale
+ );
}
- }
- /**
- * Object used to keep some data when a configuration change happens and the activity is
- * re-created.
- * It's boiler-plate but this is how to save View state.
- */
- private static class SavedState extends BaseSavedState {
- /* This will store the current scale and position */
- float mScale;
- int mSavedCenterX;
- int mSavedCenterY;
+ /**
+ * 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, and aligned
+ * along the bottom edge to the y value supplied.
+ *
+ * Anchor values assigned to individual markers will override these default values.
+ *
+ * @param anchorX 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 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) {
+ mMarkerLayout.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 instance to be added to the TileView.
+ * @param x Relative x position the View instance should be positioned at.
+ * @param y Relative y position the View instance should be positioned at.
+ * @param anchorX 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 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 The View instance added to the TileView.
+ */
+ public View addMarker(View view, double x, double y, Float anchorX, Float anchorY) {
+ return mMarkerLayout.addMarker(view,
+ mCoordinateTranslater.translateX(x),
+ mCoordinateTranslater.translateY(y),
+ anchorX, anchorY
+ );
+ }
+
+ /**
+ * Removes a marker View from the TileView's view tree.
+ *
+ * @param view The marker View to be removed.
+ */
+ public void removeMarker(View view) {
+ mMarkerLayout.removeMarker(view);
+ }
+
+ /**
+ * Moves an existing marker to another position.
+ *
+ * @param view The marker View to be repositioned.
+ * @param x Relative x position the View instance should be positioned at.
+ * @param y Relative y position the View instance should be positioned at.
+ */
+ public void moveMarker(View view, double x, double y) {
+ mMarkerLayout.moveMarker(view,
+ mCoordinateTranslater.translateX(x),
+ mCoordinateTranslater.translateY(y));
+ }
+
+ /**
+ * Scroll the TileView so that the View passed is centered in the viewport.
+ *
+ * @param view The View marker that the TileView should center on.
+ * @param shouldAnimate True if the movement should use a transition effect.
+ */
+ public void moveToMarker(View view, boolean shouldAnimate) {
+ if (mMarkerLayout.indexOfChild(view) == -1) {
+ throw new IllegalStateException("The view passed is not an existing marker");
+ }
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ if (params instanceof MarkerLayout.LayoutParams) {
+ MarkerLayout.LayoutParams anchorLayoutParams = (MarkerLayout.LayoutParams) params;
+ int scaledX = FloatMathHelper.scale(anchorLayoutParams.x, getScale());
+ int scaledY = FloatMathHelper.scale(anchorLayoutParams.y, getScale());
+ if (shouldAnimate) {
+ slideToAndCenter(scaledX, scaledY);
+ } else {
+ scrollToAndCenter(scaledX, scaledY);
+ }
+ }
+ }
+
+ /**
+ * Register a MarkerTapListener for the TileView instance (rather than on a single marker view).
+ * Unlike standard touch events attached to marker View's (e.g., View.OnClickListener),
+ * MarkerTapListener.onMarkerTapEvent does not consume the touch event, so will not interfere
+ * with scrolling.
+ *
+ * @param markerTapListener Listener to be added to the TileView's list of MarkerTapListener.
+ */
+ public void setMarkerTapListener(MarkerLayout.MarkerTapListener markerTapListener) {
+ mMarkerLayout.setMarkerTapListener(markerTapListener);
+ }
+
+ /**
+ * 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 according to the x and y values supplied.
+ * 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 instance to be added to the TileView.
+ * @param x Relative x position the View instance should be positioned at.
+ * @param y Relative y position the View instance should be positioned at.
+ * @param anchorX 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 anchorY 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 The View instance added to the TileView.
+ */
+ public View addCallout(View view, double x, double y, Float anchorX, Float anchorY) {
+ return mCalloutLayout.addMarker(view,
+ mCoordinateTranslater.translateX(x),
+ mCoordinateTranslater.translateY(y),
+ anchorX, anchorY
+ );
+ }
+
+ /**
+ * Removes a callout View from the TileView.
+ *
+ * @param view The callout View to be removed.
+ */
+ public void removeCallout(View view) {
+ mCalloutLayout.removeMarker(view);
+ }
+
+ /**
+ * Register a HotSpot that should fire a listener when a touch event occurs that intersects the
+ * Region defined by the HotSpot.
+ *
+ * The HotSpot virtually moves and scales with the TileView.
+ *
+ * @param hotSpot The hotspot that is tested against touch events that occur on the TileView.
+ * @return The HotSpot instance added.
+ */
+ public HotSpot addHotSpot(HotSpot hotSpot) {
+ mHotSpotManager.addHotSpot(hotSpot);
+ return hotSpot;
+ }
+
+ /**
+ * Register a HotSpot that should fire a listener when a touch event occurs that intersects the
+ * Region defined by the HotSpot.
+ *
+ * The HotSpot virtually moves and scales with the TileView.
+ *
+ * @param positions (List
+ * However, a zoom out may require a lot of tiles of the locked {@code DetailLevel} to be rendered.
+ * In worst case, it can cause {@link OutOfMemoryError}.
+ * Then, disabling the {@code DetailLevel} lock is a bandage to that issue. Using
+ * {@code setShouldUpdateDetailLevelWhileZooming( true )} is not advised unless you have that issue.
+ *
* Children of ZoomPanLayout are laid out to the sizes provided by setSize,
* and will always be positioned at 0,0.
*/
public class ZoomPanLayout extends ViewGroup implements
- GestureDetector.OnGestureListener,
- GestureDetector.OnDoubleTapListener,
- ScaleGestureDetector.OnScaleGestureListener,
- TouchUpGestureDetector.OnTouchUpListener {
-
- private static final int DEFAULT_ZOOM_PAN_ANIMATION_DURATION = 400;
-
- private int mBaseWidth;
- private int mBaseHeight;
- private int mScaledWidth;
- private int mScaledHeight;
-
- private float mScale = 1;
-
- private float mMinScale = 0;
- private float mMaxScale = 1;
-
- private int mOffsetX;
- private int mOffsetY;
-
- private float mEffectiveMinScale = 0;
- private boolean mShouldLoopScale = true;
-
- private boolean mIsFlinging;
- private boolean mIsDragging;
- private boolean mIsScaling;
- private boolean mIsSliding;
-
- private int mAnimationDuration = DEFAULT_ZOOM_PAN_ANIMATION_DURATION;
-
- private HashSet