Thursday, January 17, 2013

Implement bouncing marker for Google Maps Android API v2

bouncing marker

Example to implement a bouncing marker:
  //Make the marker bounce
        final Handler handler = new Handler();
        
        final long startTime = SystemClock.uptimeMillis();
        final long duration = 2000;
        
        Projection proj = myMap.getProjection();
        final LatLng markerLatLng = marker.getPosition();
        Point startPoint = proj.toScreenLocation(markerLatLng);
        startPoint.offset(0, -100);
        final LatLng startLatLng = proj.fromScreenLocation(startPoint);

        final Interpolator interpolator = new BounceInterpolator();

        handler.post(new Runnable() {
            @Override
            public void run() {
                long elapsed = SystemClock.uptimeMillis() - startTime;
                float t = interpolator.getInterpolation((float) elapsed / duration);
                double lng = t * markerLatLng.longitude + (1 - t) * startLatLng.longitude;
                double lat = t * markerLatLng.latitude + (1 - t) * startLatLng.latitude;
                marker.setPosition(new LatLng(lat, lng));

                if (t < 1.0) {
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                }
            }
        });
        



The code in main activity:
package com.example.androidmapsv2;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.Projection;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.FragmentManager;
import android.graphics.Point;
import android.view.Menu;
import android.view.MenuItem;
import android.view.animation.BounceInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity 
 implements OnMapLongClickListener, OnMarkerClickListener{
 
 final int RQS_GooglePlayServices = 1;
 
 GoogleMap myMap;
 
 TextView tvLocInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvLocInfo = (TextView)findViewById(R.id.locinfo);
        
        FragmentManager myFragmentManager = getFragmentManager();
        MapFragment myMapFragment 
         = (MapFragment)myFragmentManager.findFragmentById(R.id.map);
        myMap = myMapFragment.getMap();
        
        myMap.setMyLocationEnabled(true);

        //myMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
        //myMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
        //myMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
        myMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
        
        myMap.getUiSettings().setZoomControlsEnabled(true);
        myMap.getUiSettings().setCompassEnabled(true);
        myMap.getUiSettings().setMyLocationButtonEnabled(true);
        
        myMap.getUiSettings().setRotateGesturesEnabled(true);
        myMap.getUiSettings().setScrollGesturesEnabled(true);
        myMap.getUiSettings().setTiltGesturesEnabled(true);
        myMap.getUiSettings().setZoomGesturesEnabled(true);
        //or myMap.getUiSettings().setAllGesturesEnabled(true);
        
        myMap.setTrafficEnabled(true);
        
        myMap.setOnMapLongClickListener(this);
        myMap.setOnMarkerClickListener(this);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
  case R.id.menu_legalnotices:
   String LicenseInfo = GooglePlayServicesUtil.getOpenSourceSoftwareLicenseInfo(
     getApplicationContext());
   AlertDialog.Builder LicenseDialog = new AlertDialog.Builder(MainActivity.this);
   LicenseDialog.setTitle("Legal Notices");
   LicenseDialog.setMessage(LicenseInfo);
   LicenseDialog.show();
   return true; 
  }
  return super.onOptionsItemSelected(item);
 }

 @Override
 protected void onResume() {

  super.onResume();
  
  int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext());
  
  if (resultCode == ConnectionResult.SUCCESS){
   Toast.makeText(getApplicationContext(), 
     "isGooglePlayServicesAvailable SUCCESS", 
     Toast.LENGTH_LONG).show(); 
  }else{
   GooglePlayServicesUtil.getErrorDialog(resultCode, this, RQS_GooglePlayServices); 
  }
 }

 @Override
 public void onMapLongClick(LatLng point) {
  tvLocInfo.setText("New marker added@" + point.toString());
  
  BitmapDescriptor bitmapDescriptor 
   = BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher);
  
  myMap.addMarker(new MarkerOptions()
        .position(point)
        .icon(bitmapDescriptor)
        .title(point.toString()));

 }

 @Override
 public boolean onMarkerClick(final Marker marker) {

  //Make the marker bounce
        final Handler handler = new Handler();
        
        final long startTime = SystemClock.uptimeMillis();
        final long duration = 2000;
        
        Projection proj = myMap.getProjection();
        final LatLng markerLatLng = marker.getPosition();
        Point startPoint = proj.toScreenLocation(markerLatLng);
        startPoint.offset(0, -100);
        final LatLng startLatLng = proj.fromScreenLocation(startPoint);

        final Interpolator interpolator = new BounceInterpolator();

        handler.post(new Runnable() {
            @Override
            public void run() {
                long elapsed = SystemClock.uptimeMillis() - startTime;
                float t = interpolator.getInterpolation((float) elapsed / duration);
                double lng = t * markerLatLng.longitude + (1 - t) * startLatLng.longitude;
                double lat = t * markerLatLng.latitude + (1 - t) * startLatLng.latitude;
                marker.setPosition(new LatLng(lat, lng));

                if (t < 1.0) {
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                }
            }
        });
        
        //return false; //have not consumed the event
        return true; //have consumed the event
 }
    
}


download filesDownload the files.

The series:
A simple example using Google Maps Android API v2, step by step.

4 comments:

Damián Arrillaga said...

Hi,

I think it could be better using the anchor attribute of the marker instead of the position, because the position is the thing that the marker represents.

I've done it based on your code and it works very well.

Some improvements could be that the makrer position doesn't change, and if you use the map rotation or the map zoom the bouncing effect won't be affected.

A negative thing could be if you don't know the default marker anchor, because you only can set the anchor value but reading it is not possible

Best wishes,

Damián.

Felix said...


The marker change his original position when I use the bouncing effect. The change depends of the zoom.

Someone knows how can I solve it.

Thank you.

Unknown said...

Store marker's properties somewhere and restore back after finishing animation :)

Anonymous said...

Awesome. Thanks :)