实现Uber式司机Marker平滑移动动画(解决闪烁回退问题)
解决Uber风格Marker平滑移动的闪烁与位置回退问题
嘿,我看了你遇到的Marker动画问题,这俩坑其实在做实时位置动画时挺常见的,咱们一步步来解决:
问题根源分析
先拆解下你当前代码的问题:
- Marker闪烁:你每次获取新位置就启动新动画,同时原来的Handler还在循环旧的
points列表动画,多个动画实例同时修改Marker位置,加上频繁设置锚点、重建CameraPosition,导致地图频繁重绘,自然就闪了。 - 位置回退:你的动画逻辑依赖预定义的
points列表循环,当服务器没返回新位置时,Handler还在遍历旧的点,Marker就会回到之前的位置,而不是停留在最后一次的真实位置。
具体修复方案
1. 先做好位置状态管理
首先要维护一个全局变量保存Marker的真实当前位置(动画结束后的最终位置),还要标记是否正在执行动画,避免多个动画冲突:
// 全局变量:保存Marker的真实当前位置 private LatLng currentMarkerPosition; // 标记是否正在执行动画,防止重叠 private boolean isAnimating = false;
2. 重构动画逻辑,去掉旧的循环
把动画抽成单独的方法,确保每次只有一个动画在运行,并且动画只在获取到新位置时触发,不再用Handler循环旧点:
private void startMarkerAnimation(LatLng start, LatLng end) { if (isAnimating) { // 如果当前有动画在跑,先取消,避免重叠闪烁 valueAnimator.cancel(); } isAnimating = true; ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); valueAnimator.setDuration(1500); // 调整合适的时长,1.5秒比较自然 valueAnimator.setInterpolator(new LinearInterpolator()); valueAnimator.addUpdateListener(animation -> { float fraction = animation.getAnimatedFraction(); // 计算当前动画帧的位置 double lat = start.latitude + (end.latitude - start.latitude) * fraction; double lng = start.longitude + (end.longitude - start.longitude) * fraction; LatLng animPos = new LatLng(lat, lng); // 更新Marker位置和朝向 marker.setPosition(animPos); marker.setRotation(getBearing(start, animPos)); // 平滑跟随相机,用newLatLng比重建CameraPosition更轻量 mMap.moveCamera(CameraUpdateFactory.newLatLng(animPos)); }); // 动画结束后更新状态 valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) {} @Override public void onAnimationEnd(Animator animator) { isAnimating = false; // 确保动画结束后Marker准确停在终点 marker.setPosition(end); } @Override public void onAnimationCancel(Animator animator) { isAnimating = false; } @Override public void onAnimationRepeat(Animator animator) {} }); valueAnimator.start(); }
3. 修改位置获取逻辑,只在新位置变化时启动动画
更新你的GetDriverloc方法,去掉旧的Handler循环,只在服务器返回有效新位置且位置变化足够大时启动动画:
private void GetDriverloc(HashMap<String, String> map) { Call<DriverLocationResToCus> call = apiInterface.GetDriverLoc(map); System.out.println("enter the currency alert api" + call.request().url()); call.enqueue(new Callback<DriverLocationResToCus>() { @Override public void onResponse(Call<DriverLocationResToCus> call, Response<DriverLocationResToCus> response) { if (response.isSuccessful()) { assert response.body() != null; // 假设DriverEndLat/Lng是司机最新位置,根据你的接口字段调整 double newLat = Double.parseDouble(response.body().getDriverCurrentLatEnd()); double newLng = Double.parseDouble(response.body().getDriverCurrentLngEnd()); LatLng newDriverLoc = new LatLng(newLat, newLng); if (currentMarkerPosition == null) { // 首次加载位置,直接设置Marker currentMarkerPosition = newDriverLoc; marker.setPosition(currentMarkerPosition); marker.setAnchor(0.5f, 0.5f); // 锚点只设一次 mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentMarkerPosition, 15.5f)); } else { // 计算位置变化距离,超过5米才启动动画(避免微小波动触发动画) float distance = calculateDistance(currentMarkerPosition, newDriverLoc); if (distance > 5) { startMarkerAnimation(currentMarkerPosition, newDriverLoc); currentMarkerPosition = newDriverLoc; } } Utilities.printV("DriverLocationResToCus", "DriverLocationResToCus SUCCESS"); } else { Utilities.printV("DriverLocationResToCus", "DriverLocationResToCus FAILURE"); } } @Override public void onFailure(Call<DriverLocationResToCus> call, Throwable t) { t.printStackTrace(); // 打印错误日志方便排查 } }); } // 辅助方法:计算两个经纬度之间的距离(米) private float calculateDistance(LatLng start, LatLng end) { Location locStart = new Location(""); locStart.setLatitude(start.latitude); locStart.setLongitude(start.longitude); Location locEnd = new Location(""); locEnd.setLatitude(end.latitude); locEnd.setLongitude(end.longitude); return locStart.distanceTo(locEnd); }
4. 最后清理旧代码
删除原来的mHandler.postDelayed循环逻辑,还有基于points列表的动画代码,这些是导致位置回退的根源。
这样修改后,Marker会从当前位置平滑移动到新的司机位置,不会闪烁,也不会在没新位置时回退到旧点。另外记得控制GetDriverloc的调用频率,比如每隔3-5秒请求一次,不要太频繁,既节省资源又避免动画太密集。
内容的提问来源于stack exchange,提问作者Aravind
相关产品推荐
相关产品推荐

