D-32
D-32

Reputation: 3255

Android Maps v2 - animate markers

I'm creating a cluster feature for maps (v2). I group location into clusters and then display the clusters as custom markers:

enter image description here

This works great, but I would like to create an animation when clusters get created and split up. I successfully did this with iOS by creating a UIView Animation on the markers (annotations). I couldn't find any code / hints online for android.

I managed to get a simple ImageView as overlay to resemble a cluster and then use a TranslateAnimation to get the desired animation. At the end I removed this view and added the marker.

Is there a better way to animate a marker?

Upvotes: 2

Views: 6185

Answers (3)

cesards
cesards

Reputation: 16319

The code below is correct, but I would puntualize something. I would use 16 ms == 60fps. The human eye can't distinguis less than 16 ms, so:

final LatLng target = NEW_LOCATION;

final long duration = 400;
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = map.getProjection();

Point startPoint = proj.toScreenLocation(marker.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);

final Interpolator interpolator = new LinearInterpolator();
handler.post(new Runnable() {
    @Override
    public void run() {
        long elapsed = SystemClock.uptimeMillis() - start;
        float t = interpolator.getInterpolation((float) elapsed / duration);
        double lng = t * target.longitude + (1 - t) * startLatLng.longitude;
        double lat = t * target.latitude + (1 - t) * startLatLng.latitude;
        marker.setPosition(new LatLng(lat, lng));
        if (t < 1.0) {
            // Post again 16ms later == 60 frames per second
            handler.postDelayed(this, 16);
        } else {
            // animation ended
        }
    }
});

Upvotes: 2

Carlton Whitehead
Carlton Whitehead

Reputation: 231

You're on the right track by animating the Marker object rather than adding and removing Views in front of the GoogleMap, but you can get better performance if you use an Animator object to animate the Marker.

With the Handler and delayed Runnable approach, you're effectively hard coding a target frame rate. If you post the Runnable with too low of a delay, your animation will take longer to execute. If too high, the frame rate will be too slow, and it'll look choppy even on powerful devices. The advantage to using an Animator over a Handler and delayed Runnable is that it will only call onAnimationUpdate() to draw the next frame as often as the system can handle.

In my clustering library, Clusterkraf, I used an ObjectAnimator (from NineOldAndroids for backwards compatibility) to animate cluster transitions when changing zoom level. It can smoothly animate around 100 markers on my Galaxy Nexus.

Below is a snippet with an overview of how to make that work.

class ClusterTransitionsAnimation implements AnimatorListener, AnimatorUpdateListener {

    private ObjectAnimator animator;
    private AnimatedTransitionState state;
    private ClusterTransitions transitions;
    private Marker[] animatedMarkers;

    void animate(ClusterTransitions transitions) {
        if (this.state == null) {
            Options options = optionsRef.get();
            Host host = hostRef.get();
            if (options != null && host != null) {
                this.state = new AnimatedTransitionState(transitions.animated);
                this.transitions = transitions;
                animator = ObjectAnimator.ofFloat(this.state, "value", 0f, 1f);
                animator.addListener(this);
                animator.addUpdateListener(this);
                animator.setDuration(options.getTransitionDuration());
                animator.setInterpolator(options.getTransitionInterpolator());
                host.onClusterTransitionStarting();
                animator.start();
            }
        }
    }

    @Override
        public void onAnimationStart(Animator animator) {
            // Add animatedMarkers to map, omitted for brevity
        }

    @Override
    public void onAnimationUpdate(ValueAnimator animator) {
        if (state != null && animatedMarkers != null) {
            LatLng[] positions = state.getPositions();
            for (int i = 0; i < animatedMarkers.length; i++) {
                animatedMarkers[i].setPosition(positions[i]);
            }
        }
    }
}

Upvotes: 8

D-32
D-32

Reputation: 3255

I found a solution that works:

final LatLng target = NEW_LOCATION;

final long duration = 400;
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = map.getProjection();

Point startPoint = proj.toScreenLocation(marker.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);

final Interpolator interpolator = new LinearInterpolator();
handler.post(new Runnable() {
    @Override
    public void run() {
        long elapsed = SystemClock.uptimeMillis() - start;
        float t = interpolator.getInterpolation((float) elapsed / duration);
        double lng = t * target.longitude + (1 - t) * startLatLng.longitude;
        double lat = t * target.latitude + (1 - t) * startLatLng.latitude;
        marker.setPosition(new LatLng(lat, lng));
        if (t < 1.0) {
            // Post again 10ms later.
            handler.postDelayed(this, 10);
        } else {
            // animation ended
        }
    }
});

It's a bit slow with about 20 simultaneously, maybe there is a better way?

Upvotes: 1

Related Questions