轨迹播放
首先上视频效果(本来是要弄成GIF动态图的,但是手机将图片用微信发到电脑,电脑修改后缀名后还是不能展示)
完整代码在最后面
在实现前需要先初始化和定义一些变量(大佬可以直接跳过),坐标列表和起点以及终点坐标根据各自的真实情况去赋值,这里就不显示了。
//获取的坐标列表
private List<LatLng> points;
//起点和终点坐标--用于给起点和终点做标记,不需要可以去掉
private LatLng latLngStart, latLngEnd;
private MapView mMapView = null;//xml布局的地图控件
private AMap aMap;//高德地图map
private ImageView mPlayBtn;//播放暂停按钮
private SeekBar mProgressBar;//进度条
boolean isFirstMove = true;//是否首次移动
boolean isPlay = true;//是否正在移动
private SmoothMoveMarker smoothMoveMarker;//控制车辆运动的对象
private int current;//当前滑动到的值
private int poiSize;//记录轨迹数据的数量--用于设计进度条的大小
private double totalTime;//轨迹总时间--用于设计进度条的总时间,可以自己设置固定值
private Timer timer;//计时器,用于控制进度条自动滑动
//onCreate方法就不说明了
private void initView() {
//高德地图所需的配置
ServiceSettings.updatePrivacyShow(getContext(), true, true);
ServiceSettings.updatePrivacyAgree(getContext(), true);
mMapView = (MapView) findViewById(R.id.mapView);//地图组件
//在activity执行onCreate时执行mMapView.onCreate(savedInstanceState),创建地图
mMapView.onCreate(savedInstanceState);
aMap = mMapView.getMap();
mPlayBtn = findViewById(R.id.play_btn);//播放按钮
mProgressBar = findViewById(R.id.progress_bar);//进度条
//是否第一次显示,是则初始化数据
if (isFirstMove)
initData();//该方法用于获取轨迹的列表值,在这里给points赋值
//赋值-points为轨迹点列表
latLngStart = points.get(0);//起点坐标
latLngEnd = points.get(points.size() - 1);//终点坐标
poiSize = points.size();//记录总数量
totalTime = ((double) poiSize) / 30;//记录总时间
mProgressBar.setMax(poiSize);//进度条设置最大值
setMap();//设置地图的方法
initListener();//点击事件函数
}
//下面的地图根据生命周期的配置
@Override
public void onResume() {
super.onResume();
//在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理
mMapView.onResume();
}
@Override
public void onPause() {
super.onPause();
//在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理
mMapView.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
//在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
mMapView.onDestroy();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态
mMapView.onSaveInstanceState(outState);
}
下面写setMap()方法,主要是定位到起点,绘制起点和终点的标记和绘制轨迹线。
private void setMap() {
//定位到起点--latLngStart为起点坐标,12为图层,控制地图比例,可以自己根据需要修改
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLngStart, 12);
aMap.moveCamera(cameraUpdate);
//绘制起点的标记
MarkerOptions startOptions = new MarkerOptions();
BitmapDescriptor startBit = BitmapDescriptorFactory.fromResource(R.drawable.icon_start);//起点坐标图片
startOptions.position(latLngStart).icon(startBit);
aMap.addMarker(startOptions);
//绘制终点的标记
MarkerOptions endOptions = new MarkerOptions();
BitmapDescriptor endBit = BitmapDescriptorFactory.fromResource(R.drawable.icon_end);//终点坐标图片
endOptions.position(latLngEnd).icon(endBit);
aMap.addMarker(endOptions);
//绘制轨迹线--points轨迹点列表,5-线的宽度,0xAAFFC107为16进制的颜色
PolylineOptions lineOptions = new PolylineOptions().addAll(points).width(5).color(0xAAFFC107);
Polyline polyline = aMap.addPolyline(lineOptions);
//轨迹动画方法--points为轨迹点列表,true为设置是否要让小车动起来
setStartAnimation(points, true);
}
setStartAnimation(points, true);方法,用于控制小车运动和进度条移动
//points轨迹点列表,isMove设置是否播放
private void setStartAnimation(List<LatLng> points, boolean isMove) {
//如果不为空说明以及有轨迹播放了,将原来的轨迹清除
if (smoothMoveMarker != null) {
smoothMoveMarker.stopMove();
smoothMoveMarker.removeMarker();
}
//若计时器不为空则清除,再重新设定计时器--自定义的函数,实现方法放在后面
stopTimer();
//设置滑动的图标
smoothMoveMarker = new SmoothMoveMarker(aMap);
smoothMoveMarker.setDescriptor(BitmapDescriptorFactory.fromResource(R.drawable.icon_truck));//里面为小车的图片,可以自己选图片
LatLng drivePoint = points.get(0);
Pair<Integer, LatLng> pair = SpatialRelationUtil.calShortestDistancePoint(points, drivePoint);
points.set(pair.first, drivePoint);
List<LatLng> subList = points.subList(pair.first, points.size());
//设置滑动的轨迹左边点
smoothMoveMarker.setPoints(subList);
//设置滑动的总时间,current为点击进度条时记录的数值,然后根据数值计算前面的部分所占比例
double cur = ((double) current) / poiSize;
smoothMoveMarker.setTotalDuration((int) (totalTime * (1 - cur) + 0.5));
//如果需要播放再开启计时器,计时器开启则会移动进度条
if (isMove) {
//开启计时器--自定义的函数,实现方法放在后面
startTimer();
//开始滑动小车,轨迹播放开始
smoothMoveMarker.startSmoothMove();
//将播放按钮的图片改为暂停图标
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_pause_video));
isPlay = true;//将播放状态设置为true
} else {
//将播放按钮的图片改为播放图标
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_play_video));
isPlay = false;//将播放状态设置为false
}
}
接下来说明开启计时器和暂停计时器的方法start Timer()和stop Timer()
/**
* 开启计时方法
*/
public void startTimer() {
timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
mProgressBar.setProgress(mProgressBar.getProgress() + 3);
}
};
timer.schedule(task, 100, 100);
}
/**
* 暂停计时方法
*/
public void stopTimer() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
最后一步,点击事件方法 initListener()
/**
* 点击事件
*/
private void initListener() {
//播放监听
mPlayBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isPlay) {//如果正在播放则变为暂停
smoothMoveMarker.stopMove();
isPlay = !isPlay;
//将播放按钮的图标改为播放图标
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_play_video));
//暂停计时器
stopTimer();
} else {//如果播放暂停则开始播放
smoothMoveMarker.startSmoothMove();
isPlay = !isPlay;
//将播放按钮的图标改为暂停图标
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_pause_video));
//开启计时器
startTimer();
}
}
});
//进度条监听
mProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
//progress当前进度值--当进度条的数值改变时就会调用该方法
current = progress;//记录当前值
if (current == poiSize) {//如果当前值为最大值,则将进度条清零,轨迹暂停
mProgressBar.setProgress(0);
setStartAnimation(points, false);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//点击了进度条
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//停止点击进度条
//newpoints用于保存点击进度条后剩余轨迹的轨迹点列表
List<LatLng> newpoints = new ArrayList<>();
//计算点击后要跳过的部分所占总长的比例
double cur = ((double) current) / poiSize;
//根据比例计算起点值,如果总长是根据轨迹点的数量设定的,可以直接 i=current
for (int i = (int) (cur * points.size()); i< points.size(); i++) {
newpoints.add(points.get(i));
}
//如果剩余轨迹不为空且长度不为0则开启播放
if (newpoints != null && newpoints.size() > 0) {
setStartAnimation(newpoints, true);
}
}
});
}
总体就是这样吧,一个效果自己弄了三天,一开始用百度地图,但是里面斜率和小车的旋转角度什么的得自己计算,我感觉会比较麻烦,于是转为高德,不需要去计算这些,感觉会方便一些,由于没有单独弄出来demo,所以没有单独发到git,但是注释以及很详细了,后续有需要可以整合到git上发布。
附上完整代码(功能在fragment中,可以根据自己需要改为Activity)
首先是xml布局(图片就自己去网上找吧iconfont-阿里巴巴矢量图标库)
fragment_new.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.amap.api.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--进度控制-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="@color/black"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:alpha="0.5"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/play_btn"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:background="@drawable/icon_play_video"
android:scaleType="fitXY" />
<SeekBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="3dp"
android:minHeight="3dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:min="0"
android:progressDrawable="@drawable/layer_list_seekbar"
android:thumb="@drawable/icon_round"
android:layout_toRightOf="@+id/play_btn"
android:layout_centerVertical="true"/>
</RelativeLayout>
</RelativeLayout>
进度条的drawable: layer_list_seekbar.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="3dp" />
<!--<gradient-->
<!--android:angle="270"-->
<!--android:centerColor="#2B2B2B"-->
<!--android:centerY="0.75"-->
<!--android:endColor="#ff747674"-->
<!--android:startColor="#ff9d9e9d" />-->
<solid android:color="#ffffff" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="3dp" />
<!--<gradient-->
<!--android:angle="270"-->
<!--android:centerColor="#ff3399CC"-->
<!--android:centerY="0.75"-->
<!--android:endColor="#ff6699CC"-->
<!--android:startColor="#ff0099CC" />-->
<solid android:color="#2496F6" />
</shape>
</clip>
</item>
</layer-list>
页面逻辑:NewFragment.java
(这里继承的BaseFragment不用理会,自行改为Fragment或者Activity就行)
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.amap.api.maps.AMap;
import com.amap.api.maps.CameraUpdate;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.MapView;
import com.amap.api.maps.model.BitmapDescriptor;
import com.amap.api.maps.model.BitmapDescriptorFactory;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.LatLngBounds;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.Polyline;
import com.amap.api.maps.model.PolylineOptions;
import com.amap.api.maps.utils.SpatialRelationUtil;
import com.amap.api.maps.utils.overlay.SmoothMoveMarker;
import com.amap.api.services.core.ServiceSettings;
import org.ismarter.zhjt.R;
import org.ismarter.zhjt.base.BaseFragment;
import org.ismarter.zhjt.electronicwaybill.bean.WayBillTrackBean;
import org.ismarter.zhjt.util.MyGson;
import org.ismarter.zhjt.web.CallBackUtil;
import org.ismarter.zhjt.web.OkhttpUtil;
import org.ismarter.zhjt.web.WebConfig;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import okhttp3.Call;
public class NewFragment extends BaseFragment{
private String carNumber;
private String waybillNo;
//获取的坐标列表
private List<LatLng> points;
//起点和终点坐标
private LatLng latLngStart, latLngEnd;
private MapView mMapView = null;
private AMap aMap;
private ImageView mPlayBtn;//mPlayBtn
private SeekBar mProgressBar;//mProgressBar
boolean isFirstMove = true;//是否首次移动
boolean isPlay = true;//是否正在移动
private SmoothMoveMarker smoothMoveMarker;
private int current;//当前滑动到的值
private int poiSize;//记录轨迹数据的数量
private double totalTime;//轨迹总时间
private Timer timer;
public NewFragment(String carNumber, String waybillNo) {
this.carNumber = carNumber;
this.waybillNo = waybillNo;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_new, parent, false);
initView(view, savedInstanceState);
return view;
}
private void initView(View view, Bundle savedInstanceState) {
ServiceSettings.updatePrivacyShow(getContext(), true, true);
ServiceSettings.updatePrivacyAgree(getContext(), true);
mMapView = (MapView) view.findViewById(R.id.mapView);
//在activity执行onCreate时执行mMapView.onCreate(savedInstanceState),创建地图
mMapView.onCreate(savedInstanceState);
aMap = mMapView.getMap();
mPlayBtn = view.findViewById(R.id.play_btn);
mProgressBar = view.findViewById(R.id.progress_bar);
initListener();
}
/**
* 开启计时方法
*/
public void startTimer() {
timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
mProgressBar.setProgress(mProgressBar.getProgress() + 3);
}
};
timer.schedule(task, 100, 100);
}
/**
* 暂停计时方法
*/
public void stopTimer() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
/**
* 点击事件
*/
private void initListener() {
//播放监听
mPlayBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isPlay) {
smoothMoveMarker.stopMove();
isPlay = !isPlay;
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_play_video));
//暂停计时器
stopTimer();
} else {
smoothMoveMarker.startSmoothMove();
isPlay = !isPlay;
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_pause_video));
//开启计时器
startTimer();
}
// mProgressBar.setProgress(20);
}
});
//进度监听
mProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
//progress当前进度值
current = progress;
Log.e("aaaaaaa", current + "");
if (current == poiSize) {
mProgressBar.setProgress(0);
setStartAnimation(points, false);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//点击了进度条
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//停止点击进度条
List<LatLng> newpoints = new ArrayList<>();
double cur = ((double) current) / poiSize;
for (int i = (int) (cur * points.size()); i< points.size(); i++) {
newpoints.add(points.get(i));
}
if (newpoints != null && newpoints.size() > 0) {
setStartAnimation(newpoints, true);
}
}
});
}
@Override
protected void onVisible() {
super.onVisible();
if (isFirstMove)
initData();
else {
// smoothMoveMarker.destroy();
// smoothMoveMarker.startSmoothMove();
}
}
/**
* 网络请求的轨迹数据,这里的动画时间是根据轨迹点的数量设定的,可以自己设置固定值
**/
private void initData() {
Map<String, String> map = new HashMap<>();
map.put("carNumber", carNumber);
map.put("waybillNo", waybillNo);
OkhttpUtil.okHttpPostJson(WebConfig.electronicWaybilltrack, MyGson.mapToJson(map), new CallBackUtil.CallBackString() {
@Override
public void onFailure(Call call, Exception e) {
}
@Override
public void onResponse(String response) {
if (!TextUtils.isEmpty(response)) {
WayBillTrackBean bean = MyGson.GsonToBean(response, WayBillTrackBean.class);
if (bean != null) {
//轨迹列表
List<WayBillTrackBean.VehicleHistoryListBean> beans = bean.vehicleHistoryList;
if (beans != null && beans.size() > 0) {
points = new ArrayList<>();
for (int i = 0; i < beans.size(); i++) {
LatLng latLng = new LatLng(beans.get(i).lat, beans.get(i).lon);
points.add(latLng);
}
if (points != null && points.size() > 0) {
latLngStart = points.get(0);
latLngEnd = points.get(beans.size() - 1);
poiSize = points.size();//记录总数量
totalTime = ((double) poiSize) / 30;//记录总时间
mProgressBar.setMax(poiSize);
setMap();
}
}
//告警地点列表
List<WayBillTrackBean.AlarmInfoListBean> alarmBeans = bean.alarmInfoList;
setAlarmPoint(alarmBeans);
} else {
Log.e("aaaaaaa", "空数据");
}
}
}
});
}
/**
* 设置警告点
* @param alarmBeans:警告点坐标列表数据
*/
private void setAlarmPoint(List<WayBillTrackBean.AlarmInfoListBean> alarmBeans) {
if (alarmBeans != null && alarmBeans.size() > 0) {
ArrayList<MarkerOptions> options = new ArrayList<>();
for (int i = 0; i < alarmBeans.size(); i++) {
LatLng latLng = new LatLng(Double.parseDouble(alarmBeans.get(i).y2d), Double.parseDouble(alarmBeans.get(i).x2d));
MarkerOptions option = new MarkerOptions();
BitmapDescriptor descriptor = BitmapDescriptorFactory.fromResource(R.drawable.icon_alarm_png);
option.position(latLng).icon(descriptor);
options.add(option);
}
if (options != null && options.size() > 0) {
aMap.addMarkers(options, false);
} else {
}
} else {
}
}
private void setMap() {
//定位到起点
// CameraPosition cameraPosition = new CameraPosition(latLngStart, 13, 0, 30);
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLngStart, 12);
aMap.moveCamera(cameraUpdate);
//绘制起点的标记
MarkerOptions startOptions = new MarkerOptions();
BitmapDescriptor startBit = BitmapDescriptorFactory.fromResource(R.drawable.icon_start);
startOptions.position(latLngStart).icon(startBit);
aMap.addMarker(startOptions);
//绘制终点的标记
MarkerOptions endOptions = new MarkerOptions();
BitmapDescriptor endBit = BitmapDescriptorFactory.fromResource(R.drawable.icon_end);
endOptions.position(latLngEnd).icon(endBit);
aMap.addMarker(endOptions);
//绘制轨迹线
PolylineOptions lineOptions = new PolylineOptions().addAll(points).width(5).color(0xAAFFC107);
Polyline polyline = aMap.addPolyline(lineOptions);
//轨迹动画
setStartAnimation(points, true);
}
/**
* 轨迹动画
*/
private void setStartAnimation(List<LatLng> points, boolean isMove) {
if (smoothMoveMarker != null) {
smoothMoveMarker.stopMove();
smoothMoveMarker.removeMarker();
}
//若计时器不为空则清除,再重新设定计时器
stopTimer();
//设置滑动的图标
smoothMoveMarker = new SmoothMoveMarker(aMap);
smoothMoveMarker.setDescriptor(BitmapDescriptorFactory.fromResource(R.drawable.icon_truck));
LatLng drivePoint = points.get(0);
Pair<Integer, LatLng> pair = SpatialRelationUtil.calShortestDistancePoint(points, drivePoint);
points.set(pair.first, drivePoint);
List<LatLng> subList = points.subList(pair.first, points.size());
//设置滑动的轨迹左边点
smoothMoveMarker.setPoints(subList);
//设置滑动的总时间
double cur = ((double) current) / poiSize;
smoothMoveMarker.setTotalDuration((int) (totalTime * (1 - cur) + 0.5));
if (isMove) {
//开启计时器
startTimer();
//开始滑动
smoothMoveMarker.startSmoothMove();
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_pause_video));
isPlay = true;
} else {
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_play_video));
isPlay = false;
}
}
@Override
public void onResume() {
super.onResume();
//在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理
mMapView.onResume();
}
@Override
public void onPause() {
super.onPause();
//在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理
mMapView.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
// mBaiduMap.setMyLocationEnabled(false);
//在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
mMapView.onDestroy();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态
mMapView.onSaveInstanceState(outState);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (!getUserVisibleHint() && smoothMoveMarker != null) {
smoothMoveMarker.stopMove();
// smoothMoveMarker.removeMarker();
isFirstMove = false;
stopTimer();
mPlayBtn.setBackground(getResources().getDrawable(R.drawable.icon_play_video));
isPlay = false;
}
}
}