You can use for your task your approach based on custom marker animation: animate separately car movement and car turns throughout all direction points. For this You need 2 kinds of animation:
1) animation for car movement;
2) animation for car turn;
which calls each other on its end (car movement animation on end calls car turn animation and vice versa: car turn animation on its end calls car movement animation and so for all points of car path).
For example on fig.:
1) animation for car movement from P0
to P1
;
2) animation for car turn on P1
;
3) animation for car movement from P1
to P2
and so on.
Car movement animation can be implemented by method like this:
private void animateCarMove(final Marker marker, final LatLng beginLatLng, final LatLng endLatLng, final long duration) {
final Handler handler = new Handler();
final long startTime = SystemClock.uptimeMillis();
final Interpolator interpolator = new LinearInterpolator();
// set car bearing for current part of path
float angleDeg = (float)(180 * getAngle(beginLatLng, endLatLng) / Math.PI);
Matrix matrix = new Matrix();
matrix.postRotate(angleDeg);
marker.setIcon(BitmapDescriptorFactory.fromBitmap(Bitmap.createBitmap(mMarkerIcon, 0, 0, mMarkerIcon.getWidth(), mMarkerIcon.getHeight(), matrix, true)));
handler.post(new Runnable() {
@Override
public void run() {
// calculate phase of animation
long elapsed = SystemClock.uptimeMillis() - startTime;
float t = interpolator.getInterpolation((float) elapsed / duration);
// calculate new position for marker
double lat = (endLatLng.latitude - beginLatLng.latitude) * t + beginLatLng.latitude;
double lngDelta = endLatLng.longitude - beginLatLng.longitude;
if (Math.abs(lngDelta) > 180) {
lngDelta -= Math.signum(lngDelta) * 360;
}
double lng = lngDelta * t + beginLatLng.longitude;
marker.setPosition(new LatLng(lat, lng));
// if not end of line segment of path
if (t < 1.0) {
// call next marker position
handler.postDelayed(this, 16);
} else {
// call turn animation
nextTurnAnimation();
}
}
});
}
where
mMarkerIcon
is:
Bitmap mMarkerIcon;
...
mMarkerIcon = BitmapFactory.decodeResource(getResources(), R.drawable.the_car); // for your car icon in file the_car.png in drawable folder
and car icon should be North oriented:
for correct rotation apply
nextTurnAnimation()
- method called on end of car movement animation to start car turn animation:
private void nextTurnAnimation() {
mIndexCurrentPoint++;
if (mIndexCurrentPoint < mPathPolygonPoints.size() - 1) {
LatLng prevLatLng = mPathPolygonPoints.get(mIndexCurrentPoint - 1);
LatLng currLatLng = mPathPolygonPoints.get(mIndexCurrentPoint);
LatLng nextLatLng = mPathPolygonPoints.get(mIndexCurrentPoint + 1);
float beginAngle = (float)(180 * getAngle(prevLatLng, currLatLng) / Math.PI);
float endAngle = (float)(180 * getAngle(currLatLng, nextLatLng) / Math.PI);
animateCarTurn(mCarMarker, beginAngle, endAngle, TURN_ANIMATION_DURATION);
}
}
In its turn car turn animation method can be like this:
private void animateCarTurn(final Marker marker, final float startAngle, final float endAngle, final long duration) {
final Handler handler = new Handler();
final long startTime = SystemClock.uptimeMillis();
final Interpolator interpolator = new LinearInterpolator();
final float dAndgle = endAngle - startAngle;
Matrix matrix = new Matrix();
matrix.postRotate(startAngle);
Bitmap rotatedBitmap = Bitmap.createBitmap(mMarkerIcon, 0, 0, mMarkerIcon.getWidth(), mMarkerIcon.getHeight(), matrix, true);
marker.setIcon(BitmapDescriptorFactory.fromBitmap(rotatedBitmap));
handler.post(new Runnable() {
@Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - startTime;
float t = interpolator.getInterpolation((float) elapsed / duration);
Matrix m = new Matrix();
m.postRotate(startAngle + dAndgle * t);
marker.setIcon(BitmapDescriptorFactory.fromBitmap(Bitmap.createBitmap(mMarkerIcon, 0, 0, mMarkerIcon.getWidth(), mMarkerIcon.getHeight(), m, true)));
if (t < 1.0) {
handler.postDelayed(this, 16);
} else {
nextMoveAnimation();
}
}
});
}
where nextMoveAnimation()
is:
private void nextMoveAnimation() {
if (mIndexCurrentPoint < mPathPolygonPoints.size() - 1) {
animateCarMove(mCarMarker, mPathPolygonPoints.get(mIndexCurrentPoint), mPathPolygonPoints.get(mIndexCurrentPoint+1), MOVE_ANIMATION_DURATION);
}
}
The mPathPolygonPoints
(geopoints of car trip) is:
private List<LatLng> mPathPolygonPoints;
And the mIndexCurrentPoint
variable is index of current point on path (it should be 0 at start of animation and incremented on each turn of path in nextTurnAnimation()
method).
TURN_ANIMATION_DURATION
- duration (in ms) animation for car turn on path geopoint;
MOVE_ANIMATION_DURATION
- duration (in ms) animation for car movement along line segment of path;
To get bearing You can use method like that:
private double getAngle(LatLng beginLatLng, LatLng endLatLng) {
double f1 = Math.PI * beginLatLng.latitude / 180;
double f2 = Math.PI * endLatLng.latitude / 180;
double dl = Math.PI * (endLatLng.longitude - beginLatLng.longitude) / 180;
return Math.atan2(Math.sin(dl) * Math.cos(f2) , Math.cos(f1) * Math.sin(f2) - Math.sin(f1) * Math.cos(f2) * Math.cos(dl));;
}
Finally You can start all animations by call animateCarMove()
once:
animateCarMove(mCarMarker, mPathPolygonPoints.get(0), mPathPolygonPoints.get(1), MOVE_ANIMATION_DURATION);
other steps of animation will be called automatically for each point of car path.
And You should take into account some "special cases" like:
1) changing sign of of turn angle (e.g. bearing changes from -120 to 150 degrees);
2) possibilities for interrupt of animation by user;
3) calculate animation duration on path segment length (e.g. 1 sec for 1 km of segment length instead of fixed MOVE_ANIMATION_DURATION
)
4) probably tune value 16
in handler.postDelayed(this, 16);
line for better performance;
5) and so on.