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

Android Google Maps SDK相机平滑移动路径瓦片完整渲染问题咨询

背景

此前Stack Overflow上已有不少类似问题,最具代表性的是GoogleMap.AnimateCamera()调用时地图不加载、Android端如何平滑移动GoogleMap相机两类问题,但所有相关帖子的回答和评论都未给出明确的实现方案。
我最初以为直接调用animateCamera(CameraUpdateFactory.newLatLng(), duration, callback)即可实现需求,但和同类问题的提问者一样,动画结束前地图全灰或非常模糊,哪怕把动画时长拉长到几十秒也没有改善!
我找到并实现了对应的辅助类,可以实现动画过程中瓦片逐段渲染,但哪怕设置延迟为0,各段动画之间仍存在明显卡顿。

现有实现代码

修改后的辅助类代码如下:

package com.coopmeisterfresh.googlemaps.NativeModules;

import android.os.Handler;

import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.GoogleMap;

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

public class CameraUpdateAnimator implements GoogleMap.OnCameraIdleListener {
    private final GoogleMap mMap;
    private final GoogleMap.OnCameraIdleListener mOnCameraIdleListener;

    private final List<Animation> cameraUpdates = new ArrayList<>();

    public CameraUpdateAnimator(GoogleMap map, GoogleMap.
        OnCameraIdleListener onCameraIdleListener) {
        mMap = map;
        mOnCameraIdleListener = onCameraIdleListener;
    }

    public void add(CameraUpdate cameraUpdate, boolean animate, long delay) {
        if (cameraUpdate != null) {
            cameraUpdates.add(new Animation(cameraUpdate, animate, delay));
        }
    }

    public void clear() {
        cameraUpdates.clear();
    }

    public void execute() {
        mMap.setOnCameraIdleListener(this);
        executeNext();
    }

    private void executeNext() {
        if (cameraUpdates.isEmpty()) {
            mOnCameraIdleListener.onCameraIdle();
        } else {
            final Animation animation = cameraUpdates.remove(0);

            new Handler().postDelayed(() -> {
                if (animation.mAnimate) {
                    mMap.animateCamera(animation.mCameraUpdate);
                } else {
                    mMap.moveCamera(animation.mCameraUpdate);
                }
            }, animation.mDelay);
        }
    }

    @Override
    public void onCameraIdle() {
        executeNext();
    }

    private static class Animation {
        private final CameraUpdate mCameraUpdate;
        private final boolean mAnimate;
        private final long mDelay;

        public Animation(CameraUpdate cameraUpdate, boolean animate, long delay) {
            mCameraUpdate = cameraUpdate;
            mAnimate = animate;
            mDelay = delay;
        }
    }
}

调用逻辑代码如下:

// 实际为React Native组件类,不影响原生逻辑实现
public class NativeGoogleMap extends SimpleViewManager<MapView> implements
    OnMapReadyCallback, OnRequestPermissionsResultCallback {

    // 省略其他无关方法
    private void animateCameraToPosition(LatLng targetLatLng, float targetZoom) {
        // googleMap为已初始化完成的GoogleMap实例
        CameraPosition currPosition = googleMap.getCameraPosition();
        LatLng currLatLng = currPosition.target;
        float currZoom = currPosition.zoom;

        double latDelta = targetLatLng.latitude - currLatLng.latitude;
        double lngDelta = targetLatLng.longitude - currLatLng.longitude;

        double latInc = latDelta / 5;
        double lngInc = lngDelta / 5;

        float zoomInc = 0;
        float minZoom = googleMap.getMinZoomLevel();
        float maxZoom = googleMap.getMaxZoomLevel();

        if (lngInc > 15 && currZoom > minZoom) {
            zoomInc = (minZoom - currZoom) / 5;
        }

        CameraUpdateAnimator animator = new CameraUpdateAnimator(googleMap,
            () -> googleMap.animateCamera(CameraUpdateFactory.zoomTo(
            targetZoom), 5000, null));

        for (double nextLat = currLatLng.latitude, nextLng = currLatLng.
            longitude, nextZoom = currZoom; Math.abs(nextLng) < Math.abs(
            targetLatLng.longitude);) {
            nextLat += latInc;
            nextLng += lngInc;
            nextZoom += zoomInc;

            animator.add(CameraUpdateFactory.newLatLngZoom(new
                LatLng(nextLat, nextLng), (float)nextZoom), true);
        }

        animator.execute();
    }
}

核心问题

是否有更优方案实现该需求?将动画逻辑迁移到工作线程执行是否可解决卡顿问题?

2021年9月30日更新

我已按照评论区建议修改了代码,功能可正常运行,但仍存在相同的卡顿和渲染问题。最终算法需要实现更复杂的逻辑:移动到经度差中点时先缩小缩放级别,后续移动过程中再逐步放大到目标级别。
我在低端测试机上运行时,同步完成所有计算、流畅渲染所有必要瓦片的负载过高,无法确定该问题是API本身的限制还是设备性能不足导致。请问如何实现队列动画完全无卡顿的流畅运行效果?


解决方案

首先可以明确的是,动画逻辑不能迁移到工作线程,Google Maps SDK的所有相机操作API都要求必须在主线程调用,迁移到子线程会直接抛出调用线程异常,无法解决问题。

现有方案卡顿的核心原因是:分段调用animateCamera依赖onCameraIdle回调触发下一段动画,本质是多段离散动画拼接,段与段之间天然存在停顿,同时每次相机移动都会触发瓦片重新加载请求,叠加后就会出现明显卡顿。

可按以下优先级优化:

  1. 改用ValueAnimator实现连续插值动画
    直接通过ValueAnimator控制相机每一帧的位置,实现真正的连续动画,同时可以自定义缩放逻辑,满足到中点缩小、后半段放大的需求。示例代码如下:
    private void animateCameraSmooth(LatLng targetLatLng, float targetZoom, long duration) {
        CameraPosition currPos = googleMap.getCameraPosition();
        LatLng startLatLng = currPos.target;
        float startZoom = currPos.zoom;
        float minZoom = googleMap.getMinZoomLevel();
        
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
        animator.setDuration(duration);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        
        animator.addUpdateListener(animation -> {
            float progress = (float) animation.getAnimatedValue();
            // 计算当前经纬度
            double currLat = startLatLng.latitude + (targetLatLng.latitude - startLatLng.latitude) * progress;
            double currLng = startLatLng.longitude + (targetLatLng.longitude - startLatLng.longitude) * progress;
            // 计算缩放:前半段缩小到最小,后半段放大到目标
            float currZoom;
            if (progress < 0.5f) {
                currZoom = startZoom + (minZoom - startZoom) * (progress / 0.5f);
            } else {
                currZoom = minZoom + (targetZoom - minZoom) * ((progress - 0.5f) / 0.5f);
            }
            // 直接移动相机到当前位置,无动画停顿
            googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(currLat, currLng), currZoom));
        });
        animator.start();
    }
    
  2. 预加载瓦片减少空白
    动画开启前可以先无动画移动到路径上的3-5个关键位置预加载瓦片,再退回起始点开动画;同时可以调用snapshot接口缓存当前帧,动画过程中优先显示缓存帧,避免灰屏。
  3. 低端机适配优化
    检测到设备性能不足时,可降低动画帧率到30fps,临时关闭地图的3D建筑、室内地图、交通图层等额外渲染项,动画结束后再恢复,降低渲染负载。
  4. 现有分段方案的临时优化
    如果要保留现有分段逻辑,可将分段数从5段提升到20-30段,每段移动距离更小,同时去掉onCameraIdle等待逻辑,直接按16ms间隔触发下一段动画,可大幅降低卡顿感。

内容的提问来源于stack exchange,提问作者Kenny83

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 23:54:04