You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现Uber式司机Marker平滑移动动画(解决闪烁回退问题)

解决Uber风格Marker平滑移动的闪烁与位置回退问题

嘿,我看了你遇到的Marker动画问题,这俩坑其实在做实时位置动画时挺常见的,咱们一步步来解决:

问题根源分析

先拆解下你当前代码的问题:

  1. Marker闪烁:你每次获取新位置就启动新动画,同时原来的Handler还在循环旧的points列表动画,多个动画实例同时修改Marker位置,加上频繁设置锚点、重建CameraPosition,导致地图频繁重绘,自然就闪了。
  2. 位置回退:你的动画逻辑依赖预定义的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:59:39