【问题标题】:Android - How to make google Maps display a polyline that animates sequenctial flashing dotsAndroid - 如何让谷歌地图显示一条动画连续闪烁点的折线
【发布时间】: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。我认为一个点是其中一个点。

【问题讨论】:

    标签: google-maps-markers google-maps-android-api-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 点之前画线,以避免与标记相交。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-28
    相关资源
    最近更新 更多