2

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?

D-32
  • 3,245
  • 2
  • 22
  • 33
  • How do you make the circles that big? Or you just gave it a big value of radius? – mr5 Jul 14 '14 at 05:38
  • It's a custom view. The more locations are in the cluster the bigger the radius. – D-32 Jul 14 '14 at 08:32
  • How do you define it's size? Are you drawing those circles within Google map? – mr5 Jul 14 '14 at 08:48
  • I'm using new MarkerOptions().position(position).icon(BitmapDescriptorFactory.fromBitmap(getBitmapForCluster(cluster)) - getBitmapForCluster(cluster) is a custom method which returns a Bitmap, in this case the circle – D-32 Jul 14 '14 at 08:55
  • I thought those were `Circles`. Uhm, one more thing, were those circles anchored centrally in their position? Also, what was the appr. size of those images? – mr5 Jul 14 '14 at 08:59
  • Yes centered. The size of the bitmap depends on how big the circle should be, and that again depends on how many locations are inside that specific cluster. – D-32 Jul 14 '14 at 09:02
  • Many thanks man, but, I still have lots of question regarding on how you implement it, could you help me more? – mr5 Jul 14 '14 at 09:08
  • Yeah sure, just send me an email info [at] d-32.com – D-32 Jul 14 '14 at 09:10
  • Thanks! btw, I have looked already in your site before you mentioned it and watched your video there. You're very good at photography, it's awesome. – mr5 Jul 14 '14 at 09:12
  • try this link http://stackoverflow.com/q/25765972/3020568 – Deniz Sep 16 '14 at 12:18

3 Answers3

8

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]);
            }
        }
    }
}
2

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
        }
    }
});
cesards
  • 15,882
  • 11
  • 70
  • 65
1

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?

Community
  • 1
  • 1
D-32
  • 3,245
  • 2
  • 22
  • 33
  • 3
    Since each change to your `Marker` involves IPC back to Play Services Framework app, I doubt that it will get any better, and it could be a lot worse. – CommonsWare Feb 01 '13 at 16:21