如何在Android地图API V2中实现标记动画?

32
我想实现平滑过渡,模拟汽车标记在地图上移动。
在Android Map API V2中是否可以动画化标记?

据我理解,您需要本地地图API V2的行为,以便将标记从一个位置平滑地移动到另一个位置? - Paul Annekov
你可以查看这篇文章:https://dev59.com/l2Yr5IYBdhLWcg3wYZKD - Pavel Dudka
4个回答

34

我尝试了所有提供的版本,但都无法满足我的需求,所以我实现了自己的定制解决方案。它可以同时提供位置和旋转动画。

/**
 * Method to animate marker to destination location
 * @param destination destination location (must contain bearing attribute, to ensure
 *                    marker rotation will work correctly)
 * @param marker marker to be animated
 */
public static void animateMarker(Location destination, Marker marker) {
    if (marker != null) {
        LatLng startPosition = marker.getPosition();
        LatLng endPosition = new LatLng(destination.getLatitude(), destination.getLongitude());

        float startRotation = marker.getRotation();

        LatLngInterpolator latLngInterpolator = new LatLngInterpolator.LinearFixed();
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(1000); // duration 1 second
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override public void onAnimationUpdate(ValueAnimator animation) {
                try {
                    float v = animation.getAnimatedFraction();
                    LatLng newPosition = latLngInterpolator.interpolate(v, startPosition, endPosition);
                    marker.setPosition(newPosition);
                    marker.setRotation(computeRotation(v, startRotation, destination.getBearing()));
                } catch (Exception ex) {
                    // I don't care atm..
                }
            }
        });

        valueAnimator.start();
    }
}

计算指定动画部分的旋转。标记会朝离起始旋转更近的方向旋转:

private static float computeRotation(float fraction, float start, float end) {
    float normalizeEnd = end - start; // rotate start to 0
    float normalizedEndAbs = (normalizeEnd + 360) % 360;

    float direction = (normalizedEndAbs > 180) ? -1 : 1; // -1 = anticlockwise, 1 = clockwise
    float rotation;
    if (direction > 0) {
        rotation = normalizedEndAbs;
    } else {
        rotation = normalizedEndAbs - 360;
    }

    float result = fraction * rotation + start;
    return (result + 360) % 360;
} 

最后是谷歌的LatLngInterpolator
private interface LatLngInterpolator {
    LatLng interpolate(float fraction, LatLng a, LatLng b);

    class LinearFixed implements LatLngInterpolator {
        @Override
        public LatLng interpolate(float fraction, LatLng a, LatLng b) {
            double lat = (b.latitude - a.latitude) * fraction + a.latitude;
            double lngDelta = b.longitude - a.longitude;
            // Take the shortest path across the 180th meridian.
            if (Math.abs(lngDelta) > 180) {
                lngDelta -= Math.signum(lngDelta) * 360;
            }
            double lng = lngDelta * fraction + a.longitude;
            return new LatLng(lat, lng);
        }
    }
}

我正在尝试实现这个功能,如何将方位角添加到目的地?您在Github上有最终项目的源代码吗? - Frank Odoom
@FrankOdoom 你应该通过 setBearing(float)bearingTo(Location) 方法在传递的 Location destination 参数上正确设置方位。 - skywall
这是我找到的最佳答案。完全没有闪烁。 - Vihanga Yasith
如何设置方位角 - M.Yogeshwaran
@M.Yogeshwaran,请在此讨论中向上查看两条消息。 - skywall
最佳答案,点赞! - phourxx

28

尝试以下代码来动画化Google Map V2上的标记。您需要使用Interpolator类将动画应用于标记,并在处理程序中处理它,如下所示:

   public void animateMarker(final Marker marker, final LatLng toPosition,
        final boolean hideMarker) {
    final Handler handler = new Handler();
    final long start = SystemClock.uptimeMillis();
    Projection proj = mGoogleMapObject.getProjection();
    Point startPoint = proj.toScreenLocation(marker.getPosition());
    final LatLng startLatLng = proj.fromScreenLocation(startPoint);
    final long duration = 500;
    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 * toPosition.longitude + (1 - t)
                    * startLatLng.longitude;
            double lat = t * toPosition.latitude + (1 - t)
                    * startLatLng.latitude;
            marker.setPosition(new LatLng(lat, lng));
            if (t < 1.0) {
                // Post again 16ms later.
                handler.postDelayed(this, 16);
            } else {
                if (hideMarker) {
                    marker.setVisible(false);
                } else {
                    marker.setVisible(true);
                }
            }
        }
    });
}

你要如何实现这段代码?@Anil?你能让标记沿着GPS坐标移动吗?请发布答案。 - momokjaaaaa
2
嘿,非常感谢。它有效了。但是在开始标记动画之前,标记会来回移动。你能告诉我如何修复它吗? - Sadia
@GrlsHu,我的代码和你的类似,但是我无法在实际道路上进行动画,它只能在直线上进行动画。你能告诉我如何实现吗? - Illegal Argument
3
请查看http://ddewaele.github.io/GoogleMapsV2WithActionBarSherlock/part3,它会指引你。 - GrIsHu
@GrIsHu 我已经做了,问题是有时标记甚至会放在建筑物上而不是路径上,所以我尝试使用折线来解决。 - Illegal Argument
我应该不调用builder.include(vehicleLocation)吗? - Siddharth

6

我们刚刚实现了一个新版本,请尝试使用此版本。

public class MarkerAnimation {
static GoogleMap map;
ArrayList<LatLng> _trips = new ArrayList<>() ;
Marker _marker;
LatLngInterpolator _latLngInterpolator = new LatLngInterpolator.Spherical();

public void animateLine(ArrayList<LatLng> Trips,GoogleMap map,Marker  marker,Context current){
    _trips.addAll(Trips);
    _marker = marker;

animateMarker();
}

    public void animateMarker() {
        TypeEvaluator<LatLng> typeEvaluator = new TypeEvaluator<LatLng>() {
            @Override
            public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
                return _latLngInterpolator.interpolate(fraction, startValue, endValue);
            }
        };
        Property<Marker, LatLng> property = Property.of(Marker.class, LatLng.class, "position");

        ObjectAnimator animator = ObjectAnimator.ofObject(_marker, property, typeEvaluator, _trips.get(0));

        //ObjectAnimator animator = ObjectAnimator.o(view, "alpha", 0.0f);
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationCancel(Animator animation) {
                //  animDrawable.stop();
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                //  animDrawable.stop();
            }

            @Override
            public void onAnimationStart(Animator animation) {
                //  animDrawable.stop();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //  animDrawable.stop();
                if (_trips.size() > 1) {
                    _trips.remove(0);
                    animateMarker();
                }
            }
        });

        animator.setDuration(300);
        animator.start();
    } 

LatLngInterpolator类是由Google开发人员预先编写的,您可以按照以下方式使用:

public interface LatLngInterpolator {

public LatLng interpolate(float fraction, LatLng a, LatLng b);

public class Spherical implements LatLngInterpolator {
    @Override
    public LatLng interpolate(float fraction, LatLng from, LatLng to) {
        // http://en.wikipedia.org/wiki/Slerp
        double fromLat = toRadians(from.latitude);
        double fromLng = toRadians(from.longitude);
        double toLat = toRadians(to.latitude);
        double toLng = toRadians(to.longitude);
        double cosFromLat = cos(fromLat);
        double cosToLat = cos(toLat);

        // Computes Spherical interpolation coefficients.
        double angle = computeAngleBetween(fromLat, fromLng, toLat, toLng);
        double sinAngle = sin(angle);
        if (sinAngle < 1E-6) {
            return from;
        }
        double a = sin((1 - fraction) * angle) / sinAngle;
        double b = sin(fraction * angle) / sinAngle;

        // Converts from polar to vector and interpolate.
        double x = a * cosFromLat * cos(fromLng) + b * cosToLat * cos(toLng);
        double y = a * cosFromLat * sin(fromLng) + b * cosToLat * sin(toLng);
        double z = a * sin(fromLat) + b * sin(toLat);

        // Converts interpolated vector back to polar.
        double lat = atan2(z, sqrt(x * x + y * y));
        double lng = atan2(y, x);
        return new LatLng(toDegrees(lat), toDegrees(lng));
    }

    private double computeAngleBetween(double fromLat, double fromLng, double toLat, double toLng) {
        // Haversine's formula
        double dLat = fromLat - toLat;
        double dLng = fromLng - toLng;
        return 2 * asin(sqrt(pow(sin(dLat / 2), 2) +
                cos(fromLat) * cos(toLat) * pow(sin(dLng / 2), 2)));
    }
}
}

然后实例化MarkerAnimation类的对象并像这样调用方法:
 MarkerAnimation.animateLine(TripPoints,map,MovingMarker,context); 

0

您只需要添加这个类并传递一个位置,您可以通过使用Fusedlocationproviderclient轻松获取。

    public class LocationMoveAnim {

        public static void startAnimation(final Marker marker, final GoogleMap googleMap, final LatLng startPosition,
                                         final LatLng endPosition,final GoogleMap.CancelableCallback callback) {
          ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
          int duration = 500;
          valueAnimator.setDuration(duration);
          final LatLngInterpolatorNew latLngInterpolator = new LatLngInterpolatorNew.LinearFixed();
          valueAnimator.setInterpolator(new LinearInterpolator());
          valueAnimator.addUpdateListener(valueAnimator1 -> {
            float v = valueAnimator1.getAnimatedFraction();
            LatLng newPos = latLngInterpolator.interpolate(v, startPosition, endPosition);
            marker.setPosition(newPos);
            marker.setAnchor(0.5f, 0.5f);
            marker.setRotation((float) bearingBetweenLocations(startPosition, endPosition));
            googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(newPos,20F));
            callback.onFinish();
          });
          valueAnimator.start();
       }

         private static double bearingBetweenLocations(LatLng latLng1,LatLng latLng2) {

           double PI = 3.14159;
           double lat1 = latLng1.latitude * PI / 180;
           double long1 = latLng1.longitude * PI / 180;
           double lat2 = latLng2.latitude * PI / 180;
           double long2 = latLng2.longitude * PI / 180;
           double dLon = (long2 - long1);
           double y = Math.sin(dLon) * Math.cos(lat2);
           double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
           double brng = Math.atan2(y, x);
           brng = Math.toDegrees(brng);
           brng = (brng + 360) % 360;

           return brng;
        }

        public interface LatLngInterpolatorNew {
           LatLng interpolate(float fraction, LatLng a, LatLng b);
           class LinearFixed implements LatLngInterpolatorNew {
            @Override
            public LatLng interpolate(float fraction, LatLng a, LatLng b) {
                double lat = (b.latitude - a.latitude) * fraction + a.latitude;
                double lngDelta = b.longitude - a.longitude;
                // Take the shortest path across the 180th meridian.
                if (Math.abs(lngDelta) > 180) {
                    lngDelta -= Math.signum(lngDelta) * 360;
                }
                double lng = lngDelta * fraction + a.longitude;
                return new LatLng(lat, lng);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接