MapBoxMap 之 轨迹回放

轨迹回放功能主要使用ValueAnimator对轨迹动态绘制。

view on github

本篇是基于LineLayer的实际使用,开发当中也确实会遇到这样的需求。

先上图

                          

                        图一                                                                     图二

可以看出主要的3个元素。
1.运动的点
2.轨迹路线

3.轨迹线灰色背景(单纯的为了更好的显示效果存在)

总体思路:

一、图一实现

 1.首先获取List<Point> point (轨迹点数据源)。

 2.绘制红色路线与灰色背景路线。

 3. 使用ValueAnimator动画遍历取出,动态更新点的位置。

二、图二实现

 与图一的区别在于红色路线也是动态绘制的。

 1.首先获取List<Point> point (轨迹点数据源)。

 2.绘制灰色背景路线。

 3. 使用ValueAnimator动画遍历取出,动态更新点的位置同时动态绘制红色路线。

直接上代码:

代码中已经尽量添加了详细的注释。

MarkerFollowingRouteActivity:
package com.ming.androidmapbox.layers;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.animation.LinearInterpolator;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.mapbox.geojson.Feature;
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.geojson.LineString;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.LineLayer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.ming.androidmapbox.R;

import java.util.ArrayList;
import java.util.List;

import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineWidth;

/**
 * 标记沿GeoJSON路线行驶
 */
public class MarkerFollowingRouteActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MarkerFollowingRoute";
    private static final String DOT_SOURCE_ID = "dot-source-id";
    private static final String DOT_LAYER_ID = "dot-layer-id";
    private static final String DOT_IMAGE_ID = "dot-image-id";
    private static final String LINE_SOURCE_ID = "line-source-id";
    private static final String LINE_LAYER_ID = "line-layer-id";
    private static final String LINE_BG_SOURCE_ID = "line-bg-source-id";
    private static final String LINE_BG_LAYER_ID = "line-bg-layer-id";

    private MapView mapView;

    private int count = 0;
    private Handler handler;
    private Runnable runnable;
    private GeoJsonSource dotGeoJsonSource;
    private ValueAnimator markerIconAnimator;
    private LatLng markerIconCurrentLocation;
    private List<Point> routeCoordinates;

    private boolean isFollowMode;
    private GeoJsonSource lineGeoJsonSource;
    private List<Point> followRouteCoordinates = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Mapbox.getInstance(this, getString(R.string.mapbox_access_token));

        setContentView(R.layout.activity_lab_marker_following_route);

        mapView = findViewById(R.id.mapView);
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(@NonNull MapboxMap mapboxMap) {
                mapboxMap.setStyle(Style.LIGHT, new Style.OnStyleLoaded() {
                    @Override
                    public void onStyleLoaded(@NonNull Style style) {
                        initCoordinates();
                        initSources(style);
                        initBgDotLinePath(style);
                        initDotLinePath(style);
                        initDotLayer(style);
                        //按钮监听
                        findViewById(R.id.btn_route).setOnClickListener(MarkerFollowingRouteActivity.this);
                        findViewById(R.id.btn_route_follow).setOnClickListener(MarkerFollowingRouteActivity.this);
                    }
                });
            }
        });
    }

    /**
     * 创造轨迹点数据源,手动添加一组点
     */
    private void initCoordinates() {
        routeCoordinates = new ArrayList<>();
        routeCoordinates.add(Point.fromLngLat(-77.044211, 38.852924));
        routeCoordinates.add(Point.fromLngLat(-77.045659, 38.860158));
        routeCoordinates.add(Point.fromLngLat(-77.044232, 38.862326));
        routeCoordinates.add(Point.fromLngLat(-77.040879, 38.865454));
        routeCoordinates.add(Point.fromLngLat(-77.039936, 38.867698));
        routeCoordinates.add(Point.fromLngLat(-77.040338, 38.86943));
        routeCoordinates.add(Point.fromLngLat(-77.04264, 38.872528));
        routeCoordinates.add(Point.fromLngLat(-77.03696, 38.878424));
        routeCoordinates.add(Point.fromLngLat(-77.032309, 38.87937));
        routeCoordinates.add(Point.fromLngLat(-77.030056, 38.880945));
        routeCoordinates.add(Point.fromLngLat(-77.027645, 38.881779));
        routeCoordinates.add(Point.fromLngLat(-77.026946, 38.882645));
        routeCoordinates.add(Point.fromLngLat(-77.026942, 38.885502));
        routeCoordinates.add(Point.fromLngLat(-77.028054, 38.887449));
        routeCoordinates.add(Point.fromLngLat(-77.02806, 38.892088));
        routeCoordinates.add(Point.fromLngLat(-77.03364, 38.892108));
        routeCoordinates.add(Point.fromLngLat(-77.033643, 38.899926));
    }

    /**
     * 初始化各个源
     * @param loadedMapStyle 
     */
    private void initSources(@NonNull Style loadedMapStyle) {
        //根据轨迹点数据源构建FeatureCollection
        FeatureCollection featureCollection = FeatureCollection.fromFeatures(
                new Feature[]{Feature.fromGeometry(LineString.fromLngLats(routeCoordinates))});
        //添加点的源
        dotGeoJsonSource = new GeoJsonSource(DOT_SOURCE_ID, featureCollection);
        loadedMapStyle.addSource(dotGeoJsonSource);
        //添加红色的线的源
        lineGeoJsonSource = new GeoJsonSource(LINE_SOURCE_ID, featureCollection);
        loadedMapStyle.addSource(lineGeoJsonSource);
        //添加灰色背景线的源
        GeoJsonSource lineBgGeoJsonSource = new GeoJsonSource(LINE_BG_SOURCE_ID, featureCollection);
        loadedMapStyle.addSource(lineBgGeoJsonSource);
    }

    /**
     * 添加灰色背景线
     *
     * @param loadedMapStyle
     */
    private void initBgDotLinePath(@NonNull Style loadedMapStyle) {
        //添加灰色背景线的layer
        loadedMapStyle.addLayer(new LineLayer(LINE_BG_LAYER_ID, LINE_BG_SOURCE_ID).withProperties(
                lineColor(Color.GRAY),
                lineWidth(10f)
        ));
    }

    /**
     * 添加红色的线
     *
     * @param loadedMapStyle
     */
    private void initDotLinePath(@NonNull Style loadedMapStyle) {
        //添加红色的线的layer
        loadedMapStyle.addLayer(new LineLayer(LINE_LAYER_ID, LINE_SOURCE_ID).withProperties(
                lineColor(Color.parseColor("#F13C6E")),
                lineWidth(4f)
        ));
    }

    /**
     * 添加运动的点
     *
     * @param loadedMapStyle
     */
    private void initDotLayer(@NonNull Style loadedMapStyle) {
        //添加点的图片资源
        loadedMapStyle.addImage(DOT_IMAGE_ID, getResources().getDrawable(R.drawable.pink_dot));
        //添加点的layer
        loadedMapStyle.addLayer(new SymbolLayer(DOT_LAYER_ID, DOT_SOURCE_ID).withProperties(
                iconImage(DOT_IMAGE_ID),
                iconSize(1f),
                iconIgnorePlacement(true),
                iconAllowOverlap(true)
        ));
    }

    @Override
    public void onResume() {
        super.onResume();
        mapView.onResume();
        // 恢复活动后,我们将重新启动标记动画。
        if (handler != null && runnable != null) {
            handler.post(runnable);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mapView.onPause();
        //检查标记当前是否正在设置动画,如果是,则我们暂停动画,以便当活动不在视图中时不使用资源。
        if (handler != null && runnable != null) {
            handler.removeCallbacksAndMessages(null);
        }
        if (markerIconAnimator != null) {
            markerIconAnimator.cancel();
        }
    }

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

    @Override
    protected void onStop() {
        super.onStop();
        mapView.onStop();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

    @Override
    public void onClick(View v) {
        //动画执行过程中不允许点击
        if (markerIconAnimator != null && markerIconAnimator.isRunning()) {
            return;
        }
        resetParams();
        switch (v.getId()) {
            case R.id.btn_route:
                initRunnable(false);
                break;

            case R.id.btn_route_follow:
                initRunnable(true);
                break;

            default:
                break;
        }
    }

    /**
     * 设置重复逻辑以沿路径移动图标。
     */
    private void initRunnable(final boolean isFollow) {
        isFollowMode = isFollow;
       // ValueAnimator用于在GeoJSON点之间移动标记,是线性完成的。该处理程序用于沿着GeoJSON点移动标记。
        handler = new Handler();
        runnable = new Runnable() {
            @Override
            public void run() {
                //检查是否在列表的末尾,如果是停止处理程序。
                if ((routeCoordinates.size() - 1 > count)) {
                    //下一个要移动的点的位置
                    Point nextLocation = routeCoordinates.get(count + 1);
                    //如果动画已开始就取消
                    if (markerIconAnimator != null && markerIconAnimator.isStarted()) {
                        markerIconCurrentLocation = (LatLng) markerIconAnimator.getAnimatedValue();
                        markerIconAnimator.cancel();
                    }
                    //取出第一个点
                    LatLng firstLocation = new LatLng(routeCoordinates.get(0).latitude(), routeCoordinates.get(0).longitude());
                    //构建markerIconAnimator
                    markerIconAnimator = ObjectAnimator
                            .ofObject(AnimalCons.LATLNG_EVALUATOR, count == 0 || markerIconCurrentLocation == null
                                            ? firstLocation
                                            : markerIconCurrentLocation,
                                    new LatLng(nextLocation.latitude(), nextLocation.longitude()))
                            .setDuration(500);
                    markerIconAnimator.setInterpolator(new LinearInterpolator());

                    markerIconAnimator.addUpdateListener(animatorUpdateListener);
                   
                    markerIconAnimator.start();

                    //逻辑后再自增,保持当前点数不变。
                    count++;

                    //一旦完成,我们就需要在ValueAnimator完成后再次执行处理程序来重复整个过程。
                    handler.postDelayed(this, 500);
                } else {
                    //复位
                    resetParams();
                }
            }
        };
        handler.post(runnable);
    }

    /**
     * 重置参数
     */
    private void resetParams() {
        count = 0;
        markerIconCurrentLocation = null;
        isFollowMode = false;
        followRouteCoordinates.clear();
    }

    /**
     * ValueAnimator提供更新值时的监听
     */
    private final ValueAnimator.AnimatorUpdateListener animatorUpdateListener =
            new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    LatLng animatedPosition = (LatLng) valueAnimator.getAnimatedValue();
                    Point point = Point.fromLngLat(
                            animatedPosition.getLongitude(), animatedPosition.getLatitude());
                    //动态更新点的位置
                    if (dotGeoJsonSource != null) {
                        dotGeoJsonSource.setGeoJson(point);
                    }
                    //如果是跟随模式,动态绘制红色路线
                    if (isFollowMode) {
                        if (lineGeoJsonSource != null) {
                            followRouteCoordinates.add(point);
                            FeatureCollection featureCollection = FeatureCollection.fromFeatures(
                                    new Feature[]{Feature.fromGeometry(LineString.fromLngLats(followRouteCoordinates))});
                            lineGeoJsonSource.setGeoJson(featureCollection);
                        }
                    }
                }
            };
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:mapbox="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".layers.MarkerFollowingRouteActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        mapbox:mapbox_cameraTargetLat="38.875"
        mapbox:mapbox_cameraTargetLng="-77.035"
        mapbox:mapbox_cameraZoom="13">

        <Button
            android:id="@+id/btn_route"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="标记沿路线行驶" />

        <Button
            android:id="@+id/btn_route_follow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:text="标记沿路线跟随" />
    </com.mapbox.mapboxsdk.maps.MapView>

</FrameLayout>

圆点图片:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值