Android - 如何让谷歌地图显示一条动画连续闪烁点的折线

Posted

技术标签:

【中文标题】Android - 如何让谷歌地图显示一条动画连续闪烁点的折线【英文标题】:Android - How to make google Maps display a polyline that animates sequenctial flashing dots 【发布时间】:2019-05-20 22:41:10 【问题描述】:

我正在寻找一种方法来为 android 设备中谷歌地图上两个标记之间的点设置动画。 所以我最终想要的是两个图像之间的以下行:

它会像这个典型的谷歌折线实现一样使用:

假设有一个点 A 和一个点 B。如果我将用户引导到点 B,那么该线会从 A 点动画到 B 点,因此用户知道朝这个方向走。

为了实现这一点,我想我可以从折线中取出点并将它们删除并重新添加 迅速。所以假设我在折线中有 5 个点,我会删除位置 1 ,然后将其放回原处,然后删除位置 2 并将其放回,以模拟此动画。

但它不起作用。一旦设置了多段线,似乎我无法更改它。你有什么建议吗?

val dotPattern = Arrays.asList(Dot(), Gap(convertDpToPixel(7).toFloat()))
            val polyLineOptions: PolylineOptions = PolylineOptions()
                    .add(usersLocation)
                    .add(users_destination)
                    .pattern(dotPattern)
                    .width(convertDpToPixel(6).toFloat())
            dottedPolyLine = googleMap.addPolyline(polyLineOptions)

dottedPolyLine?.points?.removeAt(1) // 这里作为一个测试,如果我的想法我尝试删除一个点,但它看起来这里的一个点表示当前位置或目的地,所以总是有 2。我认为一个点是其中一个点。

【问题讨论】:

【参考方案1】:

您可以在this answer 中使用基于 MapView 的自定义视图 View Canvas 动画:

这种方法需要 基于MapView custom view, 实现:

在 MapView 画布上绘图;

自定义线条样式(圆形而不是简单的线条);

地图纬度/经度坐标的绑定路径

执行动画。

在 MapView 上绘制需要覆盖 dispatchDraw()。 自定义线型需求 setPathEffect() 的方法 Paint 允许为“圆形印章”创建路径的类(在 像素),它将重复每个“前进”(也以像素为单位), 类似的东西:

mCircleStampPath = new Path(); mCircleStampPath.addCircle(0,0, CIRCLE_RADIUS,路径.方向.逆时针); mCircleStampPath.close();

用于将屏幕上的路径绑定到纬度/经度坐标 Projection.toScreenLocation() 需要,这需要 GoogleMap 对象所以自定义视图应该实现OnMapReadyCallback 收到它。对于连续动画 postInvalidateDelayed() 可以使用。

但不是直接从 A 点到 B 点绘制路径,而是从 A 点到 C 点的动画从 A 到 B 点。要获取 C 点的当前位置,您可以使用 Google Maps Android API Utility Library 中的 SphericalUtil.interpolate()。类似的东西:

public class EnhancedMapView extends MapView implements OnMapReadyCallback 

    private static final float CIRCLE_RADIUS = 10;
    private static final float CIRCLE_ADVANCE = 3.5f * CIRCLE_RADIUS;   // spacing between each circle stamp
    private static final int FRAMES_PER_SECOND = 30;
    private static final int ANIMATION_DURATION = 10000;


    private OnMapReadyCallback mMapReadyCallback;
    private GoogleMap mGoogleMap;
    private LatLng mPointA;
    private LatLng mPointB;
    private LatLng mPointC;

    private float mCirclePhase = 0;                                     // amount to offset before the first circle is stamped
    private Path mCircleStampPath;
    private Paint mPaintLine;
    private final Path mPathFromAtoC = new Path();
    private long mStartTime;
    private long mElapsedTime;

    public EnhancedMapView(@NonNull Context context) 
        super(context);
        init();
    

    public EnhancedMapView(@NonNull Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
        init();
    

    public EnhancedMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        init();
    

    public EnhancedMapView(@NonNull Context context, @Nullable GoogleMapOptions options) 
        super(context, options);
        init();
    

    @Override
    public void dispatchDraw(Canvas canvas) 
        super.dispatchDraw(canvas);
        canvas.save();
        drawLineFomAtoB(canvas);
        canvas.restore();

        // perform one shot animation
        mElapsedTime = System.currentTimeMillis() - mStartTime;
        if (mElapsedTime < ANIMATION_DURATION) 
            postInvalidateDelayed(1000 / FRAMES_PER_SECOND);
        
    

    private void drawLineFomAtoB(Canvas canvas) 
        if (mGoogleMap == null || mPointA == null || mPointB == null) 
            return;
        

        // interpolate current position
        mPointC = SphericalUtil.interpolate(mPointA, mPointB, (float) mElapsedTime / (float)ANIMATION_DURATION);

        final Projection mapProjection = mGoogleMap.getProjection();
        final Point pointA = mapProjection.toScreenLocation(mPointA);
        final Point pointC = mapProjection.toScreenLocation(mPointC);

        mPathFromAtoC.rewind();
        mPathFromAtoC.moveTo(pointC.x, pointC.y);
        mPathFromAtoC.lineTo(pointA.x, pointA.y);

        // change phase for circles shift
        mCirclePhase = (mCirclePhase < CIRCLE_ADVANCE)
                ? mCirclePhase + 1.0f
                : 0;
        mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));

        canvas.drawPath(mPathFromAtoC, mPaintLine);
    

    private void init() 
        setWillNotDraw(false);

        mCircleStampPath = new Path();
        mCircleStampPath.addCircle(0,0, CIRCLE_RADIUS, Path.Direction.CCW);
        mCircleStampPath.close();

        mPaintLine = new Paint();
        mPaintLine.setColor(Color.BLACK);
        mPaintLine.setStrokeWidth(1);
        mPaintLine.setStyle(Paint.Style.STROKE);
        mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));

        // start animation
        mStartTime = System.currentTimeMillis();    
        postInvalidate();
    

    @Override
    public void getMapAsync(OnMapReadyCallback callback) 
        mMapReadyCallback = callback;
        super.getMapAsync(this);
    

    @Override
    public void onMapReady(GoogleMap googleMap) 
        mGoogleMap = googleMap;
        mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() 
            @Override
            public void onCameraMove() 
                invalidate();
            
        );
        if (mMapReadyCallback != null) 
            mMapReadyCallback.onMapReady(googleMap);
        
    

    public void setPoints(LatLng pointA, LatLng pointB) 
        mPointA = pointA;
        mPointB = pointB;
    


注意!这只是想法,不是经过完整测试的代码。

【讨论】:

你的工作是天才。感谢你的帮助。我需要的一件事。我不希望这条线过多地接触标记。一旦线到达标记,它就不应该再显示了。我怎么能控制它?我希望有一个 z-index 来使标记出现在线上。你有什么建议?我只需要这条线不要越过标记的顶部。 @j2emanue 感谢,但我只是 Android 工程师 :) 由于直接在 View 画布上绘图,因此无法考虑 Google Maps z-index。可能的解决方法 - 将标记 LatLng 转换为屏幕坐标(通过mapProjection.toScreenLocation(marker.getPosition());)并在drawLineFomAtoB(canvas); 调用后在画布上手动绘制标记。标记可以像this 问题的答案一样绘制为位图。或者您可以在 A 点稍靠后和 B 点之前画线,以避免与标记相交。

以上是关于Android - 如何让谷歌地图显示一条动画连续闪烁点的折线的主要内容,如果未能解决你的问题,请参考以下文章

如何让谷歌地图再次请求位置权限?

如何在Android上使用谷歌地图或mapquest动态突出显示道路的一侧(或在旁边画一条线)

如何让谷歌玩游戏成为可选?

谷歌地图的连续动画

在 Android WebView 中显示谷歌纵横位置徽章时出现问题

如何从谷歌地图 api android 中删除多余的折线