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

Animate marker #16

Closed emnl closed 10 years ago

emnl commented 11 years ago

I am trying to do something like this:

double[] newCords = newCords(); // not pixels - cords Geolocator-wise
double[] newPixels = mapView.latLngToPixels(newCords[0], newCords[1]);

ObjectAnimator animX = ObjectAnimator.ofFloat(myMarker, "x", newPixels[0]);
ObjectAnimator animY = ObjectAnimator.ofFloat(myMarker, "y", newPixels[1]);
animX.start();
animY.start();

But the MapView/TileView won't have any of it. The location of the marker will be very distorted and it will not follow zooming or moving as it should as soon as I touch it's x/y position.

Is this a bug or is there a better way to animate markers on the map/tile?

moagrius commented 11 years ago

Every layout pass, the marker container recalculates the position of each marker based on the coordinate and the current scale, so animating those properties directly is probably not going to work.

Also, I think ObjectAnimator tweens the property passed on the object, and the marker's don't have x or y properties - their LayoutParams do. You could probably tween that.

Normal translation animations don't affect the Layout of the view, just the drawing matrix, so you should be able to use those.

Finally, you could create a marker at a fixed position, but maybe add the visual elements to a child View, and animate the child view...

In the old MapViewDemo repo, I show a way to transition in Callouts, but the animation is across scale, not position - nevertheless the general approach might be helpful to see.

Does that help?

emnl commented 11 years ago

Modifying the LayoutParams of the marker works! However, the performance-hit of requestLayout() every frame makes the technique useless. Any other suggestions? I am hoping to contribute back my changes if I solve this.

moagrius commented 11 years ago

What exactly are you trying to achieve? Have the marker slide in from top-left? Or just top? From 0,0, or just a limited distance (like 100px from where ever it's supposed to end up)? I can probably come up with an example if you can give me exact specs...

In general, I think you might be looking for a TranslateAnimation, which doesn't actually affect layout, just where it's drawn.

emnl commented 11 years ago

The marker is moving around between points set by the geolocator. A translation animation did not work out when I used it, it was hard to sync with the coordinates. And the object animator for the value "x" and "y" does not work either, as said above. I want to move the marker between points (20, 10) -> (-10, 40) -> (x, y) ... The problem is just moving from (x1, y1) to (x2, y2). Not timing nor continuous movement.

moagrius commented 11 years ago

If you actually want to move it from one point to another (and not just fake it, as is done with a TranslateAnimation, or manipulating the canvas in onDraw), then you have to update the layout. That's just how elements are placed on the screen.

As far as normal positioning - remember that (generally) position methods accept x, y values, which are relative to the dimensions in setSize. See the wikis on Positioning and Anchors, and my comments in this thread

Hopefully that makes sense, and is helpful - I still can't say I completely understand the issue you're experiencing, but maybe the info above makes that unnecessary.

emnl commented 11 years ago

This is what I puzzled together, still doesn't work. Any suggestions?

double xCord = ??;
double yCord = ??;
Point point = mapView.translate(xCord, yCord);
ObjectAnimator animX = ObjectAnimator.ofFloat(this, "x", point.x);
ObjectAnimator animY = ObjectAnimator.ofFloat(this, "y", point.y);
moagrius commented 11 years ago

So you want to animate the marker from 0,0 (top left) to some position on the TileView - right?

emnl commented 11 years ago

Let's say from (40, 50) to some position. (40, 50) is the current position of the marker, where it was addMarker:ed.

moagrius commented 11 years ago

OK - I'll write up an example (maybe a couple) of how I'd do it, and post back later tonight or tomorrow

emnl commented 11 years ago

Ok, cool! I hope it gets included in the wiki if anyone else want to try it.

moagrius commented 11 years ago

@emnl, Here's one way, using classes already available in the package:

tileView.addMarkerEventListener( new MarkerEventListener() {

    @Override
    public void onMarkerTap( final View view, int x, int y ) {

        final AnchorLayout.LayoutParams lp = (AnchorLayout.LayoutParams) view.getLayoutParams();

        final int oldX = lp.x;
        final int oldY = lp.y;

        int destX = 0;
        int destY = 0;

        final int deltaX = destX - oldX;
        final int deltaY = destY - oldY;

        Tween tween = new Tween();
        tween.setDuration( 500 );

        tween.addTweenListener( new TweenListener() {
            @Override
            public void onTweenStart() {

            }
            @Override
            public void onTweenProgress( double progress, double eased ) {
                lp.x = (int) ( oldX + ( deltaX * eased ) );
                lp.y = (int) ( oldY + ( deltaY * eased ) );
                view.setLayoutParams( lp );
            }
            @Override
            public void onTweenComplete() {

            }
        });
        tween.start();
    } 
} );

I didn't do any hardcore optimizations or anything (e.g., those listeners should probably be discreet classes), but it should be pretty easy to follow what's going on. I've tested this on a device and it works as expected.

you can set the destination location to whatever you want, just update destX and destY (which are currently set to 0, or top left).

Remember that the destination values should be relative values - so if you setSize(2000,2000) and want to be halfway across (regardless of current scale), the x and y values would be 1000.

HTH, LMK if this is about what you were looking for.

emnl commented 11 years ago

Back to square one. I had a similar solution previously. But as I said, it's unusable due to the requestlayout on every setLayoutParams (in onTweenProgress). Also, your reply does not consider the problem of just having coordinates to go on. I use:

Point point = tileView.translate(cordX, cordY);
int destX = point.x;
int destY = point.y;

But I'm unsure if it's the right method.

This was a tricky problem indeed.

moagrius commented 11 years ago

If you want to update a View's layout, you have to do so in this fashion - the only other way is to "fake" the visual effect by drawing with an offset or using a translation matrix, neither of which will work if you want actually want to update the layout, and neither of which are appropriate for this. You want to move the position of the marker - which is done with LayoutParams (and implicit calls to requestLayout).

I'm not sure how you plan on updating a marker's position (which is updating it's layout), without layout methods...? That's just how it's done in android - to update layout, you have to update layout : )

I did not notice any performance issues in the demo I posted, btw. It uses a handler, so it'll only update as often as is appropriate in the UI thread.

For the second part (re: translate), yes that's correct.

emnl commented 11 years ago

My application have ~70 markers, with 1 moving (animating). It renders the application completely unusable. My tests was performed using a high-end 2013 mobile phone as well as a high-end 2013 tablet. "faking" it of course leaves the application running smoothly but with the result of markers running wild. It's a shame that there is no way of faking it while keeping it working with TileView. It was a fairly easy job with CATiledLayer.

Animation might merit a warning in the wiki if you manage to reproduce my performance issue.

moagrius commented 11 years ago

I think what you're describing is an issue with the Android framework generally, and not specific or limited to TileView. Even the most stripped down, highly optimized Android app of this type will never compete with an iOS version in terms of performance - iOS is hardware + software, and natively accelerated. It might come close with a lot of work, but having worked with both frameworks, I think it's safe to say almost everything is easier in iOS (IMHO).

That said, if TranslateAnimation (and/or drawing methods) did work for you, but you had trouble just syncing coordinates, you can certainly run the math backwards.

To get the "real" pixel a marker is at, get the x and y properties from the LayoutParams, and apply the current scale.

Does that help?

moagrius commented 11 years ago

@emnl did this get settled? can i close the issue, or do you need more help? i'm happy to point you to any part of the code you think might be useful...

tekblom commented 11 years ago

Hi! I have taken over the issue with the animation and I am still interested in the issue!

I will take a deeper look in to the problem later this week. Would be great with some directions, but I need to get back later when I've looked a bit deeper in to it.

Thanks

moagrius commented 10 years ago

Sounds good - I'll leave it open for now then.

moagrius commented 10 years ago

it's been a while since last activity on this thread - i'm going to go ahead and close it, but just post back if you still need help or guidance and i'll re-open