Google 地图 - 制作类似 grab app 的动画来显示路径方向

Google Maps - making an animation like grab app to show the path direction

我想制作一个沿路径移动标记的动画。

所以我考虑了如何使用比路径线稍暗的标记来做到这一点。并非常缓慢地移除和更新其位置,以便它看起来像是在移动。但我只是不认为那是视频中发生的事情。因为市场完全呈现出路径的形状。它完美地流动。

这是我目前的情况:

fun showLineAtUsersLocation(loc_destination: LatLng) {
    val currentLoc = activity.getCachedCurrentLoc()
        val pattern = Arrays.asList(Dash(), Gap(convertDpToPixel(6).toFloat()))
        val polyLineOptions: PolylineOptions = PolylineOptions()
                .add(currentLoc)
                .add(loc_destination)
                .geodesic(true)
                .pattern(pattern)
                .width(convertDpToPixel(8).toFloat())
        googleMap.addPolyline(polyLineOptions)
}

但这只是显示了一条从一点到另一点的叠加线。没有真正遵循路径。我应该寻找什么?

我看到 ios 有一个 class good for this GMSStyleSpan 来处理图像。但我在 android 中找不到等效项。我没有看到跨度 class 或样式 class 我可以参考这方面的任何想法吗?或者甚至在标记上使用 AnomatedVectorDrawable

对于此类(实际上是任何类型的)动画,您可以使用 View Canvas 动画。此方法需要 MapView-based custom view,实现:

  • 在 MapView 上绘图 canvas;

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

  • 绑定到 Lat/Lon 地图坐标的路径

  • 执行动画。

在 MapView 上绘制需要覆盖 dispatchDraw()。自定义线条样式需要 setPathEffect() method of Paint class 允许为 "circle stamp"(以像素为单位)创建创建路径,这将重复每个 "advance"(也以像素为单位),类似这样:

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

要将屏幕上的路径绑定到 Lat/Lon 坐标,可以使用 Projection.toScreenLocation() needed, that requires GoogleMap object so custom view should implements OnMapReadyCallback for receive it. For continuous animation postInvalidateDelayed()。因此,使用基于 MapView 的自定义 EnhancedMapView:

的完整源代码
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 OnMapReadyCallback mMapReadyCallback;
    private GoogleMap mGoogleMap;
    private LatLng mPointA;
    private LatLng mPointB;

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

    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 continuous animation
        postInvalidateDelayed(1000 / FRAMES_PER_SECOND);
    }

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

        final Projection mapProjection = mGoogleMap.getProjection();
        final Point pointA = mapProjection.toScreenLocation(mPointA);
        final Point pointB = mapProjection.toScreenLocation(mPointB);

        mPathFromAtoB.rewind();
        mPathFromAtoB.moveTo(pointB.x, pointB.y);
        mPathFromAtoB.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(mPathFromAtoB, 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));

        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;
    }

}

MainActivity 赞:

public class MainActivity extends AppCompatActivity {

    private static final String MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey";
    static final LatLng MAIDAN = new LatLng(50.450891, 30.522843);
    static final LatLng SOPHIA = new LatLng(50.452967, 30.514498);

    static final LatLng INITIAL_MAP_CENTER = new LatLng(50.452011, 30.518766);
    static final int INITIAL_ZOOM = 15;

    private GoogleMap mGoogleMap;
    private EnhancedMapView mMapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Bundle mapViewBundle = null;
        if (savedInstanceState != null) {
            mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY);
        }

        mMapView = (EnhancedMapView) findViewById(R.id.mapview);
        mMapView.onCreate(mapViewBundle);
        mMapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(GoogleMap googleMap) {
                mGoogleMap = googleMap;
                mMapView.setPoints(MAIDAN, SOPHIA);
                mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(INITIAL_MAP_CENTER, INITIAL_ZOOM));
            }
        });

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        Bundle mapViewBundle = outState.getBundle(MAP_VIEW_BUNDLE_KEY);
        if (mapViewBundle == null) {
            mapViewBundle = new Bundle();
            outState.putBundle(MAP_VIEW_BUNDLE_KEY, mapViewBundle);
        }

        mMapView.onSaveInstanceState(mapViewBundle);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mMapView.onResume();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mMapView.onStart();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mMapView.onStop();
    }
    @Override
    protected void onPause() {
        mMapView.onPause();
        super.onPause();
    }
    @Override
    protected void onDestroy() {
        mMapView.onDestroy();
        super.onDestroy();
    }
    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mMapView.onLowMemory();
    }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.test.just.googlemapsgeneral.views.EnhancedMapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />

</RelativeLayout>

你有这样的东西:

注意!您应该使用 Path 而不是画线。这只是方法,不是完整的解决方案。