moagrius / TileView

TileView is a subclass of android.view.ViewGroup that asynchronously displays, pans and zooms tile-based images. Plugins are available for features like markers, hotspots, and path drawing.
MIT License
1.46k stars 337 forks source link

Highlight an area that maintains its size while zooming #54

Closed nverbeek closed 10 years ago

nverbeek commented 10 years ago

This may be a dumb question, but I haven't been able to achieve this yet. I'd like to highlight an area on the map, and have that area scale differently than the markers. For example, in the TileViewDemo application - say I want to draw a rectangle around the dining room table in the Building Plans layout. I can add a marker to draw the area, but when I zoom in, the area will shrink and no longer surround the entire table.

If I'm not making sense, you can replace the onCreate code in BuildingPlansTileViewActivity with the following code:

    super.onCreate( savedInstanceState );

    // multiple references
    TileView tileView = getTileView();

    // size of original image at 100% scale
    tileView.setSize( 2736, 2880 );

    // detail levels
    tileView.addDetailLevel( 1.000f, "tiles/plans/1000/%col%_%row%.jpg", "samples/plans.JPG");
    tileView.addDetailLevel( 0.500f, "tiles/plans/500/%col%_%row%.jpg", "samples/plans.JPG");
    tileView.addDetailLevel( 0.250f, "tiles/plans/250/%col%_%row%.jpg", "samples/plans.JPG");
    tileView.addDetailLevel( 0.125f, "tiles/plans/125/%col%_%row%.jpg", "samples/plans.JPG");

    ImageView areaPin = new ImageView(this);
    int RED_FADE = Color.argb(100, 209, 50, 57);
    areaPin.setBackgroundColor(RED_FADE);
    areaPin.setMinimumWidth(40);
    areaPin.setMinimumHeight(100);
    tileView.addMarker(areaPin, 1200, 1225, -0.5f, -0.5f);

    // scale it down to manageable size
    tileView.setScale( 0 );

    // center the frame
    frameTo( 0.5, 0.5 );

This will surround the chairs in the living room with an area, but then when I zoom it shrinks (as a marker should). I want that area to keep its borders around both chairs as I zoom in. I'd like to be able to preserve the current marker functionality, but also be able to draw a highlighted area like this. Can this be done? I thought maybe some use of HotSpots could work, but those have no support for drawing.

Thoughts? Thanks in advance.

moagrius commented 10 years ago

It's possible. There are a couple ways you could do it. The easiest would probably to be add your own layer, a new ScalingLayout, sync the scale to the TileView, and add Views to it as you have above. The following is off the top of my head and is not complete, but should get you started.

  1. Make a new ScalingLayout class that sync it's scale to the TileView
public class HighlightLayer extends ScalingLayout implements DetailLevelEventListener {

    public HighlightLayer ( Context context, DetailManager detailManager ) {
        super( context );
        detailManager.addDetailLevelEventListener( this );
    }

    @Override
    public void onDetailLevelChanged() {

    }

    @Override
    public void onDetailScaleChanged( double scale ) {
        setScale( scale );
    }

}
  1. Add it as a new layer to the TileView
HighlightManager highlightManager = new HighlightManager(this);
tileView.addView(highlightManager);
  1. Add Views to it...
ImageView areaPin = new ImageView(this);
int RED_FADE = Color.argb(100, 209, 50, 57);
areaPin.setBackgroundColor(RED_FADE);
areaPin.setMinimumWidth(40);
areaPin.setMinimumHeight(100);
ScalingLayout.LayoutParams lp = new ScalingLayout.LayoutParams(40, 100, 1200, 1225);
highlightManager.addView(areaPin, lp);

You could also achieve what you're trying to do with a combination of a custom path drawing implementation and a hotspot.

Last thing - if you're using the TileViewDemo as the source, it does not have the latest TileView lib - grab the latest jar from the releases page.

HTH, post back if you need more help, or the idea above won't work for you.

nverbeek commented 10 years ago

Thanks so much for responding! Could you elaborate a little more on what I'd need to do inside the Highlight Manager? I have the above implemented, but it results in the area moving around when zoomed. I think this is the right path to go down, I'm just not sure how to proceed. Any details you can provide would be helpful. Thanks!

moagrius commented 10 years ago

I ended up doing it a little differently, so you didn't have to hack the core (and can use the jar and keep up to date with the latest release).

I ran a quick test and it works.

The idea is the same, but we get there a little differently.

All this happens in the Activity containing the TileView instance.

A. Create a reference to a ScalingLayout:

private ScalingLayout highlightManager;

B. Create a listener to pass the scale of the TileView to the ScalingLayout

private TileView.TileViewEventListenerImplementation listener = new TileView.TileViewEventListenerImplementation(){
    @Override
    public void onScaleChanged( double scale ){
        highlightManager.setScale( scale );
    }
};

C. in onCreate, set them up:

// add the listener
tileView.addTileViewEventListener( listener );

// instantiate the ScalingLayout and add it to the view tree
highlightManager = new ScalingLayout(this);      
tileView.addView(highlightManager);

// draw some highlights
ImageView areaPin = new ImageView(this);
int RED_FADE = Color.argb(100, 209, 50, 57);
areaPin.setBackgroundColor(RED_FADE);
areaPin.setMinimumWidth(40);
areaPin.setMinimumHeight(100);
ScalingLayout.LayoutParams lp = new ScalingLayout.LayoutParams(40, 100, 1200, 1225);
highlightManager.addView(areaPin, lp);

That should do it. The dimensions are relative to the map at it's full size.

LMK if that's what you were looking for.

nverbeek commented 10 years ago

Got it! That is what I was looking for. I also added a HotSpot with the same dimensions to gather click events on it, since I don't think there's another way to do that with this implementation.

One further question.. is there any way to make this layer draw beneath the markers?

moagrius commented 10 years ago

I also added a HotSpot with the same dimensions to gather click events on it, since I don't think there's another way to do that with this implementation.

You can add a plain-ol' View.OnClickListener to the ImageView, instead of using a HotSpot (but that will consume TouchEvents, so for example if you touched the highlighted area it wouldn't initiate a drag action - this might be OK or might not, depending on the exact nature of your setup).

is there any way to make this layer draw beneath the markers?

Honestly I don't remember how I did the addView overrides, since all children are routed through a "proxy" view, but you can _try_ to use addView(child, index), so instead of tileView.addView(highlightManager); use tileView.addView(highlightManager, 2); (which, in theory, should put it above the tiles but below paths and markers).

LMK how it goes

nverbeek commented 10 years ago

As for the layering, using tileView.addView(highlightManager, 2); worked exactly as expected. I think I have everything I need for that part now.

You can add a plain-ol' View.OnClickListener to the ImageView, instead of using a HotSpot (but that will consume TouchEvents, so for example if you touched the highlighted area it wouldn't initiate a drag action - this might be OK or might not, depending on the exact nature of your setup).

As for the clicking, I actually tried to add a plain View.OnClickListener to the ImageView and for some reason it wouldn't fire, that's why I resorted to the HotSpot. I'll try it again just in case, otherwise I think the HotSpot will work.

Thanks again for the help.

moagrius commented 10 years ago

NP, sounds good. Close the issue when you've got everything settled.